iOS安全防护--在AFNetworking中实现 SSL pinning

我手头上的APP都是企业级证书的APP
而这些APP呢每一年都要被抓去做一次Pentest并进行enhance
pentest是penetration test的简写,渗透性测试的意思。

渗透测试 (penetration test)并没有一个标准的定义,国外一些安全组织达成共识的通用说法是:渗透测试是通过模拟恶意黑客的攻击方法,来评估计算机网络系统安全的一种评估方法。这个过程包括对系统的任何弱专点、技术缺陷或漏洞的主动分属析,这个分析是从一个攻击者可能存在的位置来进行的,并且从这个位置有条件主动利用安全漏洞。

我的亲儿子(我负责的其中一个APP)也在这两年pentest的修复中茁壮成长
收到pentest的报告其中有一项骂我说没有帮他做SSL pinning。。
下面就记录一下这个SSL pinning


其实我的APP很危险

HTTP:我有SSL/TLS的加持,恕我直言,在座的各位,都是垃圾
Charles:哦?是吗?那么棒?你帮我看看刚刚你的这个登录密码对不对?
Fiddle:Charles哥你负责在Mac的,Windows那边交给我
其实我的APP很危险,甚至被中间人攻击了都不知道🤷♀️

苍蝇不叮无缝的蛋

在你了解完http加上s的工作原理之后,以为非对称加密+对称加密就很安全了吗?
其实在客户端和服务器握手的第一步,中间人就能截获客户段请求,截获客户端发给服务器之后用来生成对称加密的钥匙的随机数(有点绕,如果很难理解的话先看懂上面的https工作原理),然后以客户段的名义跟服务器握手,以服务器的名义与客户端愉快的交流

中间人用的伪证书,这个证书可能就是从CA官方申请的,客户端一般都会信任这类证书,这就是可以进行中间人攻击的原因所在。

为什么伪造证书可以实现中间人攻击?

答案就在于用户让iOS系统信任了不应该信任的证书(安装了证书)。用户设置系统信任的证书,会作为锚点证书(Anchor Certificate)来验证其他证书。当返回的服务器证书是锚点证书,就被信任。
其实Charles和Fiddle就是中间人,而我们使用它们的时候必须要下载并信任它的证书,就是这个道理


其实我的APP也可以没那么危险

如果从CA申请证书打包到App中,把这个证书作为Anchor Certificate来保证证书链的唯一性和可信性。只相信app里面的锚点证书,也就只会验证通过由这些锚点证书签发的证书。这样就算被验证的证书是由系统其他信任的锚点证书签发的,也无法验证通过。
简单来说,将服务器的证书固定在客户端上,通过SSL证书绑定来验证服务器身份,防止应用被抓包

  • 解决办法思路:如果从CA申请证书打包到App中,把这个证书作为Anchor Certificate来保证证书链的唯一性和可信性。只相信app里面的锚点证书,也就只会验证通过由这些锚点证书签发的证书。这样就算被验证的证书是由系统其他信任的锚点证书签发的,也无法验证通过。

  • 做法:选择哪个证书打包到app里面,很多开发者会直接选择叶子证书。其实对于自建证书来说,选择哪一节点都是可行的。而对于由CA颁发的证书,则建议导入颁发该证书的CA机构证书或者是更上一级CA机构的证书,甚至可以是根证书。这是因为:

  1. 一般叶子证书的有效期都比较短
  2. 越往证书链的末端,证书越有可能变动;

准备工作

ssl pinning:我要上场啦
我用的是Objective-C语言

取到证书

客户端需要证书(Certification file), .cer格式的文件。可以跟服务器端索取。
如果他们给个.pem文件,要使用命令行转换:

openssl x509 -inform PEM -in name.pem -outform DER -out name.cer

如果给了个.crt文件,请这样转换:

openssl x509 -in name.crt -out name.cer -outform der

如果啥都不给你,你只能自己动手了:

openssl s_client -connect www.website.com:443 </dev/null 2>/dev/null | openssl x509 -outform DER > myWebsite.cer

好,我们拿到证书了。

把证书加进项目中

