CTF:PHP 多关卡绕过挑战
题目链接:http://node4.anna.nssctf.cn:28789/
题目类型:Web 安全 - PHP 漏洞利用
题目分析
这是一个包含三个关卡的 PHP 安全挑战,需要通过构造特定的参数来绕过每一关的验证。
<?php
highlight_file(__FILE__);
include "./flag.php";
include "./result.php";
if(isset($_GET['aaa']) && strlen($_GET['aaa']) < 20){$aaa = preg_replace('/^(.*)level(.*)$/', '${1}<!-- filtered -->${2}', $_GET['aaa']);if(preg_match('/pass_the_level_1#/', $aaa)){echo "here is level 2";if (isset($_POST['admin']) and isset($_POST['root_pwd'])) {if ($_POST['admin'] == $_POST['root_pwd'])echo '<p>The level 2 can not pass!</p>';// START FORM PROCESSING else if (sha1($_POST['admin']) === sha1($_POST['root_pwd'])){echo "here is level 3,do you kown how to overcome it?";if (isset($_POST['level_3'])) {$level_3 = json_decode($_POST['level_3']);if ($level_3->result == $result) {echo "success:".$flag;}else {echo "you never beat me!";}}else{echo "out";}}else{die("no");}// perform validations on the form data}else{echo '<p>out!</p>';}}else{echo 'nonono!';}echo '<hr>';
}?>
第一关:正则匹配绕过
第一关的核心代码是:
$aaa = preg_replace('/^(.*)level(.*)$/', '${1}<!-- filtered -->${2}', $_GET['aaa']);
if(preg_match('/pass_the_level_1#/', $aaa)){echo "here is level 2";// ...
}
关键点:
- 需要通过 GET 参数
aaa
传递包含pass_the_level_1#
的字符串 - 字符串长度小于 20
- 不能被正则表达式
/^(.*)level(.*)$/
匹配到level
绕过方法:
使用换行符\n
(URL 编码为%0a
)在level
前,使正则表达式无法从字符串开头匹配到level
:
GET参数:aaa=%0apass_the_level_1%23
这样构造的字符串实际上是\npass_the_level_1#
,虽然包含level
,但由于level
前面有换行符,正则无法匹配到level
,从而成功绕过过滤,同时保留了pass_the_level_1#
字符串。
第二关:PHP 弱类型与数组哈希绕过
第二关的核心代码是:
if ($_POST['admin'] == $_POST['root_pwd'])echo '<p>The level 2 can not pass!</p>';
else if (sha1($_POST['admin']) === sha1($_POST['root_pwd'])){echo "here is level 3,do you kown how to overcome it?";// ...
}
关键点:
- 需要 POST 两个参数:
admin
和root_pwd
- 这两个参数不能相等(
==
弱类型比较) - 但它们的 SHA1 哈希值必须相等(
===
强类型比较)
绕过方法:
利用 PHP 对数组的特殊处理特性:
POST参数:admin[]=123&root_pwd[]=125
原理分析:
-
PHP 中使用
[]
符号会创建数组,因此:admin[]=123
创建的数组是[0 => "123"]
root_pwd[]=125
创建的数组是[0 => "125"]
-
当使用
==
比较两个数组时:- PHP 会比较数组的结构和元素值
- 由于两个数组的第一个元素分别是 "123" 和 "125",它们不相等
- 因此,
$_POST['admin'] == $_POST['root_pwd']
的结果是false
-
当对数组使用
sha1()
函数时:- PHP 会尝试将数组转换为字符串
- 数组转换为字符串的结果通常是 "Array"
- 因此,
sha1($_POST['admin'])
和sha1($_POST['root_pwd'])
都会计算字符串 "Array" 的 SHA1 哈希值 - 所以,
sha1($_POST['admin']) === sha1($_POST['root_pwd'])
的结果是true
第三关:JSON 解析与变量比较
第三关的核心代码是:
if (isset($_POST['level_3'])) {$level_3 = json_decode($_POST['level_3']);if ($level_3->result == $result) {echo "success:".$flag;}else {echo "you never beat me!";}
}
关键点:
- 需要 POST 一个 JSON 字符串参数
level_3
- JSON 对象中的
result
属性值必须等于$result
变量的值 $result
变量在result.php
文件中定义
绕过方法:
通过测试发现$result
变量的值为ioi
,构造 JSON 字符串:
POST参数:level_3={result:ioi}
注意事项:
标准 JSON 应该使用双引号包围键和字符串值,但 PHP 的json_decode
函数在某些情况下可以容忍这种非标准格式。
完整 POC
将三个关卡的绕过方法组合起来,得到完整的 POC:
GET请求:http://node4.anna.nssctf.cn:28789/?aaa=%0apass_the_level_1%23POST数据:
admin[]=123
root_pwd[]=125
level_3={result:ioi}
总结
这个题目主要考察了以下几个 PHP 特性:
- 正则表达式匹配的边界条件和字符串构造技巧
- PHP 的弱类型比较和哈希函数对数组的特殊处理
- JSON 解析的灵活性和对非标准格式的容忍度
通过巧妙地构造参数,我们成功绕过了三关验证,获取了 flag。这也提醒我们在开发 PHP 应用时,需要特别注意这些语言特性可能带来的安全隐患。