前言:zip
壓縮格式應用廣泛,各個平臺都有使用,Windows
平臺使用來壓縮文件,Android
平臺使用來作為apk
文件的格式。由于zip
文件格式比較復雜,在解析zip
文件格式時,如果處理不當,可能導致一些有意思的邏輯漏洞,本篇文章將挑選有意思的漏洞進行解析。
很早之前,國外安全研究人員爆料Winrar 4.x版本存在文件擴展名欺騙漏洞,黑客可以通過該漏洞誘騙受害者執行惡意程序。該漏洞的主要原理是:Winrar
在文件預覽和解壓縮顯示文件名使用的是不同結構體的字段導致的。
在了解漏洞的原理前,先熟悉下zip格式的文件結構。
如果一個壓縮包文件里有多個文件,可以認為每個文件都是被單獨壓縮,然后再拼成一起。
一個 ZIP
文件由三個部分組成:壓縮源文件數據區+壓縮源文件目錄區+壓縮源文件目錄結束標志,如下圖:
1)文件頭(壓縮源文件目錄區)在文件末尾,即圖1中的File Header
,記錄了索引段的偏移、大小等等。
2)數據段(壓縮源文件數據區)在文件開頭,即圖1中的Local Header
,記錄了數據的一些基本信息,可以用來跟File Header
中記錄的數據進行比較,保證數據的完整性。
3)Local Header
還包含了文件被壓縮之后的存儲區,即圖1中的Data
區域。
4)圖2和圖3為Local Header
(圖2中的ZIPFILERECORD
)和File Header
(圖3中的ZIPDIRENTRY
)的數據對比,兩者數據是一致的。
Winrar
在文件預覽的時候使用的是ZIPDIRENTRY
下面的deFileName
字段來顯示文件名,解壓縮的時候使用的是ZIPFILERECORD
下面的frFileName
字段來顯示文件名。如果將deFileName
字段文件擴展名改成jpg
、gif
等圖片的文件擴展名,可以欺騙用戶運行惡意程序。
Winrar
文件預覽示意圖:
用戶看到的是jpg
圖片,打開的確實exe
文件,真坑啊!
Winrar
解壓縮文件示意圖:
解壓縮之后顯示的exe
,兩處顯示的不一樣。
之前,國外安全研究人員爆出第三個Android Master Key漏洞,該漏洞的主要原理是:android
在解析Zip
包時,沒有校驗ZipEntry
和Header
中的FileNameLength
是否一致。
在了解漏洞的原理前,還是先熟悉下zip
格式的文件結構。
如果一個壓縮包文件里有多個文件,可以認為每個文件都是被單獨壓縮,然后再拼成一起。
一個 ZIP
文件由三個部分組成:壓縮源文件數據區+壓縮源文件目錄區+壓縮源文件目錄結束標志,如圖1所示:
1)文件頭(壓縮源文件目錄區)在文件末尾,即圖1中的File Header
,記錄了索引段的偏移、大小等等。
2)數據段(壓縮源文件數據區)在文件開頭,即圖1中的Local Header
,記錄了數據的一些基本信息,可以用來跟File Header
中記錄的數據進行比較,保證數據的完整性。
3)Local Header
還包含了文件被壓縮之后的存儲區,即圖1中的Data
區域。
4)圖2和圖3為Local Header
(圖2中的ZIPFILERECORD
)和File Header
(圖3中的ZIPDIRENTRY
)的數據對比,兩者數據是一致的。
先來看一下是如何定位到Local Header
中的Data
數據:
off64_t dataOffset = localHdrOffset +
kLFHLen +
get2LE(lfhBuf + kLFHNameLen) +
Data
的偏移是通過Header
的起始偏移+Header
的大小(固定值)+Extra data
的大小+文件名的大小,如下圖
回頭看一下,java
在獲取Data
偏移的處理,在讀取Extra data
的長度的時候,它已經預存了文件名在FileHeader
中的長度。
// We don't know the entry data's start position.
// All we have is the position of the entry's local
// header. At position 28 we find the length of the
// extra data. In some cases this length differs
// from the one coming in the central header.
RAFStream rafstrm = new RAFStream(raf,
entry.mLocalHeaderRelOffset + 28);
DataInputStream is = new DataInputStream(rafstrm);
int localExtraLenOrWhatever =
漏洞就在這里產生了,如果Local Header
中的FileNameLength
被設成一個大數,并且FileName
的數據包含原來的數據,File Header
中的FileNameLength
長度不變,那么底層C++
運行和上層Java
運行就是不一樣的流程。
C++ Header 64k Name Data
+--------> +----------------------> +---------->
length=64k classes.dex dex\035\A... dex\035\B...
+--------> +---------> +---------->
如上面所示,底層C++
的執行會讀取64k的FileName
長度,而Java
層由于是讀取File Header
中的數據,FileName
的長度依舊是11,于是Java
層校驗簽名通過,底層執行會執行惡意代碼。