把生成的.cer证书摁住直接拖进你的项目相关文件中,记得勾选Copy items if needed和你的targets

image.png

AFSecurityPolicy

下面我们来分两个版本来说

  1. AFNetworking 3.0 以下版本使用AFHTTPRequestOperationManager
+(AFHTTPRequestOperationManager *)manager{
    static AFHTTPRequestOperationManager *manager = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        //1.创建manager对象
        NSURL *baseUrl = [NSURL URLWithString:@"你的访问的地址的domian"];
        manager = [[AFHTTPRequestOperationManager manager]initWithBaseURL:baseUrl];
        
        //2.设置接收的response类型
        [[manager responseSerializer]setAcceptableContentTypes:[NSSet setWithObjects:@"application/json",@"text/plain",@"text/html", nil]];
        
        //3.https证书配置
        //3.1 先将证书拖进项目
        //3.2 获取证书的路径
        NSString *certPath = [[NSBundle mainBundle] pathForResource:@"你的证书名字" ofType:@"cer"];
        //3.3 获取证书data
        NSData *certData = [NSData dataWithContentsOfFile:certPath];
        //3.4 创建AFN 中的securityPolicy
        AFSecurityPolicy *securitypolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
        //3.5 绑定证书
        [securitypolicy setPinnedCertificates:@[certData]];
        //3.6 是否允许无效证书
        [securitypolicy setAllowInvalidCertificates:NO];
        //3.7 是否需要验证域名
        /*
        validatesDomainName 是否需要验证域名,默认为YES;
        假如证书的域名与你请求的域名不一致,需把该项设置为NO;
        如设成NO的话,即服务器使用其他可信任机构颁发的证书,也可以建立连接,这个非常危险,建议打开。
        置为NO,主要用于这种情况:客户端请求的是子域名,而证书上的是另外一个域名。
        因为SSL证书上的域名是独立的,假如证书上注册的域名是www.google.com,那么mail.google.com是无法验证通过的;
        当然,有钱可以注册通配符的域名*.google.com,但这个还是比较贵的。
        如置为NO,建议自己添加对应域名的校验逻辑。
         */
        [securitypolicy setValidatesDomainName:YES];
        
        //4. 上述securitypolicy设置为manager的securitypolicy
        manager.securityPolicy = securitypolicy;
        
    });
    return manager;
}

发送请求

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    AFHTTPRequestOperationManager *manager = [NetWorkManager manager];
    manager.responseSerializer = [AFHTTPResponseSerializer serializer];
    
   [manager GET:@"https://www......."
      parameters:nil
         success:^(AFHTTPRequestOperation * _Nonnull operation, id  _Nullable responseObject) {
       NSLog(@"%@",responseObject);
   } failure:^(AFHTTPRequestOperation * _Nullable operation, NSError * _Nonnull error) {
       NSLog(@"%@",error);
   }];
}
  1. AFNetworking 3.0 以上版本移除了AFHTTPRequestOperationManager并且用AFHTTPSessionManager替代
+ (AFHTTPSessionManager *)manager
{
    static AFHTTPSessionManager *manager = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        
        //1.创建manager对象
        NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
        manager =  [[AFHTTPSessionManager alloc] initWithBaseURL:[NSURL URLWithString:@"你的访问的地址的domian"] sessionConfiguration:config];
        //2.设置接收的response类型
        [[manager responseSerializer]setAcceptableContentTypes:[NSSet setWithObjects:@"application/json",@"text/plain",@"text/html", nil]];
        
        //3.https 证书配置
        //3.1 将证书拖进项目
        //3.2 获取证书路径
        NSString *certPath = [[NSBundle mainBundle] pathForResource:@"你的证书名字" ofType:@"cer"];
        //3.3 获取证书data
        NSData *certData = [NSData dataWithContentsOfFile:certPath];
        //3.4 创建AFN 中的securityPolicy
        AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModePublicKey withPinnedCertificates
                                                                                  :[[NSSet alloc] initWithObjects:certData,nil]];
        //3.5 这里就可以添加多个server证书
        NSSet *dataSet = [[NSSet alloc]initWithObjects:certData, nil];
        //3.6 绑定证书(不止一个证书)
        [securityPolicy setPinnedCertificates:dataSet];
        //3.7 是否允许无效证书
        [securityPolicy setAllowInvalidCertificates:NO];
        //3.8 是否需要验证域名
        /*
        validatesDomainName 是否需要验证域名,默认为YES;
        假如证书的域名与你请求的域名不一致,需把该项设置为NO;
        如设成NO的话,即服务器使用其他可信任机构颁发的证书,也可以建立连接,这个非常危险,建议打开。
        置为NO,主要用于这种情况:客户端请求的是子域名,而证书上的是另外一个域名。
        因为SSL证书上的域名是独立的,假如证书上注册的域名是www.google.com,那么mail.google.com是无法验证通过的;
        当然,有钱可以注册通配符的域名*.google.com,但这个还是比较贵的。
        如置为NO,建议自己添加对应域名的校验逻辑。
         */
        [securityPolicy setValidatesDomainName:YES];

        manager.securityPolicy = securityPolicy;
    });
    return manager;
}

