Description
This secret box is designed to conceal your secrets. It's perfectly secure - only you can see what's inside. Or can you? Try uncovering the admin's secret.
Setup
Launch the challenge instance and open the web application.
Register an account to explore how secrets are stored.
Solution
Want to try it yourself first?
The guided walkthrough reveals hints one step at a time.
Step 1
Sign up and download the source codeObservationI noticed the challenge provides downloadable source code and involves a web app with user accounts, which suggested auditing the server-side SQL queries for any endpoints that concatenate user input rather than using parameterized statements.Register an account and log in. Download the source code from the challenge page. Look for a SQL query in the 'create secret' flow that does not use a parameterized query.bash# In the browser: register a new account, log inbash# Download the source code from the challenge pagebash# Look for vulnerable query in the create secret handlerWhat didn't work first
Tried: Focus the audit on the login endpoint, assuming login forms are the classic SQL injection entry point.
The login query uses a parameterized prepared statement, so no injection is possible there. The vulnerable INSERT is in the create-secret handler, not the authentication path. Reading only the login-related source files causes you to miss the actual sink entirely.
Tried: Search for 'SELECT' in the source to find injection points rather than looking at INSERT statements.
The injection happens in an INSERT query where user-supplied content is concatenated directly into the SQL string. Grepping only for SELECT skips the vulnerable write path. Any DML statement that interpolates user input without parameterization is a potential injection point.
Learn more
When reading source code, look for SQL queries built with string concatenation (
+or template literals) rather than parameterized queries (prepared statements with?placeholders). The create secret endpoint is likely vulnerable because the content field is inserted directly into a SQL string.Step 2
Inject SQL in the content field to extract the admin's passwordObservationI noticed the source code revealed that the create-secret INSERT statement interpolates the content field directly into the SQL string without parameterization, which suggested using SQLite's || concatenation operator to embed a subquery that reads the admin's password from the users table.In the create secret form, instead of normal content, enter a SQL injection payload using the concatenation operator (|| in SQLite). The payload closes the current string, concatenates the admin's password from the users table, and reopens the string. After submitting, view your secret to read the admin's password. Then log out and log in as admin with that password to see the flag in the admin's vault.bash# In the create secret form, enter this as the content:bash' || (SELECT password FROM users WHERE username='admin') || 'bash# View your secrets - your new secret's content will be the admin's passwordbash# Log out, then log in as admin using the extracted password to see the flagWhat didn't work first
Tried: Use MySQL-style string concatenation with CONCAT() instead of the SQLite || operator: ' || CONCAT((SELECT password FROM users WHERE username='admin')) || '
SQLite does not have a CONCAT() function. The payload will cause a syntax error and the secret will not be created. SQLite uses || for string concatenation, so the correct payload is ' || (SELECT password FROM users WHERE username='admin') || '.
Tried: Look for the flag directly in your own vault after submitting the injection, assuming the flag is stored as a secret in your account.
The flag is stored in the admin's secret vault, not yours. The injection only copies the admin's password into your secret's content field. You must take that extracted password, log out, and log in as admin to view the flag in the admin's own vault.
Learn more
The vulnerable query is the INSERT for creating a secret. Inject
' || (SELECT password FROM users WHERE username='admin') || 'as the content. The database stores the admin's plaintext password as your secret's content. After reading your secret, log out and log in as admin with that password to see the flag in the admin's vault.||is SQLite's string concatenation operator. When this query executes, the subquery runs inline and its result becomes the stored value. The flag is not in your vault directly - it is in the admin's vault, which you access by logging in as admin.See SQL injection for CTF for the full pattern catalogue.
Interactive tools
- SQL Injection Payload GeneratorGenerate SQL injection payloads for auth bypass, UNION extraction, blind SQLi, NoSQL operator injection, and sqlmap commands. Supports MySQL, PostgreSQL, SQLite, and MSSQL.
Flag
Reveal flag
picoCTF{sq1_1nject10n_...}
SQL injection in the create-secret content field (login uses safe prepared statements, but the INSERT interpolates content). Use ' || (SELECT password FROM users WHERE username='admin') || ' to store the admin's password in your secret. Then log out and log in as admin with that password to view the flag in the admin's vault.