反序列化漏洞最近一直不得安寧,先有Apache Commons Collections通過反序列化實現遠程代碼執行,再有Spring RMI 反序列化漏洞,最新又有了common upload file的反序列化漏洞CVE-2016-1000031(https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-1000031)
漏洞原因
先來看DiskFileItem.java 代碼
/**
* Reads the state of this object during deserialization.
*
* @param in The stream from which the state should be read.
*
* @throws IOException if an error occurs.
* @throws ClassNotFoundException if class cannot be found.
*/
private void readObject(ObjectInputStream in)
throws IOException, ClassNotFoundException {
// read values
in.defaultReadObject();
/* One expected use of serialization is to migrate HTTP sessions
* containing a DiskFileItem between JVMs. Particularly if the JVMs are
* on different machines It is possible that the repository location is
* not valid so validate it.
*/
if (repository != null) {
if (repository.isDirectory()) {
// Check path for nulls
if (repository.getPath().contains("\0")) {
throw new IOException(format(
"The repository [%s] contains a null character",
repository.getPath()));
}
} else {
throw new IOException(format(
"The repository [%s] is not a directory",
repository.getAbsolutePath()));
}
}
OutputStream output = getOutputStream();
if (cachedContent != null) {
output.write(cachedContent);
} else {
FileInputStream input = new FileInputStream(dfosFile);
IOUtils.copy(input, output);
dfosFile.delete();
dfosFile = null;
}
output.close();
cachedContent = null;
}
DiskFileItem裏面封裝了反序列的方法,在反序列化的方法裏1. 序列化中的數據可以保存在服務器的臨時文件中
2. 複製服務器的文件到臨時文件
3. 刪除服務器的文件
private transient DeferredFileOutputStream dfos;
public OutputStream getOutputStream()
throws IOException {
if (dfos == null) {
File outputFile = getTempFile();
dfos = new DeferredFileOutputStream(sizeThreshold, outputFile);
}
return dfos;
}
dfos 是不可序列化的,反序列化中dfos爲空,只能獲取臨時文件 protected File getTempFile() {
if (tempFile == null) {
File tempDir = repository;
if (tempDir == null) {
tempDir = new File(System.getProperty("java.io.tmpdir"));
}
String tempFileName = format("upload_%s_%s.tmp", UID, getUniqueId());
tempFile = new File(tempDir, tempFileName);
}
return tempFile;
}
也就是基於設置的repository 目錄的臨時文件
從風險角度來考慮
1. 複製到臨時文件upload_UID_UniqueID.tmp
2. 可以刪除任意有刪除權限的文件,問題的風險性並不高
CVE的描述
CVE所提示的風險感覺定義不清楚
Apache Commons FileUpload DiskFileItem File Manipulation Remote Code Execution
DiskFileItem中注入執行代碼,也無法在反序列化的時候被執行,這個和collection的反序列化的漏洞的風險是完全不同的,不清楚爲何描述成可以操縱執行遠端代碼,是否是只要是反序列化的漏洞就是可以執行遠端代碼?JDK6下的風險
JDK7以上在Java的file相關的基礎類中都做了空字符的保護,這也是在針對java的string 和 c char的結束方式不一致,在Java中文件的操作中使用String這種char 數組,而C中的char 是以空字符爲結束符,所以java操作的文件中很容易通過注入空字符來操作完全不同的文件
比如Java File file = new File("/test/test.txt\0.jsp") 看起來再操作 test.txt\0.jsp 實際上在底層調用的(本質還是c讀寫文件)是在操作test.txt
在JDK7以後的版本File 裏面會有一個判斷是否有空字符的函數
final boolean isInvalid() {
if (status == null) {
status = (this.path.indexOf('\u0000') < 0) ? PathStatus.CHECKED
: PathStatus.INVALID;
}
return status == PathStatus.INVALID;
}
而在很多關鍵操作中都會有isInValid 函數的判斷,避免被注入空字符繞過風險,但是JDK6並沒有進行防護,在JDK6是可以通過輸入空字符串進行文件名控制。
1. 我們先看目錄的空字符是否有機會注入,在readObject的代碼中,我們看到了在判斷目錄的時候,對路徑進行了空字符保護
if (repository != null) {
if (repository.isDirectory()) {
// Check path for nulls
if (repository.getPath().contains("\0")) {
throw new IOException(format(
"The repository [%s] contains a null character",
repository.getPath()));
}
} else {
throw new IOException(format(
"The repository [%s] is not a directory",
repository.getAbsolutePath()));
}
}
2. 在文件名中我們看是否有機會注入空字符
String tempFileName = format("upload_%s_%s.tmp", UID, getUniqueId());
UID, getUniqueId 都是可以構造的,但只能構造upload_爲前綴的文件名
3. 通過相對路徑構建任意文件名
構建 設置UID = /../../anyfile\0
只要有upload_爲前綴的目錄存在,通過相對路徑,可以生成任意文件,同時最後注入空字符,結束文件名。這樣就可以在服務器端生產任意文件
生成任意文件前提
1. JDK6 以下的版本
2. 必須在系統裏存在upload_爲前綴的目錄存在
顯然形成這樣的攻擊,前提條件是苛刻的
其他的風險
Dos攻擊
因爲使用了 DeferredFileOutputStream ,可以設置一個超大的sizeThreshold(保存在內存裏),設置一個系統裏的超大文件dfosFile在反序列化時複製dfosFile
到DefferredFileOutputStream導致JVM OOM
刪除任意可刪除的文件
設置dfosFile 爲任意文件,因爲反序列化後,複製完後會刪除dfosFile,這樣達到刪除任意文件的目的
通過上述分析,我們可以看到上傳文件反序列化的問題,並沒有什麼高危操作,而且有些攻擊還必須有場景配合。
APACHE的官方態度
Apache 目前給出幾個方案
1. 不認爲是問題
2. 去除DiskFileItem.java的反序列化功能,這顯然遭到了反對
3. 和collection解決方案一樣在添加個系統參數的開關,如果打開的話,才能進行反序列化,就好像如果你打開了開關,就必須自己校驗當從外部非可信域傳入的時候
目前還無選擇結果
反序列化JVM的不作爲
目前JVM在反序列化的時候,JVM無法通過沙箱設置反序列化的類的黑白名單,只確保的反序列化類是在class loader 能初始化的。
對反序列化的保護,目前常見的解決方案擴展ObjectInputStream,在resolveClass方法裏進行類的黑白名單保護,但涉及到上層代碼的改動。