发送请求

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    AFHTTPSessionManager *manager = [NetworkManager manager];
    
    [manager POST:@"https://www....." parameters:nil headers:nil constructingBodyWithBlock:^(id<AFMultipartFormData>  _Nonnull formData) {
        NSLog(@"formData:%@",formData);
    } progress:^(NSProgress * _Nonnull uploadProgress) {
        
    } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        NSLog(@"responseObject:%@",responseObject);
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        NSLog(@"error:%@",error);
    }];

}

最关紧是设置 AFSSLPinningMode

AFSecurityPolicy policyWithPinningMode:AFSSLPinningModePublicKey

AFSSLPinningModeNone: 完全信任;
AFSSLPinningModePublicKey:只校验服务器证书和本地证书的Public Key是否一致
AFSSLPinningModeCertificate:比对服务器证书和本地证书的所有内容,完全一致则信任服务器证书

选择那种模式呢?

AFSSLPinningMode:信任任何证书,没有安全性可言,是默认值。
AFSSLPinningModeCertificate:最安全的比对模式。但是也比较麻烦,因为证书是打包在APP中,如果服务器证书改变或者到期,旧版本无法使用了,我们就需要用户更新APP来使用最新的证书。
AFSSLPinningModePublicKey:只比对证书的Public Key,而一般更新服务器证书,公钥是不会变的,只要公钥没有改变,证书的其他变动都不会影响使用。

如果你不能保证你的用户总是使用你的APP的最新版本,所以我们使用AFSSLPinningModePublicKey

Charles对使用SSL Pinning前后抓包对比
证书固定前.png
证书固定后.png

亲测过的坑(泪目)

  1. 服务器的同事给了我.cer.crt的证书文件给我,但是cert pinning怎么都不起作用, 后来我用.crt 证书用命令行转换之后再导进项目就起作用了。。
openssl x509 -in name.crt -out name.cer -outform der
  1. 我尝试导出github的ssl证书再去试cert pinning但是始终不起作用,如果有大神知道的话麻烦不吝赐教🙏。

本文参考资料(感谢🙏)

AFNetworking + SSL Pinning
iOS 集成 SSL Pinning
如何使用SSL pinning来使你的iOS APP更加安全
App的中间人攻击
iOS AFNetworking框架HTTPS请求配置
iOS afnetworking最新版报错 没有AFHTTPRequestOperationManager类了
HTTPS 的工作原理

Android版的请顺着网线移步到Android逆开发--Volley/OkHttp SSL Pinning(证书固定)可以这样做

写作初心

梳理,积累,分享,交流

靴靴你能看到这里
下一篇见 ᕕ(ᐛ)ᕗ

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,444评论 4 365
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,867评论 1 298
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,157评论 0 248
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,312评论 0 214
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,673评论 3 289
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,802评论 1 223
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,010评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,743评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,470评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,696评论 2 250
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,187评论 1 262
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,538评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,188评论 3 240
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,127评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,902评论 0 198
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,889评论 2 283
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,741评论 2 274