June 22, 2026
I Made the Database Confess Its Own Name
Part 3 of my SQL injection series. First I stole hidden data. Then I logged in as admin with no password. This time, I make the database…

By morgan_hack
5 min read
Part 3 of my SQL injection series. First I stole hidden data. Then I logged in as admin with no password. This time, I make the database tell me exactly what it is — because that one answer unlocks everything that comes next.
Three labs in, and the pattern is clear.
In Lab 1, I pulled out products the shop was hiding from customers. In Lab 2, I walked into the admin account without a password. Both were loud, obvious wins.
This one is quieter. And in a real attack, it's the move that matters most — because it comes first.
Before an attacker steals your data, they ask one question: what database am I even talking to? Get that answer, and every later attack becomes a precision strike instead of a guess.
This lab ran on Oracle [one of the big, expensive databases that banks and enterprises run on]. And I made a gift-shop page print Oracle's full version string in plain sight.
The bug in plain words. The website builds its database question by gluing my typing directly into it. Nothing checks my input. So if I type database commands, the database obeys them as if a developer wrote them. That's SQL injection [SQL is the language databases speak; "injection" means slipping my own commands into it].
What I Noticed
I opened the shop through Burp Suite [a tool that sits between my browser and the website so I can read and edit every request before it's sent].
Clicking a product category fired one request:
GET /filter?category=GiftsGET /filter?category=GiftsThat category=Gifts is my input reaching the database. That's the doorway. I sent the request to Burp Repeater [a panel where I change one request and re-send it as many times as I want].
Then the oldest test in the book. Add a single quote:
GET /filter?category=Gifts'GET /filter?category=Gifts'
500 Internal Server Error : 500 error after the single quote
That error is a confession. My quote broke the database's own sentence. My input lands inside the query, raw and untrusted. We're in.
The action, step by step
I'm setting up a UNION attack [UNION staples a second result onto the first one, so my data rides out alongside the real product data]. UNION has one rule: both halves must have the same number of columns [columns = the vertical fields in a result, like a spreadsheet's headers]. So first, I count them.
I climb with ORDER BY [sorts the results by column number — and throws an error the moment I name a column that doesn't exist:
Gifts' ORDER BY 1-- -> 200 OK
Gifts' ORDER BY 2-- -> 200 OK
Gifts' ORDER BY 3-- -> 500 error
Gifts' ORDER BY 1-- -> 200 OK
Gifts' ORDER BY 2-- -> 200 OK
Gifts' ORDER BY 3-- -> 500 error
The -- is a comment [it deletes whatever the developer wrote after my payload, so their leftover quote can't break my line]. Column 3 doesn't exist. The query has 2 columns.
Now I try two empty columns:Now I try two empty columns:500 error. The count is right, so why fail? This is the clue. Oracle is the only major database that refuses a SELECT with no table attached. So I give it Oracle's built-in dummy table, dual:
500 error. The count is right, so why fail? This is the clue. Oracle is the only major database that refuses a SELECT with no table attached. So I give it Oracle's built-in dummy table, dual:
Gifts' UNION SELECT NULL,NULL FROM dual--Gifts' UNION SELECT NULL,NULL FROM dual--That one word — dual — is the fingerprint. Only Oracle needs it. I now know the database brand without it ever telling me directly.
Which column actually shows on the page? I swap the empty values for bright labels:
Gifts' UNION SELECT 'abc','def' FROM dual--Gifts' UNION SELECT 'abc','def' FROM dual--Both abc and def appear in the product list. Both columns print text
What fell out
Oracle stores its version in a built-in view called v$version, in a column named BANNER. I ask for it:
Gifts' UNION SELECT BANNER,NULL FROM v$version--Gifts' UNION SELECT BANNER,NULL FROM v$version--And the page — a listing for a gift shop — calmly prints Oracle's full version banne
Brand. Edition. Exact version number.
That string is the master key. It tells an attacker precisely which known break-ins [public exploits tied to that exact version] will work next.
(One honest stumble: I kept typing new payloads over half-erased old ones, making junk like Gifts'Gifts' and random 500s. The fix is dull but real — delete the whole value after category= before typing the next one.)
Why This Works
The developer wrote something like ... WHERE category = '[your input]' and dropped my input straight in. There's no wall between code and data, so my data becomes code.
The real fix is a parameterized query [the input is handed to the database as a sealed value that can never be read as a command]. Developers cause this by building queries with simple text-joining because it's fast and feels harmless — right up until someone sends a quote.
What This Costs a Real Business
On its own, leaking a version number sounds minor. It isn't. It's the cheap first domino — and here's the line of dominoes behind it:
- Reconnaissance that arms the attacker. The exact version maps straight to a list of public exploits. The attacker stops guessing and starts aiming.
- Full data breach. The same injection that reads the version reads the tables — customer names, emails, password hashes [scrambled passwords that can often be cracked], order history, payment details.
- Regulatory fines. Under GDPR [the EU data-protection law], a serious breach can cost up to €20 million or 4% of worldwide annual revenue, whichever is larger.
- Breach-response bills. Mandatory customer notifications, free credit monitoring, forensic investigators, and lawyers — all before a single lawsuit lands.
- Lost customers and brand damage. "Our shop got hacked" is the kind of headline people remember when they choose where to spend next.
- Server takeover. On some setups, database features let an attacker run operating-system commands — turning a leaky web form into full control of the machine.
Now stack the series together. Lab 1 read hidden data. Lab 2 bypassed the login. Lab 3 identified the engine. Chain those three on a real site and you don't have a bug — you have a breach.
Real Bug Bounty Payouts
- A researcher reported SQL injection to Starbucks; after manual testing turned it into a time-based attack, sqlmap confirmed it and returned the database version (Microsoft SQL Server 2012), and Starbucks paid a $4,000 bounty for the critical issue. The exact opening move of this lab — read the version. HackerOne
- A SQL injection in Valve's report_xml.php via the countryFilter[] parameter paid $25,000 on HackerOne. GitHub
- A time-based SQL injection at city-mobil.ru, reported to Mail.ru, paid $15,000. GitHub
- A UNION-based SQL injection on zomato.com, chained with a web-firewall bypass, paid $1,000 to Eternal — the same UNION technique used here, just pushed past a filter. GitHub