ios ZipperDown 漏洞

看了網(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中解壓出來赦政。

image.png

而在三方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

參考1
參考2
參考3
參考4

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市遵蚜,隨后出現(xiàn)的幾起案子帖池,更是在濱河造成了極大的恐慌,老刑警劉巖吭净,帶你破解...
    沈念sama閱讀 218,640評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件睡汹,死亡現(xiàn)場離奇詭異,居然都是意外死亡寂殉,警方通過查閱死者的電腦和手機(jī)囚巴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,254評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來不撑,“玉大人文兢,你說我怎么就攤上這事晤斩』烂剩” “怎么了?”我有些...
    開封第一講書人閱讀 165,011評論 0 355
  • 文/不壞的土叔 我叫張陵澳泵,是天一觀的道長实愚。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么腊敲? 我笑而不...
    開封第一講書人閱讀 58,755評論 1 294
  • 正文 為了忘掉前任击喂,我火速辦了婚禮,結(jié)果婚禮上碰辅,老公的妹妹穿的比我還像新娘懂昂。我一直安慰自己,他們只是感情好没宾,可當(dāng)我...
    茶點故事閱讀 67,774評論 6 392
  • 文/花漫 我一把揭開白布凌彬。 她就那樣靜靜地躺著,像睡著了一般循衰。 火紅的嫁衣襯著肌膚如雪铲敛。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,610評論 1 305
  • 那天会钝,我揣著相機(jī)與錄音伐蒋,去河邊找鬼。 笑死迁酸,一個胖子當(dāng)著我的面吹牛先鱼,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播奸鬓,決...
    沈念sama閱讀 40,352評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼型型,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了全蝶?” 一聲冷哼從身側(cè)響起闹蒜,我...
    開封第一講書人閱讀 39,257評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎抑淫,沒想到半個月后绷落,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,717評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡始苇,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,894評論 3 336
  • 正文 我和宋清朗相戀三年砌烁,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片催式。...
    茶點故事閱讀 40,021評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡函喉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出荣月,到底是詐尸還是另有隱情管呵,我是刑警寧澤,帶...
    沈念sama閱讀 35,735評論 5 346
  • 正文 年R本政府宣布哺窄,位于F島的核電站捐下,受9級特大地震影響账锹,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜坷襟,卻給世界環(huán)境...
    茶點故事閱讀 41,354評論 3 330
  • 文/蒙蒙 一奸柬、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧婴程,春花似錦廓奕、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,936評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蹲蒲,卻和暖如春番甩,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背届搁。 一陣腳步聲響...
    開封第一講書人閱讀 33,054評論 1 270
  • 我被黑心中介騙來泰國打工缘薛, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人卡睦。 一個月前我還...
    沈念sama閱讀 48,224評論 3 371
  • 正文 我出身青樓宴胧,卻偏偏與公主長得像,于是被迫代替她去往敵國和親表锻。 傳聞我的和親對象是個殘疾皇子恕齐,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,974評論 2 355

推薦閱讀更多精彩內(nèi)容

  • iOS安全攻與防 本地數(shù)據(jù)攻與防 https UIWebview 第三方sdk與xcode 反編譯與代碼混淆 越獄...
    天機(jī)否閱讀 10,632評論 8 66
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)瞬逊,斷路器显歧,智...
    卡卡羅2017閱讀 134,657評論 18 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,158評論 25 707
  • CloudKit Subscription push使用
    工匠良辰閱讀 100評論 0 0
  • 凌初買了些燒烤,在網(wǎng)吧狹小的走道中小心翼翼的穿行确镊。大力摔打鍵盤的聲音此起彼伏士骤,好像如此便能發(fā)泄心中的苦悶似的。...
    玥落無心閱讀 210評論 0 0