我手头上的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机构的证书,甚至可以是根证书。这是因为:
- 一般叶子证书的有效期都比较短
- 越往证书链的末端,证书越有可能变动;
准备工作
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 | openssl x509 -outform DER > myWebsite.cer
好,我们拿到证书了。
把证书加进项目中
把生成的.cer
证书摁住直接拖进你的项目相关文件中,记得勾选Copy items if needed
和你的targets
AFSecurityPolicy
下面我们来分两个版本来说
- 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);
}];
}
- 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 _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前后抓包对比
亲测过的坑(泪目)
- 服务器的同事给了我
.cer
和.crt
的证书文件给我,但是cert pinning怎么都不起作用, 后来我用.crt
证书用命令行转换之后再导进项目就起作用了。。
openssl x509 -in name.crt -out name.cer -outform der
- 我尝试导出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(证书固定)可以这样做
写作初心
梳理,积累,分享,交流
靴靴你能看到这里
下一篇见 ᕕ(ᐛ)ᕗ