美文网首页
ios ZipperDown 漏洞

ios ZipperDown 漏洞

作者: 天下林子 | 来源:发表于2018-05-17 15:05 被阅读311次

    看了网易的新闻,得知很多app有ZipperDown安全隐患,故了解了一下。仅供参考,望指正。

    漏洞的攻击原理。

    • 漏洞与zip文件有关,ios没有提供官方的unzip API函数,基本上现有的ios App都是使用的SSZipArchive或ziparchive这两个第三方库来实现解压的功能。使用第三方zip库在解压zip文件过程中没有考虑文件名中带有”../../”这样的情况,从而产生了目录穿越漏洞。因此,如果一个iOS 应用下载了恶意的zip文件,并且使用ziparchive库解压,利用漏洞可以做到app container目录下的任意文件覆盖,如果覆盖了应用重要的文件会造成应用崩溃(DOS),如果覆盖了app的hotpatch文件则会造成代码执行。
    • 攻击条件:
      1.APP用了ZipArchive
      2.原APP下发的某个zip包传输过程没加密,zip包也没加密
      3.原APP使用了JSPatch或其他执行引擎,且本地脚本没有加密,只要把脚本放指定目录即可执行
      4.用户连上第三方wifi遭受攻击。

    漏洞原理

    ZipperDown漏洞并非iOS平台自身问题,而是与Zip文件解压有关。iOS平台没有提供官方的unzipAPI函数,而是引用了第三方库来实现解压功能,由于现有的iOS App基本上采用SSZipArchive或Ziparchive来实现解压,因此漏洞是来自使用第三方Zip库解压Zip文件的过程中没有对Zip内文件名做校验导致的。如果文件名中含有“../”则可以实现目录的上一级跳转,从而实现应用内任意目录的跳转,进一步可以实现文件覆盖,如果把App的hotpatch文件覆盖替换了,可以达到执行黑客指定指令,从而按照黑客的意图实现任意应用内攻击。

    这个漏洞不禁让易盾联想到不久前Android平台上的unZip解压文件漏洞,和这个漏洞几乎是完全一样,只是平台和第三方解压库不同而已。Android平台上的被称为unZip解压文件漏洞,网易云易盾安全检测平台已经可以实现扫描检测。

    • ------- SSZipArchive ---------------

    压缩文件是允许路径指向类似../A/../B这种格式的, UNIX下../代表这个文件夹的上一层。比如说/A/B/../C实际上指的是/A/C
    有问题的解压库没有对这种../做过滤,也就是说可以往解压路径外的地方解压文件。这不是一个系统级的沙盒逃逸漏洞

    很多App会把所谓的热更新补丁放在沙盒内的某个路径下,比如说我们叫Documents/A.js吧,如果热更新的传输过程有中间人攻击的问题或者app被通过某种方式打开恶意的压缩包, 攻击者就可以覆盖A.js的内容,这样下次app启动加载热更新补丁A.js时就会执行恶意代码。

    SSZipArchive
    我们在开发app的时候,有时会需要对文件进行压缩和解压的操作,比如百度网盘,这个时候我们就必须要用到一个第三方的开源库,SSZipArchive ,来对目标文件进行压缩和解压的操作。

    // Unzip 解压  
          
    /** 
     * @param          path    源文件 
     * @param   destination    目的文件 
     * @param      uniqueId    标记,用于区别多个解压操作 
     * 
     * @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    标记,用于区别多个解压操作 
     * 
     * @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    设置代理 
     * @param      uniqueId    标记,用于区别多个解压操作 
     * 
     * @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    设置代理 
     * @param      uniqueId    标记,用于区别多个解压操作 
     * 
     * @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 结尾的路径) 
     * @param  filenames    要压缩的文件路径 
     * 
     * @return 返回 YES 表示成功,返回 NO 表示压缩失败。 
     */  
    + (BOOL)createZipFileAtPath:(NSString *)path withFilesAtPaths:(NSArray *)filenames;  
      
    /** 
     * @param       path    目的路径(格式:~/xxx.zip 结尾的路径) 
     * @param  filenames    要压缩的文件目录路径 
     * 
     * @return 返回 YES 表示成功,返回 NO 表示压缩失败。 
     */  
    + (BOOL)createZipFileAtPath:(NSString *)path withContentsOfDirectory:(NSString *)directoryPath;  
      
    /** 
     * 初始化压缩对象 
     * 
     * @param  path    目的路径(格式:~/xxx.zip 结尾的路径) 
     * 
     * @return 初始化后的对像 
     */  
    - (id)initWithPath:(NSString *)path;  
      
    /** 
     *  打开压缩对象 
     * @return 返回 YES 表示成功,返回 NO 表示失败。 
     */  
    - (BOOL)open;  
      
    /** 
     * 添加要压缩的文件的路径 
     * 
     * @param  path    文件路径 
     * 
     * @return 返回 YES 表示成功,返回 NO 表示失败。 
     */  
    - (BOOL)writeFile:(NSString *)path;  
      
    /** 
     * 向此路径的文件里写入数据 
     * 
     * @param      data    要写入的数据 
     * @param  filename    文件路径 
     * 
     * @return 返回 YES 表示成功,返回 NO 表示失败。 
     */  
    - (BOOL)writeData:(NSData *)data filename:(NSString *)filename;  
      
    /** 
     *  关闭压缩对象 
     * @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 
    

    当对文件进行解压的时候,如果文件名包含了../,则可以实现目录上一级跳转,从而实现应用内任意目录的跳转,进一步可以实现文件覆盖。
    例如: 有一个文件名为wzg/../wwww.zip,在对该文件下载之后进行解压,如果正常情况下,为下图的第一种情况,解压的test.txt文件会在C文件位置解压出来,如果出现不正确的路径,如下图的第二种情况,解压的test.txt文件会在C的上面一级文件夹路径A中解压出来。

    image.png

    而在三方SSZipArchive中,底层实现如下:

    #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 ------------------核查文件的路径是否包含不规范字符---------------------------------
                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 ----------------解压路径---------------------------------
                //如果是一个沙盒路径, 就创建这个路径,将解压的 东西放到这个路径下
                //如果不是则创建一个路径,
                /*
                 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
                    //确保我们没有创建的文件条目
                    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) {
                            //关闭文件
                            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;
                    }
    
    

    分析解压代码,可以知道,在进行解压的时候,三方库是有对解压的文件夹名字进行做路径判断和处理,

    • 过滤了包含__MACOSX/,如果有路径有以__MACOSX/开头,则直接关闭当前文件路径,
    • 如果出现\则将\替换为/,
    • 如果路径不是一个正确的沙盒路径,则使用fullPath.stringByDeletingLastPathComponent进行处理,参考官方文档如下:
                    fullPath                 处理后的fullPath 
                 “/tmp/scratch.tiff”           “/tmp”
                 “/tmp/lock/”                   “/tmp”
                 “/tmp/”                        “/”
                 “/tmp”                         “/”
                 “/”                            “/”
                 “scratch.tiff”                 “” (an empty string)
    

    但是三方库中并没有在解压的方法中处理出现../这种情况,
    最完整的解决方案是对SSZipArchive库进行修补,在解压函数:

    • (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进行检测,如果出现可能造成目录穿越的”../”字符串时进行拦截。
    • -------- JSPath 热更新 ---------------
      JSPath 是一个ios动态更新框架,它可以让JS调用或者替换任意OC的方法,让ios App具备热更新的能力。因为OC是动态语言,所以OC上所有方法的调用或者类的生成都是通过OC的Runtime 在运行时进行的,JS传递字符串给OC,OC通过Runtime接口调用和替换OC方法。
      JSPath 方法调用是借助JavaScriptCore将类名字符串和方法名字符串传递给OC,由OC借助Runtime来反射出类和方法来调用,例如创建一个UIView实例
      js端 UIView.alloc() ---------> OC端 [UIView alloc]


      image.png

    js的方法调用规则是必须对已经存在的对象调用已经存在的方法,构建对象,就是在调用方法前使用require函数为每一个类在js中构建同名全局对象,源码如下:

    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构建函数,为了避免内存消耗,只定义了一个元函数,在元函数中将类名、方法名以及参数传递给OC,js脚本代码在交由JavaScriptCore执行前,是先经过转换的,所有的方法调用都被转换成了调用__c函数,js源码的这样转换是通过正则匹配替换的,核心代码如下:

    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:所有的方法调用都被替换成了__c函数调用并将方法名作为参数传入:

    ;(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,内部实现是根据实例方法或者类方法调用了_OC_callI和_OC_callC中的其中一个,而这两个函数在初始化JPEnige的时候就已经注册到JS上下文了,这是JavaScriptCore的接口,在JS上下文中创建JS函数。当函数被调用,会将消息传递给OC端,同时将参数传递给OC,OC执行相应的block,最后将返回值回传JS,其实js传递消息给oc,是借助于JavaScriptCore。OC从JS端接收了消息,需要调用指定方法。JSPatch在处理的时候是通过NSInvocation来调用的,这是因为:JS传过来的参数类型需要转换成OC相应的类型,而NSInvocation很方便从方法签名中获取方法参数类型。同时,也能根据返回值类型取出返回值。
    

    ps:iOS中可以直接调用 某个对象的消息 方式有2种,一种是performSelector:withObject:另一种是NSInvocation,NSInvocation也是一种消息调用的方法,并且它的参数没有限制,可以处理参数、返回值等相对复杂的操作。
    JSPatch通过下发JS脚本文件对app进行修复或更新,JS脚本的权限是很大的,如果在下发传输过程中文件被第三方截获,可以修改了脚本内容,故在使用时需对脚本文件进行加密处理。对脚本文件加密主要有以下方案:
    a、可以使用对称加密,服务器端和客户端保存一把相同的私钥,下发脚本文件前先对文件进行加密,客户端拿到脚本文件后用相同的私钥解密。这种方案弊端很明显,密钥保存在客户端,一旦客户端被破解,密钥就泄露了。
    b、https传输。不过需要购买证书,部署服务器,这种方案也比较安全可靠。
    c、RSA签名验证。通过RSA非对称加密,此时需要服务器端,对要下发的脚本文件计算MD5值,用服务器私钥对MD5只进行加密,将脚本文件和加密后的MD5值下发给客户端,而客户端,需要用服务器端公钥解密加密过的MD5值,对接受的脚本文件计算MD5值,将解密出来的MD5与新计算出来的MD5进行比对校验,如果校验通过,则表明脚本在传输过程中没有被篡改。

    • -------- 梳理整个漏洞攻击的流程如下 ---------------
      在启动app的时候,有时会执行js的脚本,即加载自己目录下的/Library/Caches/A/B/patch.js并执行,当我们在使用app时,可能会有通过http下载一个zip包到本地,并使用SSZipArchive库进行解压,如果下载的zip文件含有../则可以实现应用内目录上的跳转,如果将应用内其他文件替换掉,再次运行时,会造成程序奔溃,如果刚好把js文件替换掉,则再次运行app时,会执行替换的js文件,执行下载的js内容。
      大致流程如下


      image.png

    如何来检测ZipperDown漏洞?
    通过指纹匹配可以获取疑似受影响的应用列表。但该漏洞形态灵活、变种类型多样,指纹匹配的漏报率很高。所以我们建议通过人工分析的方式确认漏洞是否存在。

    ZipperDown漏洞如何触发?
    ZipperDown漏洞攻击场景与受影响应用业务场景相关。常见攻击场景包括:在不安全网络环境下使用受影响应用、在攻击者诱导下使用某些应用功能等。

    对漏洞进行安全防范

    针对 iOS 应用的 ZipperDown 漏洞,对IOS 应用可以进行一下的几点防守策略。

    • 数据库文件安全
      开发中会使用SQLite数据库来存储应用数据,而数据库本身一般存储在沙盒文件中,如果数据库里面存储的数据没有进行复杂的加密处理,会是应用程序有敏感信息泄漏的风险,同时也有助于攻击者进行逆向分析。
      安全实施方案: 使用较复杂的加密加盐算法对敏感数据加密后存储。

    • NSUserDefaults 安全
      保存在 NSUserDefaults 中的信息在应用关闭后再次打开依然存在。且保存到 NSUserDefautls 中的数据是没有加密的,可以很轻易地从沙盒中找到。NSUserDefautls 被存储在一个以应用 Bundle ID 为名称的 Plist 文件中。
      安全实施方案:重要的敏感数据存储在 Keychain 中。

    • Keychain 安全
      Keychain是一个安全的存储容器,它是一个SQlite数据库,位于 /private/var/Keychains/keychain-2.db,其保存的所有数据都是加密过的。Keychain 在沙盒之外 App 会将部分重要数据存放在 Keychain 中使用进行读取,但如果写入后未清除就卸载App,则可能会导致下次安全的时候直接从KeyChain中读取密码登录或手势密码无法解除等问题。
      安全实施方案:首次安装应用程序启动后,进行删除keychain数据操作。

    • HTTPS 安全
      在使用HTTPS时, 也有可能因为没有校验服务器证书的原因导致被劫持,如果交互请求数据处理不当,攻击者可以解密得到明文通信数据;甚至进一步伪造 App 的请求,这是极大的安全隐患。
      安全措施:
      a. App内对HTTPS 进行证书做校验
      b.避免使用有漏洞的三方库
      c. 重要的数据,单独加密

    • WebView安全
      WebView 跨平台、动态等特性被广泛使用,同时在ios终端上,WebView可以注册一些敏感信息数据,比如发短信,付款,定位信息等,也会有安全风险。
      安全实施方法
      a.对输入进行过滤和编码
      b.服务端对App发送的数据进行过滤和编码
      c. 尽量减少敏感接口的注册,尽量不要加载第三方内容,如果需要加载,则必须在WebView 的Delegate方法中,通过白名单机制实现对调用者的检验

    • 加密算法
      1.对称加密算法要使用AES,DES 或3DES,避免使用RC4 等目前可能被爆破的算法
      2.对于数据安全性要求较高的地方,使用非对称加密算法如RSA等
      3.对称加密算法的KEY和IV,应避免硬编码,如果加密数据仅是本地存储,可基于设备相关的信息来生成KEY 和 IV

    参考1
    参考2
    参考3
    参考4

    相关文章

      网友评论

          本文标题:ios ZipperDown 漏洞

          本文链接:https://www.haomeiwen.com/subject/acvbdftx.html