Security

安全保护

Posted by kunnan on August 16, 2018

前言

I 、数据加密:静态字符串、本地存储及网络传输加密

  • 对用户的敏感信息进行加密保存

    • 不管是本地的数据还是网络中传输的数据,对敏感数据需要采用加密算法进行加密处理,而常用的加密算法有 AES,RSA、BASE64、MD5、RSA。

1.1 本地存储加密

  • 本地存储的方式

    • NSUserDefault: 一般用于存储小规模数据、业务逻辑弱的数据。

    • keychain: 苹果提供的可逆存储,因为有着只要app不重装系统、可以同步iCloud的特性,一般用来对用户的标识符或者一些需要加密的小数据进行存储。

    • 归档:主要原理是对数据进行序列化、反序列化操作后,写入、读出数据。方便便捷易使用,缺点查询、更改数据耗时耗性能。

    • 数据库:主要的有三种sqlite3、core data、realm

      • 整库加密: 可使用第三方的SQLite扩展库,对数据库进行整体的加密。如:SQLCipher

        • 优点:对数据库整体操作,减少资源消耗。
        • 缺点:需要使用第三库。
  • 剪切板的缓存

    • 我们在使用剪切板进行操作时,iOS系统会缓存所操作的数据,如果数据涉及隐私,将会带来安全问题。如下图,iOS的三种剪切板和功能,其中系统级别的剪切板优先级最高。
  • 系统键盘缓存

    • 我们打字在使用系统键盘打字时时,会出现提示,键盘一般会缓存这些提示字符串,缓存在手机的“/private/var/mobile/Library/Keyboard/xx-dynamic-text.dat”文件中,攻击者可以提取出来,出现频率最高的往往是账户名和密码。

1.2 网络传输加密

对于网络传输的数据可以使用 AES+RSA 的方式对数据进行加密,如果使用 Https 的方式需要对证书进行校验

1.2.0 在传输过程使用RSA+AES 进行加密传输
  • 客户端随机生成AES的key(可以采用md5进行生成key)

  • 使用RSA 公钥加密算法来 加密步骤一的aeskey

  • 使用步骤一的aeskey 对数据进行aes加密

  • 将RSA加密之后的key 和AES加密之后的数据一起发送给服务端,

    • 同时报文中可附加时间戳字段,来防止重复请求攻击

    image

