目前,我们在开发App的时候对Https的校验方面都比较忽略,当我们使用一些抓包工具测试的时候,即使手机不安装抓包工具的证书,也可以抓取我们Https进行劫持看见我们的明文内容会泄露用户的隐私数据,下面我就写下我的防护措施。
针对SSL的中间人攻击方式主要有两类,分别是SSL劫持攻击和SSL剥离攻击
SSL劫持攻击即SSL证书欺骗攻击,攻击者为了获得HTTPS传输的明文数据,需要先将自己接入到客户端和目标网站之间;在传输过程中伪造服务器的证书,将服务器的公钥替换成自己的公钥,这样,中间人就可以得到明文传输带Key1、Key2和Pre-Master-Key,从而窃取客户端和服务端的通信数据;
但是对于客户端来说,如果中间人伪造了证书,在校验证书过程中会提示证书错误,由用户选择继续操作还是返回,由于大多数用户的安全意识不强,会选择继续操作,此时,中间人就可以获取浏览器和服务器之间的通信数据
这种攻击方式也需要将攻击者设置为中间人,之后见HTTPS范文替换为HTTP返回给浏览器,而中间人和服务器之间仍然保持HTTPS服务器。由于HTTP是明文传输的,所以中间人可以获取客户端和服务器传输数据。
上面的内容来自《Https协议简析及中间人攻击原理》如果还需要对Https的知识理解和补充可以自行Google,这里就不多做解释了。
通常我们看似做了Https,但是我们没有做一些安全措施的话,我们的数据就会暴露,下面我们来做一些测试。
Charles是一个很好用的抓包工具,有很多强大的功能,不过是付费的,有30天的试用期。
这里有一个很简单的教程看了配置下就可以了《十分钟学会Charles抓包(iOS的http/https请求)
》,虽然是文章是关于IOS但是Android的配置方式是一样的。
我屏蔽了一些敏感信息,可以看见我们成功的抓取了Https链接里面的明文信息,并且我没有安装抓包工具的证书,下面我们来进行分析。
我来展示下我在App中编写的代码:
OkHttpClient okHttpClient = new OkHttpClient();
try {
final TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
@Override
public void checkClientTrusted(
java.security.cert.X509Certificate[] chain,
String authType) throws CertificateException {
}
@Override
public void checkServerTrusted(
java.security.cert.X509Certificate[] chain,
String authType) throws CertificateException {
}
@Override
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
}};
final SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustAllCerts,
new java.security.SecureRandom());
// Create an ssl socket factory with our all-trusting manager
final SSLSocketFactory sslSocketFactory = sslContext
.getSocketFactory();
okHttpClient.setSslSocketFactory(sslSocketFactory);
okHttpClient.setHostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
});
} catch (Exception e) {
}
在代码中造成ssl中间人攻击有三个可能性:
安全
的呢。PS:这里如果理解有问题的话,需要看一些HTTPS的相关知识,这里我补一张图。方便阅读便于记忆
SSL/TLS层负责客户端和服务器之间的加解密算法协商、密钥交换、通信连接的建立,安全连接的建立过程如下所示:
目前的Https认证中,分为两种证书:
CA机构颁发的证书
HTTPS网站所用的证书可向可信CA机构申请,不过这一类基本上都是商业机构,申请证书需要缴费,一般是按年缴费,费用因为CA机构的不同而不同。
自签署服务证书
如果只是APP与后台服务器进行HTTPS通信,可以使用openssl工具生成自签发的数字证书,可以节约费用,不过得妥善保护好证书私钥,不能泄露或者丢失。HTTPS通信所用的数字证书格式为X.509。
我们先来看一段代码:
OkHttpClient.Builder builder = new OkHttpClient().newBuilder();
try {
client = builder.hostnameVerifier(RELEASE)
.sslSocketFactory(getSslSocketFactory1())
.build();
} catch (Exception e) {
}
ps:代码这里我使用了OKhttp,在这里就默认大家会使用了。网上教程很多,可以自行google。
验证证书有两个重要的步骤:
第一步,是通过我们的数字证书生成一个新的SSLSocketFactory替换掉默认的,也就是我们的自定义TrustManager,使系统信任我们的证书,和前面的抓包工具安装证书类似。
第二步, 是对我们证书的校验,当第一步成功以后,就会将我们Https的域名变为合法的,当我们访问自己的服务器的时候就不会出现SSLHandshakeException。
如果申请CA认证的话,需要我们对CA机构缴费,许多公司不愿意缴费,在这种情况下,由于您具有系统不信任的 CA,将发生 SSLHandshakeException。原因可能是您有一个来自 Android 还未信任的新 CA 的证书,或您的应用在没有 CA 的较旧版本上运行。
在这里,您可以指示 HttpsURLConnection 信任特定的 CA 集。此过程可能有点复杂,下面的示例展示了这个过程,从 InputStream 获取一个特定的 CA,用该 CA 创建 KeyStore,然后用后者创建和初始化 TrustManager。TrustManager 是系统用于从服务器验证证书的工具,可以使用一个或多个 CA 从 KeyStore 创建,而创建的 TrustManager 将仅信任这些 CA。
如果是新的 TrustManager,此示例将初始化一个新的 SSLContext,后者可以提供一个 SSLSocketFactory,您可以通过 HttpsURLConnection 用它来替换默认的 SSLSocketFactory。这样一来,连接将使用您的 CA 验证证书。
public static SSLSocketFactory getSslSocketFactory1() throws NoSuchAlgorithmException, KeyManagementException, CertificateException, KeyStoreException, IOException {
//以X.509格式获取证书
CertificateFactory cf = CertificateFactory.getInstance(X_509);
InputStream caInput = null;
try {
caInput = new BufferedInputStream(new FileInputStream("load-der.crt"));//一般是存放在Assert下
} catch (IOException e) {
e.printStackTrace();
}
Certificate ca;
try {
ca = cf.generateCertificate(caInput);
System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN());
} finally {
caInput.close();
}
// 创建包含受信任CA的KeyStore
String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
keyStore.setCertificateEntry(CA, ca);
// 创建一个信任我们的KeyStore中的CA的TrustManager
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(keyStore);
// 创建一个使用我们的TrustManager的SSLContext
SSLContext context = SSLContext.getInstance(TSL);
context.init(null, tmf.getTrustManagers(), null);
return context.getSocketFactory();
}
上面的代码就是将自签署的服务证书放在Assert包下,然后通过带代码生成一个SSLSocketFactory替换默认的。上面的代码中我们还可以使用另外一种形式来进行验证。
public static SSLSocketFactory getSslSocketFactory2() throws NoSuchAlgorithmException, KeyManagementException {
final TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
private X509Certificate serverCert;
private final String CRT_NAME = "martin.crt";
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {
}
@Override
public void checkServerTrusted(X509Certificate[] x509Certificates, String authType) throws CertificateException{
if (x509Certificates == null) {
throw new IllegalArgumentException("check Server X509Certificates is null");
}
if (x509Certificates.length < 0) {
throw new IllegalArgumentException("check Server X509Certificates is empty");
}
InputStream caInput = null;
try {
caInput = new BufferedInputStream(Utils.getApp()
.getAssets()
.open(CRT_NAME));
} catch (IOException e) {
e.printStackTrace();
}
CertificateFactory cf = CertificateFactory.getInstance("X.509");
serverCert = (X509Certificate) cf.generateCertificate(caInput);
for (X509Certificate cert : x509Certificates) {
try {
cert.checkValidity();
cert.verify(serverCert.getPublicKey());//和App预埋证书做对比
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchProviderException e) {
e.printStackTrace();
} catch (SignatureException e) {
e.printStackTrace();
}
}
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
};};
final SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
return sslContext.getSocketFactory();
}
上面的两种方法,选一种实现就行。
正如本文开头所述,验证 SSL 连接有两个关键环节。首先是验证证书是否来自值得信任的来源,这是前面部分重点讲述的内容。而此部分侧重于第二个环节:确保您正在通信的服务器提供正确的证书。
public static HostnameVerifier RELEASE = new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return HttpsURLConnection.getDefaultHostnameVerifier().verify(hostname, session);
}
};
当我们加入了自签署证书以后,验证就会为true。
因为在Android手机刚出厂的时候,系统都会默认装一些CA机构的根证书以作为认证。所以不需要公钥进行自定义TrustManager,那怎么得知我们的链接是进行过CA机构认证的呢?
当我们再google输入百度的网址时候,会出现一个锁的图片,点击后出现下面的证书细节。
上面已经看见百度的证书是CA机构认证的,如果你们公司的也是的话,我们只需要进行主机域名认证就没问题了。
public static HostnameVerifier RELEASE = new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return HttpsURLConnection.getDefaultHostnameVerifier().verify(hostname, session);
}
};
今天讲解的内容还是比较简单,总结一下:
中间人攻击攻击分为
– SSL剥离攻击
– SSL劫持攻击
安装抓包工具Charles,对不进行证书校验的App进行明文抓包。
两种签名证书
– 自签名服务证书校验的两个步骤: 自定义SSLContextFactory 2.进行主机名验证。
– CA认证证书的校验方法。