Login
ChallengesLearn
Scoreboard
Teams
SPNZ

LearnSQL InjectionSQL Injection: Database enumeration
SQL Injection·Lesson 11 of 20

SQL Injection: Database enumeration

You have one query. Map the entire database - tables, columns, types, row counts - before you start pulling data.

Intermediate16 min
SQLiReconSchema
Loading lesson…
PreviousSQL Injection: Time-based blindNextSQL Injection: Extracting data

© 2026 SPNZ.

Terms of ServicePrivacy PolicyCookie Policy

A UNION-based payload gives you one row per query. A good enumeration pipeline turns that into a complete map of the database - every table, every column, every type, every row count - before you start pulling data. This is the recon phase of a SQLi breach.

Prerequisites
Read these lessons first:
  • L6UNION-based extraction
  • L7Error-based extraction
What you'll be able to do
  • Use information_schema.tables and information_schema.columns to map the schema.
  • Discover which tables contain high-value data - users, tokens, sessions, payments - before you start pulling rows.
  • Use COUNT(*) to size a table before extracting it.
  • Recognise the schema differences between MySQL, PostgreSQL, and SQL Server.
Key terms
information_schema
A virtual schema in every ANSI-compliant RDBMS that exposes the database's own metadata - tables, columns, types, indexes, constraints. The first target of any enumeration.
Schema mapping
The process of building a complete picture of the database - every table, every column, every type - before extracting data.
High-value table
A table whose contents justify the cost of extraction: users, api_keys, sessions, orders, payments, credentials. The attacker's goal is to identify these tables first.
row_count estimation
A pre-extraction COUNT(*) to size the table. A 10-row table takes seconds; a 10-million-row table takes hours and is more likely to be detected.
What is it?

The metadata that ships with every database

Every modern RDBMS maintains a virtual schema called information_schema. It describes the database's own structure - tables, columns, types, indexes, constraints. The same SQL grammar that lets you query your data lets an attacker query the metadata.

sqlvulnerable
-- Step 1: list tables in the current database
UNION SELECT 1, table_name FROM information_schema.tables
WHERE table_schema = database()

-- Step 2: list columns in a chosen table
UNION SELECT 1, column_name FROM information_schema.columns
WHERE table_name = 'users'

-- Step 3: size the table
UNION SELECT 1, COUNT(*) FROM users

-- Step 4: dump a small sample
UNION SELECT id, username FROM users LIMIT 5

The same primitives work in PostgreSQL, SQL Server (where the metadata is exposed via sys.tables and sys.columns), and Oracle (all_tables, user_tables). MySQL is the friendliest target because the metadata is ANSI-compliant and indexed.

Three-step kill chain
Mini Map
Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.
Try it

Walk the three-step chain

The sandbox fakes a database with four tables - users, api_keys, orders, sessions. Pick the first target, then the column listing, then the dump. The information_schema queries return as if they were real UNION SELECT payloads.

stagingstaging.intranet.local/api/employees
intranet-portal
1
2
3
4
Step 1 - list tables

UNION SELECT table_name, table_schema FROM information_schema.tables WHERE table_schema = database()

sql-kw">SELECT id, name sql-kw">FROM products sql-kw">WHERE id = 1 sql-kw">UNION sql-kw">SELECT 1, table_name sql-kw">FROM information_schema.tables sql-kw">WHERE table_schema = database()-- 
Real-world relevance

Why attackers always start here

A SQLi finding without a schema map is a foothold. A SQLi finding with a schema map is a breach. Bug bounty payouts for SQLi findings scale with the value of the data the attacker can demonstrate access to - and a complete schema map is the most efficient way to prove that. A good bug report shows the table list, the high-value table names, and a single row from each. That is a P1; a single error message is a P3.

Defenders should expect the same probes. The first request from any scanner after a successful SQLi will hit information_schema.tables. A rule that lets all SELECT queries through but blocks that specific table is the most effective practical detection - the application almost never queries its own metadata.

Mitigation

The fix is the same; the defence-in-depth is sharper

Parameterised queries close the injection - the attacker's payload never becomes SQL grammar. Two defence-in-depth measures make enumeration harder even if a different vector opens the door:

  • Use a least-privilege database user for the application. The application should not be able to read information_schema at all; the migration tool runs as a different user.
  • Column-level encryption for PII (email, phone, address) means a successful dump is structurally incomplete - the attacker gets the schema but the values are AES-GCM ciphertext.
javascriptparameterised
// SAFE - the table name is still concatenated because the schema is
// chosen at runtime. Whitelist it.
const ALLOWED_TABLES = ['users', 'orders', 'products'];
if (!ALLOWED_TABLES.includes(tableName)) throw new Error('invalid table');
const query = `SELECT id, name FROM ${tableName} WHERE id = $1`;
await db.query(query, [id]);

Even whitelisted identifiers must be checked against a known set - never let the client choose a table or column name without a guard.

Further reading
  • MySQL information_schema reference(MySQL)
  • PostgreSQL system catalogs(PostgreSQL)
  • SQL injection cheat sheet (PortSwigger)(PortSwigger)
Key takeaways

What to remember

  • information_schema.tables and information_schema.columnsare the attacker's first two queries after a successful injection.
  • Map the schema before you start pulling data. A 10-row table and a 10-million-row table take different amounts of time and noise.
  • Use a least-privilege application database user that cannot read information_schema as defence-in-depth.
  • Whitelist any identifier (table, column, sort column) that is chosen at runtime.

Knowledge check

0/3 answered · 0 correct
  1. 1.Why is the first query after a successful SQLi always information_schema.tables?

  2. 2.A bug bounty report shows a single SQL error message containing the table name "users". A second report shows the full information_schema.tables output. Which is the higher-severity finding?

  3. 3.Why should the application database user NOT be able to read information_schema?