1.2.1 https 证书验证
  • 验证方法

    • AFNetworking的验证策略

    • 在NSURLConnection 的(void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge; 中处理

    • 在NSURLSession 的`- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge

      ​ completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler;` 获取证书信息进行验证

本文重点介绍AFNetworking的验证策略。

  • AFNetworking 请求HTTPS时 SSL的身份验证设置

    • 0) 获取到站点的证书:

      •   我们可以使用以下openssl命令来获取到服务器的公开二进制证书(以google为例):

        "openssl s_client -connect www.google.com:443 </dev/null 2>/dev/null | openssl x509 -outform DER > https.cer"
              
        
    • 1)web端服务器必须要提供一个叫SSL的证书,证书里面包含有一个叫.crt的文件,你需要将它转换成.cer的格式,转换方法如下: openssl x509 -in your certificate.crt -out your certificate.cer -outform der

      • 转换成功后需要找到你转换成.cer的文件.添加到你的xcode工程当中去;然后读取二进制数据对其赋值

      • 项目中的文件,攻击者很容易通过解包取出,所以我们一般,在代码中存放证书的二进制流数据

    • 2)AFN2.x 需要添加AFSecuriPolicy和setAFHTTPRequestOperationManager

      • 设置安全策略setSecurityPolicy

          AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManagermanager];
            [manager setSecurityPolicy:[selfSecurityPolicy]];
           manager.responseSerializer = [AFHTTPResponseSerializerserializer];
            [manager GET:@"your Url" parameters:nilsuccess:^(AFHTTPRequestOperation *operation,id responseObject) {
                NSLog(@"成功了");
            } failure:^(AFHTTPRequestOperation *operation,NSError *error) {
                NSLog(@"Error: %@", error);
            }];
                   
        }
              
        
      • #pragma mark - ******** ssl证书检测设置添加安全策略

        + (AFSecurityPolicy*)setupSecurityPolicy
               
        {
               
            NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"cmpay"ofType:@"cer"];
               
            NSData *certData = [NSData dataWithContentsOfFile:cerPath];
               
        //    AFSecurityPolicy *securityPolicy = [[AFSecurityPolicy alloc] init];
               
            AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModePublicKey];
               
            [securityPolicy setPinnedCertificates:@[certData]];
               
            securityPolicy.allowInvalidCertificates =NO;//定义了客户端是否信任非法证书
               
            securityPolicy.validatesDomainName =YES;//是指是否校验在证书中的domain这一个字段
               
            securityPolicy.validatesCertificateChain =NO;//指的是是否校验其证书链。
               
            return securityPolicy;
               
        }
               
               
               
        //ps:正确的设置
        securityPolicy.allowInvalidCertificates =NO;
        securityPolicy.validatesDomainName =YES;
        securityPolicy.validatesCertificateChain =NO;
               
        1)allowInvalidCertificates 是否允许无效证书(也就是自建的证书),默认为NO;如果是需要验证自建证书,需要设置为YES
        2)validatesDomainName 是否需要验证域名,默认为YES;假如证书的域名与你请求的域名不一致,需把该项设置为NO;
        如设成NO的话,即服务器使用其他可信任机构颁发的证书,也可以建立连接,这个非常危险,建议打开。
        设置为NO,主要用于这种情况:客户端请求的是子域名,而证书上的是另外一个域名。因为SSL证书上的域名是独立
        3)validatesCertificateChain 是否验证整个证书链,默认为YES, 设置为YES,
        
