一、SSL
在日常的安全渗透过程中,我们经常会遇到瓶颈无处下手,这时候如果攻击者从APP进行突破,往往会有很多惊喜。但是目前市场上的APP都会为防止别人恶意盗取和恶意篡改进行一些保护措施,比如模拟器检测、root检测、APK加固、代码混淆、代码反调试、反脱壳、签名校验等等对抗机制。而测试人员对APP进行渗透的首步操作通常就是上burp或者Charles这类抓包工具进行抓包,查看请求记录里的域名及链接地址是否可以进一步利用。
我们都知道http协议传输的是明文信息,是可以直接捕获的,从而造成了数据泄露。为了防止中间人的拦截,出现了HTTPS加密机制。在HTTPS中,使用了证书+数字签名解决了抓包的问题,这里用到了两个概念:数字签名和数字证书。
- 数字签名:是发送方的明文经历了两次加密得到的两个东西组成,一个是hash ,一个是经过私钥加密。
- 数字证书:其实就是明文+数字签名。但是数字证书中的内容远不止这俩,还包括了权威机构的信息,服务器的域名,最重要的是有签名的计算方法,还有就是证书中还包括公钥,公钥用于发放给请求证书的客户端。
SSL(安全套接字层)又被称为TLS(数据层安全协议),是一种为网络通信提供数据完整性的一种安全协议。它位于TCP/IP协议与各种应用协议之间。SSL协议主要分为两个部分:Handshake Protocol和Record Protocol。
Handshake protocol是用来协商加密通信数据的密钥,Record Protocol定义传输内容的格式。事实上,HTTPS就是使用SSL/TLS协议进行加密传输,让客户端拿到服务器的公钥,然后客户端随机生成一个对称加密的秘钥,使用公钥加密,传输给服务端,后续的所有信息都通过该对称秘钥进行加密解密,完成整个HTTPS的流程。
二、SSL pinning方案
证书锁定本质是对抗中间人攻击,并非用于对抗抓包破解。但如果程序逻辑未被注入运行在"可信环境"中倒是有些作用。SSL证书锁定之前,我们需要理解一些基本的概念:
- 可信CA: CA(Certificate Authority)是数字证书认证中心的简称,是指发放、管理、废除数字证书的机构。CA的作用是检查证书持有者身份的合法性,并签发证书,以防证书被伪造或篡改,以及对证书和密钥进行管理。
- 双向锁定:在客户端锁定服务端证书的基础上,服务端对客户端的证书也进行锁定,需要客户端再做一次证书预埋。多见于金融业务场景。
- 证书链:证书链就是Root CA签发二级Intermediate CA,二级Intermediate CA可以签发三级Intermediate CA,也可以直接签发用户证书。从Root CA到用户证书之间构成了一个信任链:信任Root CA,就应该信任它所信任的二级Intermediate CA,从而就应该信任三级Intermediate CA直至信任用户证书。
- 逐级验证:客户端对于收到的多级证书,需要从站点证书(leaf certificate)开始逐级验证,直至出现操作系统或浏览器内置的受信任CA 根证书(root certificate)。
SSL Pinning技术指的是在应用程序中只信任固定证书或是公钥。应用程序开发人员在程序中使用SSL pinning技术作为应用流量的附加安全层。实现SSL pinning的方法主要有两种:证书固定和公钥固定。
- 证书固定:开发者将SSL证书的某些字节码硬编码在用程序中。当应用程序与服务器通信时,它将检查证书中是否存在相同的字节码。如果存在,则应用程序将请求发送到服务器;如果字节码不匹配,它将抛出SSL证书错误。此技术可防止攻击者使用自己的自签名证书。
- 公钥固定:在客户访问网站时进行公钥固定中,服务器将其公钥固定(通过注入)在客户端(客户)浏览器中。当客户端重新访问同一网站时,服务器将标识其公共密钥以检查连接的完整性。此技术还可以防止攻击者使用自签名证书。
通常,我们在测试一个应用的时候会设置代理,从而获取应用在运行过程中的流量。我们使用代理工具获取流量的话,需要在设备中安装我们自己的证书到可信任的根证书中,这样应用程序才会将我们的证书视为可信任的有效证书,允许代理工具拦截应用的流量。但是使用的SSL pinning技术的应用程序,只信任指定的证书,那么我们就算把我们的证书安装到设备中,应用程序也不会信任我们的证书,这样的话我们就不能通过代理的方式来拦截应用的流量。
三、SSL pinning方案对比
根据安全级别的不同,证书锁定方案大体可以分为如下几种:
安全等级 | 策略 | 信任范围 | 破解方法 | |
---|---|---|---|---|
0 | 完全兼容策略 | 信任所有证书包括自签发证书 | 无需特殊操作 | |
1 | 系统/浏览器默认策略 | 信任系统或浏览器内置CA证书、用户安装的证书 | 设备安装代理证书(用户) | |
2 | System CA pinning | 只信任系统根证书,不信任用户安装的证书(android7支持配置network-security-config) | 注入或者root后将用户证书拷贝到系统证书目录 | |
3 | CA pinningRoot(intermediate)certificate pinning | 信任指定CA办法的证书 | Hook注入等方式篡改锁定逻辑 | |
4 | Leaf Certificate pinning | 信任指定站点证书 | Hook注入等方式篡改锁定逻辑,如遇双向锁定需将app自带证书导入代理软件 |
3.1 站点证书锁定
锁定站点证书是一种比较安全的做法,但是它有个缺陷就是需要维护预埋证书。如果你没考虑过更新预埋证书的话就会出现SSL握手失败的提示。现在的站点证书一般有效期都是在1到2年,所以做站点证书锁定还要保证服务可用性的话就得必须实现客户端锁定证书指纹的更新。更新站点证书的网络请求通常有如下策略:
- 指纹更新请求被劫持到的概率比较低,不锁定更新指纹请求直接使用https完成,缺点是安全性稍弱。
- 自签名证书的有效期非常长,用自签名证书锁定指纹更新请求,缺点是兼容性稍弱。
通常,锁定站点证书时,服务端需要实现证书指纹下发接口。还有每到证书即将过期的时候需要人为的将证书指纹配置到客户端中。提取指纹配置可以由代码实现,但是签发证书是由第三方CA完成的,所以此种方式并不是很智能。并且,"人"为因素的引入会给业务稳定性带来极大风险。
3.2 中间证书锁定
锁定中间证书或根证书的优势是安全性接近锁定站点证书,且这两证书的有效期一般很长,可以达到10年到30年。所以,在不考虑热更新证书指纹的情况下,可以使用此种方案。
除了证书有效期时间长的优势外,锁定间证书或根证书还可以更好的兼顾业复杂的业务场景。因为企业子域名很多情况下都是自己业务的站点证书,但是一个企业通常站点证书都是由一个中间证书(根证书)衍生下来的。所以,锁定间证书或根证书不用特别对每个业务线做调整,一套策略或者方案基本可以适用企业整个业务线。
比如,我们有一个中间证书,到期时间为2029年,对于这么长的时间窗口,我们完全可以让指纹随着应用更新完成迭代。
但是,锁定中间证书的方案会遇到一个问题,那就是更换证书CA(数字证书颁发机构)。这就需要通过备份一些可能会用的到CA指纹,中间证书的量级相对于根证书要高出很多,而且也不好预测将来可能会更换到哪些中间证书。
3.3 根证书锁定
参考操作系统更新预埋CA根证书的机制,我们可以通过自升级完成锁定CA的指纹更新。在Android N系统版本中,内置了150多个系统根证书。而实际作为一个应用是不需要信任这么多CA的根证书的。可靠卖证书的CA就那么十来家,业务的安全需求决定了需要哪种类型的证书,这样备份证书的范围就收窄了,且根证书的数量级相对小,所以就没中间证书备份难的问题。
目前主流的SSL证书主要分为 DV < OV < EV,安全性和价格是递增的。DV和OV型证书最大的差别是:DV型证书不包含企业名称信息;而OV型证书包含企业名称信息。
综合看来,根锁定策略的安全性实施难度比较适合账号业务。接下来就是备份证书的选择,备份锁定证书的主要的考量因素:
- 有效期
- 安全性
- 兼容性
例如,下面是Mac系统内置的一些根证书。有效期较长CA分别是HARICA、DigiCert等。其中,对于那些将在未来某个时间才会生效的根证书,我们可以将这类根作为备份,因为这些有效期长且在较早的系统版本中预埋,说明兼容性也过关,如果对安全性有更改追求可以预埋些EV证书。
解决根证书的锁定问题后,接下来就是解决到期的问题。比如锁的根证书2031年到期,2031年之后应该如何处理。对于这种场景,我们可以有如下选项:
- 拒绝连接,安全优先;
- 允许连接,可用优先;
- 提示风险让用户选择,折中策略;
3.4 客户端系统证书锁定
这个锁定方案相对前三个要保守许多,安全性提升也相对有限,不过可以作为一种加强的方案。我们需要做的仅仅是将通用操作系统中用户安装的第三方证书移除APP的证书信任列表。并且从Android7.0版本开始默认支持此特性,操作的方法也很简单,只需要通过network-security-config更改配置即可。
在 Android 7.0 及其以后的版本中,我们可以通过使用“网络安全性配置”来自定义其安全(HTTPS、TLS)连接的行为,无需修改任何代码,下面是官方关于网络安全配置的说明。
四、网络安全性配置
下面我们简单介绍一下,网络安全性配置network-security-config,如果想要了解更多细节,可以点击查看:网络安全配置的说明。
4.1 系统证书锁定
通常,Android的应用包只有在release模式下只能信任系统证书,移除用户安装的证书的信任。
debug模式下可以加入对信任用户安装的证书。
4.2 根证书锁定
在使用根证书锁定时,强制锁定了两个根证书GoDaddy Class 2 Certification Authority Root Certificate和DigiCert。debug模式下加入对信任用户安装的证书,超过根证书的时间时解除锁定。
www.mi.com
VjLZe/p3W/PJnd6lL8JVNBCGQBZynFLdZSTIqcO0SJ8=
VjLZe/p3W/PJnd6lL8JVNBCGQBZynFLdZSTIqcO0SJ8=
http://report.m.com/log_report
五、设备证书
设备ROM发版相对app发版要复杂许多,所以设备的证书锁定场景复杂性更高。为了方便说明,我们先将设备抽象成两大类:系统自带的证书,比如AndroidTV操作系统自带的;另一种是系统没有预制的证书,比如实时操作系统(RTOS)。如果设备是第一类通用操作系统,则比较好处理。
- 如果证书是CA签发的,只需信任系统证书即可,最好同时开启系统分区保护。
- 如果证书是自签发的,除了信任系统证书以外还需要额外只信任此自签发证书。需要注意的是,切勿为了跑通业务盲目信任所有证书。
如果,一些业务刚开发的时候可能还没买证书,所以初期代码是信任所有证书,后来买正式证书后忘记修复证书信任代码。例如没买证书之前curl使用了-k参数,买完证书后忘记统一除去此参数。
-k, --insecure Allow connections to SSL sites without certs (H)
如果设备是第二类RTOS,首先得需要确认其是否支持SSL。其上运行的业务是否需要SSL,如果需要且支持,则可以通过自行预制根再参考前文完成锁定。
六、TrustKit
对于Android N及更高版本,我们可以通过添加了证书方式来保证安全,但是对于Android N一以下的版本,我们需要怎么做呢?此处给大家介绍一款开源TrustKit,它可以在network_security_config.xml文件中使用相同的格式来添加对Android N下版本的支持。
下面我们就来说说TrustKit的使用。首先,在项目的app/build.gradle文件添加如下依赖。
implementation 'com.datatheorem.android.trustkit:trustkit:'
然后,打开network_security_config.xml文件,添加如下内容:
www.datatheorem.com
k3XnEYQCK79AtL9GYnT/nyhsabas03V+bhRQYHQbpXU=
2kOi4HdYYsvTR1sTIR7RHwlf2SescTrpza9ZrWy7poQ=
http://report.datatheorem.com/log_report
完成上述配置之后,在AndroidManifest.xml文件中引入上面的配置文件,如下。
...
当然,我们也可以通过代码的方式来引入TrustKit并进行相关的配置。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.OnCreate(savedInstanceState);
// Using the default path - res/xml/network_security_config.xml
TrustKit.initializeWithNetworkSecurityConfiguration(this);
// OR using a custom resource (TrustKit can't be initialized twice)
TrustKit.initializeWithNetworkSecurityConfiguration(this, R.xml.my_custom_network_security_config);
URL url = new URL("https://www.datatheorem.com");
String serverHostname = url.getHost();
//Optionally add a local broadcast receiver to receive PinningFailureReports
PinningValidationReportTestBroadcastReceiver receiver = new PinningValidationReportTestBroadcastReceiver();
LocalBroadcastManager.getInstance(context)
.registerReceiver(receiver, new IntentFilter(BackgroundReporter.REPORT_VALIDATION_EVENT));
// HttpsUrlConnection
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
connection.setSSLSocketFactory(TrustKit.getInstance().getSSLSocketFactory(serverHostname));
// OkHttp 2.x
OkHttpClient client =
new OkHttpClient()
.setSslSocketFactory(OkHttp2Helper.getSSLSocketFactory());
client.interceptors().add(OkHttp2Helper.getPinningInterceptor());
client.setFollowRedirects(false);
// OkHttp 3.0.x, 3.1.x and 3.2.x
OkHttpClient client =
new OkHttpClient.Builder()
.sslSocketFactory(OkHttp3Helper.getSSLSocketFactory())
.addInterceptor(OkHttp3Helper.getPinningInterceptor())
.followRedirects(false)
.followSslRedirects(false)
// OkHttp 3.3.x and higher
OkHttpClient client =
new OkHttpClient.Builder()
.sslSocketFactory(OkHttp3Helper.getSSLSocketFactory(), OkHttp3Helper.getTrustManager())
.addInterceptor(OkHttp3Helper.getPinningInterceptor())
.followRedirects(false)
.followSslRedirects(false)
.build();
}
class PinningFailureReportBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
PinningFailureReport report = (PinningFailureReport) intent.getSerializableExtra(BackgroundReporter.EXTRA_REPORT);
}
}
关于SSL pinning的知识就介绍这么多,有疑问欢迎留言。