看了網(wǎng)易的新聞蕴掏,得知很多app有ZipperDown安全隱患蹬挤,故了解了一下缚窿。僅供參考,望指正焰扳。
漏洞的攻擊原理倦零。
- 漏洞與zip文件有關(guān),ios沒有提供官方的unzip API函數(shù)吨悍,基本上現(xiàn)有的ios App都是使用的SSZipArchive或ziparchive這兩個第三方庫來實現(xiàn)解壓的功能光绕。使用第三方zip庫在解壓zip文件過程中沒有考慮文件名中帶有”../../”這樣的情況,從而產(chǎn)生了目錄穿越漏洞畜份。因此,如果一個iOS 應(yīng)用下載了惡意的zip文件欣尼,并且使用ziparchive庫解壓爆雹,利用漏洞可以做到app container目錄下的任意文件覆蓋,如果覆蓋了應(yīng)用重要的文件會造成應(yīng)用崩潰(DOS)愕鼓,如果覆蓋了app的hotpatch文件則會造成代碼執(zhí)行钙态。
- 攻擊條件:
1.APP用了ZipArchive
2.原APP下發(fā)的某個zip包傳輸過程沒加密,zip包也沒加密
3.原APP使用了JSPatch或其他執(zhí)行引擎菇晃,且本地腳本沒有加密册倒,只要把腳本放指定目錄即可執(zhí)行
4.用戶連上第三方wifi遭受攻擊。
漏洞原理
ZipperDown漏洞并非iOS平臺自身問題磺送,而是與Zip文件解壓有關(guān)驻子。iOS平臺沒有提供官方的unzipAPI函數(shù),而是引用了第三方庫來實現(xiàn)解壓功能估灿,由于現(xiàn)有的iOS App基本上采用SSZipArchive或Ziparchive來實現(xiàn)解壓崇呵,因此漏洞是來自使用第三方Zip庫解壓Zip文件的過程中沒有對Zip內(nèi)文件名做校驗導(dǎo)致的。如果文件名中含有“../”則可以實現(xiàn)目錄的上一級跳轉(zhuǎn)馅袁,從而實現(xiàn)應(yīng)用內(nèi)任意目錄的跳轉(zhuǎn)域慷,進(jìn)一步可以實現(xiàn)文件覆蓋,如果把App的hotpatch文件覆蓋替換了汗销,可以達(dá)到執(zhí)行黑客指定指令犹褒,從而按照黑客的意圖實現(xiàn)任意應(yīng)用內(nèi)攻擊。
這個漏洞不禁讓易盾聯(lián)想到不久前Android平臺上的unZip解壓文件漏洞弛针,和這個漏洞幾乎是完全一樣叠骑,只是平臺和第三方解壓庫不同而已。Android平臺上的被稱為unZip解壓文件漏洞削茁,網(wǎng)易云易盾安全檢測平臺已經(jīng)可以實現(xiàn)掃描檢測座云。
- ------- SSZipArchive ---------------
壓縮文件是允許路徑指向類似../A/../B這種格式的, UNIX下../代表這個文件夾的上一層芝雪。比如說/A/B/../C實際上指的是/A/C
有問題的解壓庫沒有對這種../做過濾,也就是說可以往解壓路徑外的地方解壓文件刨肃。這不是一個系統(tǒng)級的沙盒逃逸漏洞
很多App會把所謂的熱更新補(bǔ)丁放在沙盒內(nèi)的某個路徑下孽文,比如說我們叫Documents/A.js吧,如果熱更新的傳輸過程有中間人攻擊的問題或者app被通過某種方式打開惡意的壓縮包, 攻擊者就可以覆蓋A.js的內(nèi)容璧帝,這樣下次app啟動加載熱更新補(bǔ)丁A.js時就會執(zhí)行惡意代碼捍岳。
SSZipArchive
我們在開發(fā)app的時候,有時會需要對文件進(jìn)行壓縮和解壓的操作睬隶,比如百度網(wǎng)盤锣夹,這個時候我們就必須要用到一個第三方的開源庫,SSZipArchive 苏潜,來對目標(biāo)文件進(jìn)行壓縮和解壓的操作银萍。
// Unzip 解壓
/**
* @param path 源文件
* @param destination 目的文件
* @param uniqueId 標(biāo)記,用于區(qū)別多個解壓操作
*
* @return 返回 YES 表示成功恤左,返回 NO 表示解壓失敗贴唇。
*/
+ (BOOL)unzipFileAtPath:(NSString *)path toDestination:(NSString *)destination uniqueId:(NSString *)uniqueId;
/**
* @param path 源文件
* @param destination 目的文件
* @param overwrite YES 會覆蓋 destination 路徑下的同名文件,NO 則不會飞袋。
* @param password 需要輸入密碼的才能解壓的壓縮包
* @param error 返回解壓時遇到的錯誤信息
* @param uniqueId 標(biāo)記戳气,用于區(qū)別多個解壓操作
*
* @return 返回 YES 表示成功,返回 NO 表示解壓失敗巧鸭。
*/
+ (BOOL)unzipFileAtPath:(NSString *)path toDestination:(NSString *)destination overwrite:(BOOL)overwrite password:(NSString *)password error:(NSError **)error uniqueId:(NSString *)uniqueId;
/**
* @param path 源文件
* @param destination 目的文件
* @param delegate 設(shè)置代理
* @param uniqueId 標(biāo)記瓶您,用于區(qū)別多個解壓操作
*
* @return 返回 YES 表示成功,返回 NO 表示解壓失敗纲仍。
*/
+ (BOOL)unzipFileAtPath:(NSString *)path toDestination:(NSString *)destination delegate:(id<SSZipArchiveDelegate>)delegate uniqueId:(NSString *)uniqueId;
/**
* @param path 源文件
* @param destination 目的文件
* @param overwrite YES 會覆蓋 destination 路徑下的同名文件呀袱,NO 則不會。
* @param password 需要輸入密碼的才能解壓的壓縮包
* @param error 返回解壓時遇到的錯誤信息
* @param delegate 設(shè)置代理
* @param uniqueId 標(biāo)記郑叠,用于區(qū)別多個解壓操作
*
* @return 返回 YES 表示成功压鉴,返回 NO 表示解壓失敗。
*/
+ (BOOL)unzipFileAtPath:(NSString *)path toDestination:(NSString *)destination overwrite:(BOOL)overwrite password:(NSString *)password error:(NSError **)error delegate:(id<SSZipArchiveDelegate>)delegate uniqueId:(NSString *)uniqueId;
/*
* 解壓
*/
+ (BOOL)unzipFileAtPath:(NSString *)path
toDestination:(NSString *)destination
preserveAttributes:(BOOL)preserveAttributes
overwrite:(BOOL)overwrite
nestedZipLevel:(NSInteger)nestedZipLevel
password:(nullable NSString *)password
error:(NSError **)error
delegate:(nullable id<SSZipArchiveDelegate>)delegate
progressHandler:(void (^_Nullable)(NSString *entry, unz_file_info zipInfo, long entryNumber, long total))progressHandler
completionHandler:(void (^_Nullable)(NSString *path, BOOL succeeded, NSError * _Nullable error))completionHandler;
// Zip 壓縮
/**
* @param path 目的路徑(格式:~/xxx.zip 結(jié)尾的路徑)
* @param filenames 要壓縮的文件路徑
*
* @return 返回 YES 表示成功锻拘,返回 NO 表示壓縮失敗油吭。
*/
+ (BOOL)createZipFileAtPath:(NSString *)path withFilesAtPaths:(NSArray *)filenames;
/**
* @param path 目的路徑(格式:~/xxx.zip 結(jié)尾的路徑)
* @param filenames 要壓縮的文件目錄路徑
*
* @return 返回 YES 表示成功,返回 NO 表示壓縮失敗署拟。
*/
+ (BOOL)createZipFileAtPath:(NSString *)path withContentsOfDirectory:(NSString *)directoryPath;
/**
* 初始化壓縮對象
*
* @param path 目的路徑(格式:~/xxx.zip 結(jié)尾的路徑)
*
* @return 初始化后的對像
*/
- (id)initWithPath:(NSString *)path;
/**
* 打開壓縮對象
* @return 返回 YES 表示成功婉宰,返回 NO 表示失敗。
*/
- (BOOL)open;
/**
* 添加要壓縮的文件的路徑
*
* @param path 文件路徑
*
* @return 返回 YES 表示成功推穷,返回 NO 表示失敗心包。
*/
- (BOOL)writeFile:(NSString *)path;
/**
* 向此路徑的文件里寫入數(shù)據(jù)
*
* @param data 要寫入的數(shù)據(jù)
* @param filename 文件路徑
*
* @return 返回 YES 表示成功,返回 NO 表示失敗馒铃。
*/
- (BOOL)writeData:(NSData *)data filename:(NSString *)filename;
/**
* 關(guān)閉壓縮對象
* @return 返回 YES 表示成功蟹腾,返回 NO 表示失敗痕惋。
*/
- (BOOL)close;
@optional
//將要解壓
- (void)zipArchiveWillUnzipArchiveAtPath:(NSString *)path zipInfo:(unz_global_info)zipInfo;
//解壓完成
- (void)zipArchiveDidUnzipArchiveAtPath:(NSString *)path zipInfo:(unz_global_info)zipInfo unzippedPath:(NSString *)unzippedPat uniqueId:(NSString *)uniqueId;
//將要解壓
- (void)zipArchiveWillUnzipFileAtIndex:(NSInteger)fileIndex totalFiles:(NSInteger)totalFiles archivePath:(NSString *)archivePath fileInfo:(unz_file_info)fileInfo;
//解壓完成
- (void)zipArchiveDidUnzipFileAtIndex:(NSInteger)fileIndex totalFiles:(NSInteger)totalFiles archivePath:(NSString *)archivePath fileInfo:(unz_file_info)fileInfo;
@end
當(dāng)對文件進(jìn)行解壓的時候,如果文件名包含了../,則可以實現(xiàn)目錄上一級跳轉(zhuǎn)娃殖,從而實現(xiàn)應(yīng)用內(nèi)任意目錄的跳轉(zhuǎn)值戳,進(jìn)一步可以實現(xiàn)文件覆蓋。
例如: 有一個文件名為wzg/../wwww.zip炉爆,在對該文件下載之后進(jìn)行解壓堕虹,如果正常情況下,為下圖的第一種情況芬首,解壓的test.txt文件會在C文件位置解壓出來赴捞,如果出現(xiàn)不正確的路徑,如下圖的第二種情況郁稍,解壓的test.txt文件會在C的上面一級文件夾路徑A中解壓出來赦政。
而在三方SSZipArchive中,底層實現(xiàn)如下:
#pragma mark - ================解壓===unzipFileAtPath=============================
+ (BOOL)unzipFileAtPath:(NSString *)path
toDestination:(NSString *)destination
preserveAttributes:(BOOL)preserveAttributes
overwrite:(BOOL)overwrite
nestedZipLevel:(NSInteger)nestedZipLevel
password:(nullable NSString *)password
error:(NSError **)error
delegate:(nullable id<SSZipArchiveDelegate>)delegate
progressHandler:(void (^_Nullable)(NSString *entry, unz_file_info zipInfo, long entryNumber, long total))progressHandler
completionHandler:(void (^_Nullable)(NSString *path, BOOL succeeded, NSError * _Nullable error))completionHandler
{
//==============核心代碼=================
unzGetCurrentFileInfo(zip, &fileInfo, filename, fileInfo.size_filename + 1, NULL, 0, NULL, 0);
filename[fileInfo.size_filename] = '\0';
BOOL fileIsSymbolicLink = _fileIsSymbolicLink(&fileInfo);
#pragma mark ------------------核查文件的路徑是否包含不規(guī)范字符---------------------------------
NSString * strPath = [SSZipArchive _filenameStringWithCString:filename size:fileInfo.size_filename];
if ([strPath hasPrefix:@"__MACOSX/"]) {
// ignoring resource forks: https://superuser.com/questions/104500/what-is-macosx-folder
unzCloseCurrentFile(zip);
ret = unzGoToNextFile(zip);
continue;
}
if (!strPath.length) {
// if filename data is unsalvageable, we default to currentFileNumber
strPath = @(currentFileNumber).stringValue;
}
// Check if it contains directory
BOOL isDirectory = NO;
if (filename[fileInfo.size_filename-1] == '/' || filename[fileInfo.size_filename-1] == '\\') {
isDirectory = YES;
}
free(filename);
// Contains a path
if ([strPath rangeOfCharacterFromSet:[NSCharacterSet characterSetWithCharactersInString:@"/\\"]].location != NSNotFound) {
strPath = [strPath stringByReplacingOccurrencesOfString:@"\\" withString:@"/"];
}
NSString *fullPath = [destination stringByAppendingPathComponent:strPath];
NSError *err = nil;
NSDictionary *directoryAttr;
if (preserveAttributes) {
NSDate *modDate = [[self class] _dateWithMSDOSFormat:(UInt32)fileInfo.dos_date];
directoryAttr = @{NSFileCreationDate: modDate, NSFileModificationDate: modDate};
[directoriesModificationDates addObject: @{@"path": fullPath, @"modDate": modDate}];
}
#pragma mark ----------------解壓路徑---------------------------------
//如果是一個沙盒路徑耀怜, 就創(chuàng)建這個路徑恢着,將解壓的 東西放到這個路徑下
//如果不是則創(chuàng)建一個路徑,
/*
stringByDeletingLastPathComponent一個新的字符串由來自接收者的組件刪除最后一個路徑,以及最終的路徑分隔符封寞。
Receiver’s String Value Resulting String
“/tmp/scratch.tiff” “/tmp”
“/tmp/lock/” “/tmp”
“/tmp/” “/”
“/tmp” “/”
“/” “/”
“scratch.tiff” “” (an empty string)
*/
NSLog(@"-------dir-------%@", fullPath);
if (isDirectory) {
[fileManager createDirectoryAtPath:fullPath withIntermediateDirectories:YES attributes:directoryAttr error:&err];
} else {
[fileManager createDirectoryAtPath:fullPath.stringByDeletingLastPathComponent withIntermediateDirectories:YES attributes:directoryAttr error:&err];
}
NSLog(@"-------dir--2222-----%@", fullPath.stringByDeletingLastPathComponent);
if (nil != err) {
if ([err.domain isEqualToString:NSCocoaErrorDomain] &&
err.code == 640) {
unzippingError = err;
unzCloseCurrentFile(zip);
success = NO;
break;
}
NSLog(@"[SSZipArchive] Error: %@", err.localizedDescription);
}
if ([fileManager fileExistsAtPath:fullPath] && !isDirectory && !overwrite) {
//FIXME: couldBe CRC Check?
unzCloseCurrentFile(zip);
ret = unzGoToNextFile(zip);
continue;
}
if (!fileIsSymbolicLink) {
// ensure we are not creating stale file entries
//確保我們沒有創(chuàng)建的文件條目
int readBytes = unzReadCurrentFile(zip, buffer, 4096);
if (readBytes >= 0) {
FILE *fp = fopen(fullPath.fileSystemRepresentation, "wb");
while (fp) {
if (readBytes > 0) {
if (0 == fwrite(buffer, readBytes, 1, fp)) {
if (ferror(fp)) {
NSString *message = [NSString stringWithFormat:@"Failed to write file (check your free space)"];
NSLog(@"[SSZipArchive] %@", message);
success = NO;
unzippingError = [NSError errorWithDomain:@"SSZipArchiveErrorDomain" code:SSZipArchiveErrorCodeFailedToWriteFile userInfo:@{NSLocalizedDescriptionKey: message}];
break;
}
}
} else {
break;
}
readBytes = unzReadCurrentFile(zip, buffer, 4096);
if (readBytes < 0) {
// Let's assume error Z_DATA_ERROR is caused by an invalid password
// Let's assume other errors are caused by Content Not Readable
success = NO;
}
}
if (fp) {
//關(guān)閉文件
fclose(fp);
if (nestedZipLevel
&& [fullPath.pathExtension.lowercaseString isEqualToString:@"zip"]
&& [self unzipFileAtPath:fullPath
toDestination:fullPath.stringByDeletingLastPathComponent
preserveAttributes:preserveAttributes
overwrite:overwrite
nestedZipLevel:nestedZipLevel - 1
password:password
error:nil
delegate:nil
progressHandler:nil
completionHandler:nil]) {
[directoriesModificationDates removeLastObject];
[[NSFileManager defaultManager] removeItemAtPath:fullPath error:nil];
} else if (preserveAttributes) {
// Set the original datetime property
if (fileInfo.dos_date != 0) {
NSDate *orgDate = [[self class] _dateWithMSDOSFormat:(UInt32)fileInfo.dos_date];
NSDictionary *attr = @{NSFileModificationDate: orgDate};
if (attr) {
if (![fileManager setAttributes:attr ofItemAtPath:fullPath error:nil]) {
// Can't set attributes
NSLog(@"[SSZipArchive] Failed to set attributes - whilst setting modification date");
}
}
}
// Set the original permissions on the file (+read/write to solve #293)
uLong permissions = fileInfo.external_fa >> 16 | 0b110000000;
if (permissions != 0) {
// Store it into a NSNumber
NSNumber *permissionsValue = @(permissions);
// Retrieve any existing attributes
NSMutableDictionary *attrs = [[NSMutableDictionary alloc] initWithDictionary:[fileManager attributesOfItemAtPath:fullPath error:nil]];
// Set the value in the attributes dict
attrs[NSFilePosixPermissions] = permissionsValue;
// Update attributes
if (![fileManager setAttributes:attrs ofItemAtPath:fullPath error:nil]) {
// Unable to set the permissions attribute
NSLog(@"[SSZipArchive] Failed to set attributes - whilst setting permissions");
}
}
}
}
else
{
// if we couldn't open file descriptor we can validate global errno to see the reason
if (errno == ENOSPC) {
NSError *enospcError = [NSError errorWithDomain:NSPOSIXErrorDomain
code:ENOSPC
userInfo:nil];
unzippingError = enospcError;
unzCloseCurrentFile(zip);
success = NO;
break;
}
}
} else {
// Let's assume error Z_DATA_ERROR is caused by an invalid password
// Let's assume other errors are caused by Content Not Readable
success = NO;
break;
}
}
else
{
// Assemble the path for the symbolic link
NSMutableString *destinationPath = [NSMutableString string];
int bytesRead = 0;
while ((bytesRead = unzReadCurrentFile(zip, buffer, 4096)) > 0)
{
buffer[bytesRead] = 0;
[destinationPath appendString:@((const char *)buffer)];
}
if (bytesRead < 0) {
// Let's assume error Z_DATA_ERROR is caused by an invalid password
// Let's assume other errors are caused by Content Not Readable
success = NO;
break;
}
分析解壓代碼,可以知道仅财,在進(jìn)行解壓的時候狈究,三方庫是有對解壓的文件夾名字進(jìn)行做路徑判斷和處理,
- 過濾了包含__MACOSX/盏求,如果有路徑有以__MACOSX/開頭抖锥,則直接關(guān)閉當(dāng)前文件路徑,
- 如果出現(xiàn)\則將\替換為/,
- 如果路徑不是一個正確的沙盒路徑碎罚,則使用fullPath.stringByDeletingLastPathComponent進(jìn)行處理磅废,參考官方文檔如下:
fullPath 處理后的fullPath
“/tmp/scratch.tiff” “/tmp”
“/tmp/lock/” “/tmp”
“/tmp/” “/”
“/tmp” “/”
“/” “/”
“scratch.tiff” “” (an empty string)
但是三方庫中并沒有在解壓的方法中處理出現(xiàn)../這種情況,
最完整的解決方案是對SSZipArchive庫進(jìn)行修補(bǔ)荆烈,在解壓函數(shù):
- (BOOL)unzipFileAtPath:(NSString *)path toDestination:(NSString *)destination preserveAttributes:(BOOL)preserveAttributes overwrite:(BOOL)overwrite nestedZipLevel:(NSInteger)nestedZipLevel password:(nullable NSString *)password error:(NSError **)error delegate:(nullable id<SSZipArchiveDelegate>)delegate progressHandler:(void (^_Nullable)(NSString *entry, unz_file_info zipInfo, long entryNumber, long total))progressHandler completionHandler:(void (^_Nullable)(NSString *path, BOOL succeeded, NSError * _Nullable error))completionHandler
中對最終解壓的strPath進(jìn)行檢測拯勉,如果出現(xiàn)可能造成目錄穿越的”../”字符串時進(jìn)行攔截。
-
-------- JSPath 熱更新 ---------------
JSPath 是一個ios動態(tài)更新框架憔购,它可以讓JS調(diào)用或者替換任意OC的方法宫峦,讓ios App具備熱更新的能力。因為OC是動態(tài)語言玫鸟,所以O(shè)C上所有方法的調(diào)用或者類的生成都是通過OC的Runtime 在運行時進(jìn)行的导绷,JS傳遞字符串給OC,OC通過Runtime接口調(diào)用和替換OC方法屎飘。
JSPath 方法調(diào)用是借助JavaScriptCore將類名字符串和方法名字符串傳遞給OC妥曲,由OC借助Runtime來反射出類和方法來調(diào)用贾费,例如創(chuàng)建一個UIView實例
js端 UIView.alloc() ---------> OC端 [UIView alloc]
image.png
js的方法調(diào)用規(guī)則是必須對已經(jīng)存在的對象調(diào)用已經(jīng)存在的方法,構(gòu)建對象檐盟,就是在調(diào)用方法前使用require函數(shù)為每一個類在js中構(gòu)建同名全局對象褂萧,源碼如下:
var _require = function(clsName) {
if (!global[clsName]) {
global[clsName] = {
__clsName: clsName
}
}
return global[clsName]
}
global.require = function(clsNames) {
var lastRequire
clsNames.split(',').forEach(function(clsName) {
lastRequire = _require(clsName.trim())
})
return lastRequire
}
js構(gòu)建函數(shù),為了避免內(nèi)存消耗遵堵,只定義了一個元函數(shù)箱玷,在元函數(shù)中將類名、方法名以及參數(shù)傳遞給OC陌宿,js腳本代碼在交由JavaScriptCore執(zhí)行前锡足,是先經(jīng)過轉(zhuǎn)換的,所有的方法調(diào)用都被轉(zhuǎn)換成了調(diào)用__c函數(shù)壳坪,js源碼的這樣轉(zhuǎn)換是通過正則匹配替換的舶得,核心代碼如下:
NSString *formatedScript = [NSString stringWithFormat:@";(function(){try{\n%@\n}catch(e){_OC_catch(e.message, e.stack)}})();",[_regex stringByReplacingMatchesInString:script options:0 range:NSMakeRange(0, script.length) withTemplate:_replaceStr]];
替換后的JS代碼 ps:所有的方法調(diào)用都被替換成了__c函數(shù)調(diào)用并將方法名作為參數(shù)傳入:
;(function(){try{
require('UIColor,UIImage');
defineClass('CustomCell', {
configWithModel: function(model) {
self.__c("headView")().__c("layer")().__c("setCornerRadius")(5.0);
self.__c("headView")().__c("layer")().__c("setBorderColor")(UIColor.__c("darkGrayColor")().__c("CGColor")());
self.__c("headView")().__c("layer")().__c("setBorderWidth")(1.0);
self.__c("headView")().__c("layer")().__c("setMasksToBounds")(YES);
self.__c("headView")().__c("setImage")(UIImage.__c("imageNamed")(model.__c("imgPath")()));
self.__c("contentLabel")().__c("setText")(model.__c("content")());
self.__c("contentLabel")().__c("setNumberOfLines")(0);
},
});
JS將消息傳遞給OC,內(nèi)部實現(xiàn)是根據(jù)實例方法或者類方法調(diào)用了_OC_callI和_OC_callC中的其中一個,而這兩個函數(shù)在初始化JPEnige的時候就已經(jīng)注冊到JS上下文了爽蝴,這是JavaScriptCore的接口沐批,在JS上下文中創(chuàng)建JS函數(shù)。當(dāng)函數(shù)被調(diào)用蝎亚,會將消息傳遞給OC端九孩,同時將參數(shù)傳遞給OC,OC執(zhí)行相應(yīng)的block发框,最后將返回值回傳JS躺彬,其實js傳遞消息給oc,是借助于JavaScriptCore梅惯。OC從JS端接收了消息宪拥,需要調(diào)用指定方法。JSPatch在處理的時候是通過NSInvocation來調(diào)用的铣减,這是因為:JS傳過來的參數(shù)類型需要轉(zhuǎn)換成OC相應(yīng)的類型她君,而NSInvocation很方便從方法簽名中獲取方法參數(shù)類型。同時葫哗,也能根據(jù)返回值類型取出返回值缔刹。
ps:iOS中可以直接調(diào)用 某個對象的消息 方式有2種,一種是performSelector:withObject:另一種是NSInvocation,NSInvocation也是一種消息調(diào)用的方法劣针,并且它的參數(shù)沒有限制桨螺,可以處理參數(shù)、返回值等相對復(fù)雜的操作酿秸。
JSPatch通過下發(fā)JS腳本文件對app進(jìn)行修復(fù)或更新灭翔,JS腳本的權(quán)限是很大的,如果在下發(fā)傳輸過程中文件被第三方截獲,可以修改了腳本內(nèi)容肝箱,故在使用時需對腳本文件進(jìn)行加密處理哄褒。對腳本文件加密主要有以下方案:
a、可以使用對稱加密煌张,服務(wù)器端和客戶端保存一把相同的私鑰呐赡,下發(fā)腳本文件前先對文件進(jìn)行加密,客戶端拿到腳本文件后用相同的私鑰解密骏融。這種方案弊端很明顯链嘀,密鑰保存在客戶端,一旦客戶端被破解档玻,密鑰就泄露了怀泊。
b、https傳輸误趴。不過需要購買證書霹琼,部署服務(wù)器,這種方案也比較安全可靠凉当。
c枣申、RSA簽名驗證。通過RSA非對稱加密看杭,此時需要服務(wù)器端忠藤,對要下發(fā)的腳本文件計算MD5值,用服務(wù)器私鑰對MD5只進(jìn)行加密楼雹,將腳本文件和加密后的MD5值下發(fā)給客戶端模孩,而客戶端,需要用服務(wù)器端公鑰解密加密過的MD5值烘豹,對接受的腳本文件計算MD5值瓜贾,將解密出來的MD5與新計算出來的MD5進(jìn)行比對校驗诺祸,如果校驗通過携悯,則表明腳本在傳輸過程中沒有被篡改。
-
-------- 梳理整個漏洞攻擊的流程如下 ---------------
在啟動app的時候筷笨,有時會執(zhí)行js的腳本憔鬼,即加載自己目錄下的/Library/Caches/A/B/patch.js并執(zhí)行,當(dāng)我們在使用app時胃夏,可能會有通過http下載一個zip包到本地轴或,并使用SSZipArchive庫進(jìn)行解壓,如果下載的zip文件含有../則可以實現(xiàn)應(yīng)用內(nèi)目錄上的跳轉(zhuǎn)仰禀,如果將應(yīng)用內(nèi)其他文件替換掉照雁,再次運行時,會造成程序奔潰答恶,如果剛好把js文件替換掉饺蚊,則再次運行app時萍诱,會執(zhí)行替換的js文件,執(zhí)行下載的js內(nèi)容污呼。
大致流程如下
image.png
如何來檢測ZipperDown漏洞裕坊?
通過指紋匹配可以獲取疑似受影響的應(yīng)用列表。但該漏洞形態(tài)靈活燕酷、變種類型多樣籍凝,指紋匹配的漏報率很高。所以我們建議通過人工分析的方式確認(rèn)漏洞是否存在苗缩。
ZipperDown漏洞如何觸發(fā)饵蒂?
ZipperDown漏洞攻擊場景與受影響應(yīng)用業(yè)務(wù)場景相關(guān)。常見攻擊場景包括:在不安全網(wǎng)絡(luò)環(huán)境下使用受影響應(yīng)用挤渐、在攻擊者誘導(dǎo)下使用某些應(yīng)用功能等苹享。
對漏洞進(jìn)行安全防范
針對 iOS 應(yīng)用的 ZipperDown 漏洞,對IOS 應(yīng)用可以進(jìn)行一下的幾點防守策略浴麻。
數(shù)據(jù)庫文件安全
開發(fā)中會使用SQLite數(shù)據(jù)庫來存儲應(yīng)用數(shù)據(jù)得问,而數(shù)據(jù)庫本身一般存儲在沙盒文件中,如果數(shù)據(jù)庫里面存儲的數(shù)據(jù)沒有進(jìn)行復(fù)雜的加密處理软免,會是應(yīng)用程序有敏感信息泄漏的風(fēng)險宫纬,同時也有助于攻擊者進(jìn)行逆向分析。
安全實施方案: 使用較復(fù)雜的加密加鹽算法對敏感數(shù)據(jù)加密后存儲膏萧。NSUserDefaults 安全
保存在 NSUserDefaults 中的信息在應(yīng)用關(guān)閉后再次打開依然存在漓骚。且保存到 NSUserDefautls 中的數(shù)據(jù)是沒有加密的,可以很輕易地從沙盒中找到榛泛。NSUserDefautls 被存儲在一個以應(yīng)用 Bundle ID 為名稱的 Plist 文件中蝌蹂。
安全實施方案:重要的敏感數(shù)據(jù)存儲在 Keychain 中。Keychain 安全
Keychain是一個安全的存儲容器曹锨,它是一個SQlite數(shù)據(jù)庫孤个,位于 /private/var/Keychains/keychain-2.db,其保存的所有數(shù)據(jù)都是加密過的沛简。Keychain 在沙盒之外 App 會將部分重要數(shù)據(jù)存放在 Keychain 中使用進(jìn)行讀取齐鲤,但如果寫入后未清除就卸載App,則可能會導(dǎo)致下次安全的時候直接從KeyChain中讀取密碼登錄或手勢密碼無法解除等問題椒楣。
安全實施方案:首次安裝應(yīng)用程序啟動后给郊,進(jìn)行刪除keychain數(shù)據(jù)操作。HTTPS 安全
在使用HTTPS時捧灰, 也有可能因為沒有校驗服務(wù)器證書的原因?qū)е卤唤俪窒牛绻换フ埱髷?shù)據(jù)處理不當(dāng),攻擊者可以解密得到明文通信數(shù)據(jù);甚至進(jìn)一步偽造 App 的請求炭庙,這是極大的安全隱患跪另。
安全措施:
a. App內(nèi)對HTTPS 進(jìn)行證書做校驗
b.避免使用有漏洞的三方庫
c. 重要的數(shù)據(jù),單獨加密WebView安全
WebView 跨平臺煤搜、動態(tài)等特性被廣泛使用免绿,同時在ios終端上,WebView可以注冊一些敏感信息數(shù)據(jù)擦盾,比如發(fā)短信嘲驾,付款,定位信息等迹卢,也會有安全風(fēng)險辽故。
安全實施方法
a.對輸入進(jìn)行過濾和編碼
b.服務(wù)端對App發(fā)送的數(shù)據(jù)進(jìn)行過濾和編碼
c. 盡量減少敏感接口的注冊,盡量不要加載第三方內(nèi)容,如果需要加載腐碱,則必須在WebView 的Delegate方法中誊垢,通過白名單機(jī)制實現(xiàn)對調(diào)用者的檢驗加密算法
1.對稱加密算法要使用AES,DES 或3DES症见,避免使用RC4 等目前可能被爆破的算法
2.對于數(shù)據(jù)安全性要求較高的地方喂走,使用非對稱加密算法如RSA等
3.對稱加密算法的KEY和IV,應(yīng)避免硬編碼谋作,如果加密數(shù)據(jù)僅是本地存儲芋肠,可基于設(shè)備相關(guān)的信息來生成KEY 和 IV