1.2.2 ssl证书安全策略的的属性分析
  • validatesCertificateChain: 指的是是否校验其证书链。

    • 通常来讲,一个CA证书颁发机构有很多个子机构,用来签发不同用途的子证书,然后这些子证书又再用来签发相应的证书。只有证书链上的证书都正确,CertificateChain才算验证完成。

      • 以Google为例:

        image

      • 从上图可以看到,Google.com的证书的根CA证书是GeoTrust Global CA; 而CA并没有直接给google.com签证书,而是先签名了Google Internet Authority G2, 然后G2再签名了google.com。这时候就需要设备中保存有Google Internet Authority G2证书才能通过校验。

      •   一般来讲,我推荐将validatesCertificateChain设置为NO,因为并不是太有必要做CertificateChain的校验。并且,在AFNetworking 2.6.0中,也正式将validatesCertificateChain拿掉了(https://github.com/AFNetworking/AFNetworking/blob/master/CHANGELOG.md), 其原因也同样为:There was no documented security advantage to pinning against an entire certificate chain。

      •   因此,在2.6.0之后,可以不管这个字段。而在此之前,从效率上来说,设定为NO会是个比较明智的选择。

  • validatesDomainName:  是指是否校验在证书中的domain这一个字段。每个证书都会包含一个DomainName, 它可以是一个IP地址,一个域名或者一端带有通配符的域名。

    • 如*.google.comwww.google.com 都可以成为这个证书的DomainName。设置validatesDomainName=YES将严格地保证其安全性
  • pinnedCertificates:   就是用来校验服务器返回证书的证书。通常都保存在mainBundle 下。

    • 通常默认情况下,AFNetworking会自动寻找在mainBundle的根目录下所有的.cer文件并保存在pinnedCertificates数组里,以校验服务器返回的证书。
  • allowInvalidCertificates: allowInvalidCertificates 定义了客户端是否信任非法证书。一般来说,每个版本的iOS设备中,都会包含一些既有的CA根证书。如果接收到的证书是iOS信任的CA根证书签名的,那么则为合法证书;否则则为“非法”证书。allowInvalidCertificates 就是用来确认是否信任这样的证书的。当然,我们也可以给iOS加入新的信任的CA证书。iOS已有的CA根证书,可以在这里了解到:https://support.apple.com/en-us/HT204132

  • AFSSLPinningMode :如何去校验服务器端给予的证书

    typedef NS_ENUM(NSUInteger, AFSSLPinningMode) {
        AFSSLPinningModeNone,
        AFSSLPinningModePublicKey,
        AFSSLPinningModeCertificate,
    };
    
    • AFSSLPinningModeNone: 代表客户端无条件地信任服务器端返回的证书。

    • AFSSLPinningModePublicKey: 代表客户端会将服务器端返回的证书与本地保存的证书中,PublicKey的部分进行校验;如果正确,才继续进行。

    • AFSSLPinningModeCertificate: 代表客户端会将服务器端返回的证书和本地保存的证书中的所有内容,包括PublicKey和证书部分,全部进行校验;如果正确,才继续进行。

  • SSLPinningMode 的更详细解释

    • AFSSLPinningModeCertificate

      • 客户端需要一份证书文件的拷贝
      • 第一步验证、先验证证书的域名/有效期等信息
      • 第二步验证、对比服务端返回的证书跟客户端存储的证书是否一致
    • AFSSLPinningModePublicKey

      • 客户端需要一份证书文件的拷贝
      • 验证时只验证证书里的公钥,不验证证书的有效期等信息
      • 即使伪造证书的公钥,也不能解密传输的数据,必须要私钥
    • AFSSLPinningModeNone

      • 这个模式不做本地证书验证(不做 SSL Pinning 操作)
      • 直接从客户端系统中的受信任颁发机构 CA 列表中去验证
1.2.3 常见错误& 解决方案
  • 问题现象
    • 使用AFSSLPinningModeCertificate 方式验证的时候,获取 DER 表示的数据时
    • 发生如下错误: Create a certificate  Return NULL 。
    • 根据“@result Return NULL if the passed-in data is not a valid DER-encoded ”,从而推测cer证书 is not a valid DER-encoded。
  • 问题分析--**to uncaught exception 'NSInvalidArgumentException', reason: '\*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil'**

    • 问题定位–获取DER信息函数

      /*!
           
          @function SecCertificateCreateWithData
           
          @abstract Create a certificate given it's DER representation as a CFData.
           
          @param allocator CFAllocator to allocate the certificate with.
           
          @param certificate DER encoded X.509 certificate.
           
          @result Return NULL if the passed-in data is not a valid DER-encoded
           
          X.509 certificate, return a SecCertificateRef otherwise.
           
      */
           
      __nullable
           
      SecCertificateRef SecCertificateCreateWithData(CFAllocatorRef __nullable allocator,
           
          CFDataRef data) __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_2_0);
      
    • 报错信息

      Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil'
    
    • 报错代码

      [pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)];
      
  • 解决方案

    •  错误信息是编码不对,证书有BASE64和DER两种编码,邮件中提供的begin-end的证书是常用的BASE64编码,der二进制编码可以使用openssl进行转换。

      • 转变格式为DER

        openssl x509 -outform der -in kncmpay.cer -out kncmpay.cer
              
        
1.2.4 绕过证书校验
1.2.5 BufferXorEncrypt

在传输buffer的时,对每个传输的字符串进行自定义的加密传输。

  • usage: 传输时调用的方法

        //自定义格式
        AMBuffer* buffer = [AMBuffer defaultForAPI];
        [buffer setNSString:@"username"];
        [buffer setNSString:username];
        [buffer setNSString:@"password"];
        [buffer setNSString:password];
        [buffer setLengthWithFirst];
          
        NSData* data = [NSData dataWithBytes:[buffer getBody] length:[buffer getLength]];
          
        request.HTTPBody = data;
      
    
1.2.6 protobuf/objectivec/

1.4 code

