forked from Trietptm-on-Security/WooYun-2
-
Notifications
You must be signed in to change notification settings - Fork 7
/
Android证书信任问题与大表哥.html
385 lines (271 loc) · 129 KB
/
Android证书信任问题与大表哥.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
<html>
<head>
<title>Android证书信任问题与大表哥 - 瘦蛟舞</title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
</head>
<body>
<h1>原文地址:<a href="http://drops.wooyun.org/tips/3296">http://drops.wooyun.org/tips/3296</a></h1>
<p>
<h2>0x00 起因</h2>
<hr />
<p>1、近期icloud.com、yahoo.com、apple.com遭遇到大规模劫持</p>
<p> <a target="_blank" href="http://www.wooyun.org/bugs/wooyun-2014-080117">WooYun: Yahoo雅虎在国内访问遭遇SSL中间人攻击(被替换为自签名证书)</a> </p>
<p>2、乌云平台、CVE都收到大量有关Android APP信任所有证书的漏洞</p>
<p> <a target="_blank" href="http://www.wooyun.org/bugs/wooyun-2014-079358">WooYun: 国内绝大部分Android APP存在信任所有证书漏洞</a> </p>
<p>3、老外写有关大表哥的文章中提到MITM时360浏览器不提示证书错误</p>
<p><a href="http://www.computerworld.com/article/2836084/chinese-big-brother-launches-nationwide-attack-on-icloud.html">http://www.computerworld.com/article/2836084/chinese-big-brother-launches-nationwide-attack-on-icloud.html</a></p>
<p>之前信任证书问题一直都有被提到,但是普遍不受大家重视,因为这个漏洞是利用是需要场景的:MITM(中间人攻击 Man-in-the-middle attack)。一般情况下MITM相对其他攻击是比较少见的,如果有良好的上网习惯如不接入不受信任的网络,那就更少可能受此类攻击了。但是近期发生的MITM据传是在核心骨干网BGP上做了改动所以劫持范围非常之广,真是防不胜防呀,你被劫持了么?</p>
<!--more-->
<h2>0x01 科普</h2>
<hr />
<p><strong>https&&ssl</strong></p>
<p>为了提高网站的安全性,一般会在比较敏感的部分页面采用https传输,比如注册、登录、控制台等。像Gmail、网银、icloud等则全部采用https传输。https/ssl主要起到两个作用:网站认证、内容加密传输和数据一致性。经CA签发的证书才起到认证可信的作用,所有有效证书均可以起到加密传输的作用。</p>
<p><strong>数字证书</strong></p>
<p>主要在互联网上的用于身份验证的用途。 安全站点在获得CA(Certificate Authority数字证书认证机构)认证后,获得一个数字证书,以此来标识其合法身份的真实性。数字证书主要分为服务器证书和客户端证书。服务器证书(SSL证书)用来进行身份验证和通信的加密,客户端证书主要用于身份验证和电子签名。找CA申请证书是要收费的。</p>
<p><strong>自签名证书</strong></p>
<p>非CA颁发的证书,通过自签名的方式得到的证书。通常Web浏览器会显示一个对话框,询问您是否希望信任一个自签名证书。这个是不用花钱的。</p>
<p><strong>中间人攻击</strong></p>
<p>是指攻击者与通讯的两端分别创建独立的联系,并交换其所收到的数据,使通讯的两端认为他们正在通过一个私密的连接与对方直接对话,但事实上整个会话都被攻击者完全控制。在中间人攻击中,攻击者可以拦截通讯双方的通话并插入新的内容。在许多情况下这是很简单的。</p>
<h2>0x02 分析</h2>
<hr />
<p>如果自己简单的实现android webview加载网页,如果直接访问可信证书的站点是可以正常显示,但是如果访问自签名的证书的站点就会显示notfound的页面。(写本文时apple.com以及apple.com.cn处于劫持状态)</p>
<p><img src="http://static.wooyun.org/drops/20141023/20141023115732webview.jpg" alt="" /></p>
<p>logcat会输出网页显示不安全的内容</p>
<pre><code>Web Console:The page displayed insecure content!
</code></pre>
<p>功能健全的手机浏览器访问自签名证书的站点会如下提醒</p>
<p><img src="http://static.wooyun.org/drops/20141023/20141023115731mUC.jpg" alt="" /></p>
<p>在PC端如果访问自签名证书的站点则会出现如下图左侧的提醒</p>
<p><img src="http://static.wooyun.org/drops/20141023/20141023115731360.jpg" alt="" /></p>
<p>为解决<code>javax.net.ssl.SSLPeerUnverifiedException: No peer certificate</code>的异常,开发者往往会采用以下的错误解决方案。如此是浏览器应用采用此类解决方案,那么风险就更大了。</p>
<p>覆盖google默认的证书检查机制</p>
<pre><code>#!java
class bv
implements X509TrustManager
{
bv(bu parambu) {}
public void checkClientTrusted(X509Certificate[] paramArrayOfX509Certificate, String paramString) {// Do nothing -> accept any certificates}
public void checkServerTrusted(X509Certificate[] paramArrayOfX509Certificate, String paramString) {// Do nothing -> accept any certificates}
public X509Certificate[] getAcceptedIssuers()
{
return null;
}
}
</code></pre>
<p>信任所有主机名</p>
<pre><code>#!java
public static HttpClient getNewHttpClient() {
try {
//获得密匙库
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustStore.load(null, null);
SSLSocketFactory sf = new SSLSocketFactoryEx(trustStore);
//信任所有主机名
sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
HttpParams params = new BasicHttpParams();
HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
HttpProtocolParams.setContentCharset(params, HTTP.UTF_8);
SchemeRegistry registry = new SchemeRegistry();
registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
registry.register(new Scheme("https", sf, 443));
ClientConnectionManager ccm = new ThreadSafeClientConnManager(params, registry);
return new DefaultHttpClient(ccm, params);
} catch (Exception e) {
return new DefaultHttpClient();
}
}
</code></pre>
<p>empty HostnameVerifier</p>
<pre><code>#!java
HostnameVerifier hv = new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
// Always return true -> Accespt any host names
return true;
}
};
</code></pre>
<p>忽略WebView证书错误继续加载</p>
<pre><code>#!java
myWebView.setWebViewClient(new WebViewClient(){
@Override
public void onReceivedError(WebView view, int errorCode,
String description, String failingUrl) {
// TODO Auto-generated method stub
super.onReceivedError(view, errorCode, description, failingUrl);
}
@Override
public void onReceivedSslError(WebView view,
SslErrorHandler handler, SslError error) {
// TODO Auto-generated method stub
handler.proceed();
}});
</code></pre>
<p>其实早在14年2月<a href="http://drops.wooyun.org/papers/959">窃听风暴: Android平台https嗅探劫持漏洞</a>文中就有提到android平台的app因为覆盖google默认的证书检查机制(X509TrustManager)之后没有对证书进行应有的安全性检查,直接接受了所有异常的https证书,不提醒用户存在安全风险,也不终止这次危险的连接。文中对证书域名检查(HostnameVerifier)部分没有细说。</p>
<p>上文有提到PC版的360浏览器访问被劫持网站居然没有证书错误提示,让人很不敢相信。加上最近android app 证书问题频发,猜想是否有可能一些手机浏览器也会有此类漏洞了。测试过程中发现360手机浏览器、和搜狗浏览器存在此风险。</p>
<p>百度和遨游轻松检测出证书异常</p>
<p><img src="http://static.wooyun.org/drops/20141023/20141023115732baidu.jpg" alt="" /></p>
<p><img src="http://static.wooyun.org/drops/20141023/20141023115731aoyou.jpg" alt="" /></p>
<p>而360和搜狗直接加载进入了被劫持的网站。</p>
<p><img src="http://static.wooyun.org/drops/20141023/20141023115731m360.jpg" alt="" /></p>
<p><img src="http://static.wooyun.org/drops/20141023/20141023115732sougou.jpg" alt="" /></p>
<p>反编译查看遨游浏览器的代码,针对证书异常做了处理</p>
<p><img src="http://static.wooyun.org/drops/20141023/20141023115732true.jpg" alt="" /></p>
<p><img src="http://static.wooyun.org/drops/20141023/20141023115731mismatch.jpg" alt="" /></p>
<p>而搜狗浏览器则是做了证书信任所有主机名不当处理</p>
<p><img src="http://static.wooyun.org/drops/20141023/20141023115732trust.jpg" alt="" /></p>
<p>关键字:checkServerTrusted、setHostnameVerifier、ALLOW_ALL_HOSTNAME_VERIFIER、X509TrustManager、onReceivedSslError</p>
<h2>0x03 对比</h2>
<hr />
<p>对主流手机浏览器进行了横向对比,测试对象包括:firefox、chrome、UC浏览器、搜狗浏览器、百度浏览器、360安全浏览器、欧鹏浏览器、遨游云浏览器、猎豹浏览器。</p>
<p>测试方法:手机访问<a href="https://example.com/">https://example.com/</a>,观察是否有安全提醒。(update:此方法已经无效.)</p>
<p>未做提醒直接加载网页:360安全浏览器、猎豹浏览器、搜狗浏览器</p>
<p>正常做出安全提醒:firefox、chrome、UC浏览器、百度浏览器、欧鹏浏览器、遨游云浏览器</p>
<h2>0x04 建议</h2>
<hr />
<p>开发者:</p>
<p>1、非浏览器app,有钱申请ca证书没钱在客户端中添加证书,切勿信任所有证书。</p>
<p>2、浏览器app,严格按照客户端校验服务器证书流程处理:</p>
<ul>
<li>查看证书是否过期 </li>
<li>CA是否可靠 </li>
<li>CA的公钥能否正确解开服务器证书的CA数字签名,即证书的签名值 </li>
<li>服务器证书上的域名是否和服务器的实际域名相匹配</li>
</ul>
<p>3、建议使用setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER)</p>
<p>一个SSLSocketFactory的example</p>
<pre><code>#!java
java public class SecureSocketFactory extends SSLSocketFactory {
private static final String LOG_TAG = "SecureSocketFactory";
private final SSLContext sslCtx;
private final X509Certificate[] acceptedIssuers;
/**
* Instantiate a new secured factory pertaining to the passed store. Be sure to initialize the
* store with the password using <a class="__cf_email__" href="/cdn-cgi/l/email-protection" data-cfemail="374c775b5e595c">[email protected]</a><script data-cfhash='f9e31' type="text/javascript">/* <![CDATA[ */!function(t,e,r,n,c,a,p){try{t=document.currentScript||function(){for(t=document.getElementsByTagName('script'),e=t.length;e--;)if(t[e].getAttribute('data-cfhash'))return t[e]}();if(t&&(c=t.previousSibling)){p=t.parentNode;if(a=c.getAttribute('data-cfemail')){for(e='',r='0x'+a.substr(0,2)|0,n=2;a.length-n;n+=2)e+='%'+('0'+('0x'+a.substr(n,2)^r).toString(16)).slice(-2);p.replaceChild(document.createTextNode(decodeURIComponent(e)),c)}p.removeChild(t)}}catch(u){}}()/* ]]> */</script> java.security.KeyStore#load(java.io.InputStream,
* char[])} method.
*
* @param store The key store holding the certificate details
* @param alias The alias of the certificate to use
*/
public SecureSocketFactory(KeyStore store, String alias)
throws
CertificateException,
NoSuchAlgorithmException,
KeyManagementException,
KeyStoreException,
UnrecoverableKeyException {
super(store);
// Loading the CA certificate from store.
final Certificate rootca = store.getCertificate(alias);
// Turn it to X509 format.
InputStream is = new ByteArrayInputStream(rootca.getEncoded());
X509Certificate x509ca = (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(is);
AsyncHttpClient.silentCloseInputStream(is);
if (null == x509ca) {
throw new CertificateException("Embedded SSL certificate has expired.");
}
// Check the CA's validity.
x509ca.checkValidity();
// Accepted CA is only the one installed in the store.
acceptedIssuers = new X509Certificate[]{x509ca};
sslCtx = SSLContext.getInstance("TLS");
sslCtx.init(
null,
new TrustManager[]{
new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
Exception error = null;
if (null == chain || 0 == chain.length) {
error = new CertificateException("Certificate chain is invalid.");
} else if (null == authType || 0 == authType.length()) {
error = new CertificateException("Authentication type is invalid.");
} else {
Log.i(LOG_TAG, "Chain includes " + chain.length + " certificates.");
try {
for (X509Certificate cert : chain) {
Log.i(LOG_TAG, "Server Certificate Details:");
Log.i(LOG_TAG, "---------------------------");
Log.i(LOG_TAG, "IssuerDN: " + cert.getIssuerDN().toString());
Log.i(LOG_TAG, "SubjectDN: " + cert.getSubjectDN().toString());
Log.i(LOG_TAG, "Serial Number: " + cert.getSerialNumber());
Log.i(LOG_TAG, "Version: " + cert.getVersion());
Log.i(LOG_TAG, "Not before: " + cert.getNotBefore().toString());
Log.i(LOG_TAG, "Not after: " + cert.getNotAfter().toString());
Log.i(LOG_TAG, "---------------------------");
// Make sure that it hasn't expired.
cert.checkValidity();
// Verify the certificate's public key chain.
cert.verify(rootca.getPublicKey());
}
} catch (InvalidKeyException e) {
error = e;
} catch (NoSuchAlgorithmException e) {
error = e;
} catch (NoSuchProviderException e) {
error = e;
} catch (SignatureException e) {
error = e;
}
}
if (null != error) {
Log.e(LOG_TAG, "Certificate error", error);
throw new CertificateException(error);
}
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return acceptedIssuers;
}
}
},
null
);
setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);
}
@Override
public Socket createSocket(Socket socket, String host, int port, boolean autoClose)
throws IOException {
injectHostname(socket, host);
Socket sslSocket = sslCtx.getSocketFactory().createSocket(socket, host, port, autoClose);
// throw an exception if the hostname does not match the certificate
getHostnameVerifier().verify(host, (SSLSocket) sslSocket);
return sslSocket;
}
@Override
public Socket createSocket() throws IOException {
return sslCtx.getSocketFactory().createSocket();
}
/**
* Pre-ICS Android had a bug resolving HTTPS addresses. This workaround fixes that bug.
*
* @param socket The socket to alter
* @param host Hostname to connect to
* @see <a href="https://code.google.com/p/android/issues/detail?id=13117#c14">https://code.google.com/p/android/issues/detail?id=13117#c14</a>
*/
private void injectHostname(Socket socket, String host) {
try {
if (Integer.valueOf(Build.VERSION.SDK) >= 4) {
Field field = InetAddress.class.getDeclaredField("hostName");
field.setAccessible(true);
field.set(socket.getInetAddress(), host);
}
} catch (Exception ignored) {
}
}
}
</code></pre>
<p>用户:使用安全性较好的app</p>
<h2>0x05 参考</h2>
<hr />
<p><a href="http://drops.wooyun.org/tips/2775" title="http://drops.wooyun.org/tips/2775">http://drops.wooyun.org/tips/2775</a></p>
<p><a href="http://drops.wooyun.org/papers/959" title="http://drops.wooyun.org/papers/959">http://drops.wooyun.org/papers/959</a></p>
<p><a href="http://developer.android.com/reference/javax/net/ssl/HttpsURLConnection.html" title="http://developer.android.com/reference/javax/net/ssl/HttpsURLConnection.html">http://developer.android.com/reference/javax/net/ssl/HttpsURLConnection.html</a></p>
<p><a href="http://developer.android.com/reference/javax/net/ssl/X509TrustManager.html">http://developer.android.com/reference/javax/net/ssl/X509TrustManager.html</a></p>
<p><a href="http://developer.android.com/training/articles/security-ssl.html">http://developer.android.com/training/articles/security-ssl.html</a></p>
<p><a href="http://developer.android.com/reference/org/apache/http/conn/ssl/SSLSocketFactory.html">http://developer.android.com/reference/org/apache/http/conn/ssl/SSLSocketFactory.html</a></p> </p>
</body>
</html>