See Also

  • iosre_anti

  • iOS安全攻与防,详细的列出了,在iOS开发中,项目会存在的安全漏洞以及解决办法。

    • 我们使用AFNetworking网络框架来配置证书验证,而我们需要配置这个框架下的类AFSecurityPolicy相关属性即可。在此我们使用AFSSLPinningModeCertificate对证书进行完整校验。
  • #pragma mark - ******** 判断设备是否越狱

    #import <sys/stat.h>
       
    #import <dlfcn.h>
       
    - (BOOL)mgjpf_isJailbroken
       
    {
       
        //以下检测的过程是越往下,越狱越高级
       
           
       
        //    /Applications/Cydia.app, /privte/var/stash
       
        BOOL jailbroken = NO;
       
        NSString *cydiaPath = @"/Applications/Cydia.app";
       
        NSString *aptPath = @"/private/var/lib/apt/";
       
        if ([[NSFileManager defaultManager] fileExistsAtPath:cydiaPath]) {
       
            jailbroken = YES;
       
        }
       
        if ([[NSFileManager defaultManager] fileExistsAtPath:aptPath]) {
       
            jailbroken = YES;
       
        }
       
           
       
        //可能存在hook了NSFileManager方法,此处用底层C stat去检测
       
        struct stat stat_info;
       
        if (0 == stat("/Library/MobileSubstrate/MobileSubstrate.dylib", &stat_info)) {
       
            jailbroken = YES;
       
        }
       
        if (0 == stat("/Applications/Cydia.app", &stat_info)) {
       
            jailbroken = YES;
       
        }
       
        if (0 == stat("/var/lib/cydia/", &stat_info)) {
       
            jailbroken = YES;
       
        }
       
        if (0 == stat("/var/cache/apt", &stat_info)) {
       
            jailbroken = YES;
       
        }
       
        //    /Library/MobileSubstrate/MobileSubstrate.dylib 最重要的越狱文件,几乎所有的越狱机都会安装MobileSubstrate
       
        //    /Applications/Cydia.app/ /var/lib/cydia/绝大多数越狱机都会安装
       
        //    /var/cache/apt /var/lib/apt /etc/apt
       
        //    /bin/bash /bin/sh
       
        //    /usr/sbin/sshd /usr/libexec/ssh-keysign /etc/ssh/sshd_config
       
           
       
        //可能存在stat也被hook了,可以看stat是不是出自系统库,有没有被攻击者换掉
       
        //这种情况出现的可能性很小
       
        int ret;
       
        Dl_info dylib_info;
       
        int (*func_stat)(const char *,struct stat *) = stat;
       
        if ((ret = dladdr(func_stat, &dylib_info))) {
       
    //        NSLog(@"lib:%s",dylib_info.dli_fname);      //如果不是系统库,肯定被攻击了
       
            if (strcmp(dylib_info.dli_fname, "/usr/lib/system/libsystem_kernel.dylib")) {   //不相等,肯定被攻击了,相等为0
       
                jailbroken = YES;
       
            }
       
        }
       
           
       
        //还可以检测链接动态库,看下是否被链接了异常动态库,但是此方法存在appStore审核不通过的情况,这里不作罗列
       
        //通常,越狱机的输出结果会包含字符串: Library/MobileSubstrate/MobileSubstrate.dylib——之所以用检测链接动态库的方法,是可能存在前面的方法被hook的情况。这个字符串,前面的stat已经做了
       
           
       
        //如果攻击者给MobileSubstrate改名,但是原理都是通过DYLD_INSERT_LIBRARIES注入动态库
       
        //那么可以,检测当前程序运行的环境变量
       
        char *env = getenv("DYLD_INSERT_LIBRARIES");
       
        if (env != NULL) {
       
            jailbroken = YES;
       
        }
       
           
       
        return jailbroken;
       
    }
    
  • https://github.com/githubPagesio/nabla-c0d3.github.io

  • knpost

/Users/devzkn/bin//knpost Security 安全保护 -t security
#原来""的参数,需要自己加上""

转载请注明: > Security