デブサミ2015...
TRANSCRIPT
事例から学ぶAndroidアプリのセキュアコーディング「SSL/TLS証明書検証の現状と対策」
JPCERT/CC 情報流通対策グループ 脆弱性解析チーム 熊谷 裕志 ([email protected])
Copyright©2015 JPCERT/CC All rights reserved.
自己紹介
1
熊谷 裕志 (くまがい ひろし) —情報流通対策グループ 解析チーム リードアナリスト
—2011年4月からJPCERT/CCで脆弱性情報の分析やセキュアコーディングの普及活動に携わる
Copyright©2015 JPCERT/CC All rights reserved.
目次
話題になったニュース 現状 —約5000件のアプリを簡易調査 —アプリをピックアップしてその実装を見てみる 対策方法 —修正されたアプリの実装を見てみる アプリの解析 —ツール紹介 —デモ 参考情報
2
Copyright©2015 JPCERT/CC All rights reserved.
9月 CERT/CC Androidアプリの脆弱性を調査公表
4
多くのAndroidアプリではSSL/TLSサーバ証明書を適切に検証していないことがわかった —https://www.cert.org/blogs/certcc/post.cfm?EntryID=204
検証したアプリのリストが公開されており、現在も更新されている —https://docs.google.com/spreadsheets/d/1t5GXwjw82Syun
ALVJb2w0zi3FoLRIkfGPc7AMjRF0r4/edit?usp=sharing
Copyright©2015 JPCERT/CC All rights reserved.
1月から12月までの間 国内では9件の脆弱性情報が公開
2014/10/23 JVN#27388160: — Android 版 「スマ保」における SSL/TLS サーバ証明書の検証不備の脆弱性
2014/09/25 JVN#48270605: — Yahoo!ボックス(Android版) における SSL サーバ証明書の検証不備の脆弱性
2014/09/22 JVN#04560253: — Android 版アプリ「ゆこゆこ」における SSL サーバ証明書の検証不備の脆弱性
2014/08/29 JVN#17637243: — Android 版アプリ Kindle における SSL サーバ証明書の検証不備の脆弱性
2014/08/14 JVN#27702217: — Android 版 Ameba における SSL サーバ証明書の検証不備の脆弱性
2014/07/30 JVN#72950786: — Android 版 Outlook.com における SSL サーバ証明書の検証不備の脆弱性
2014/06/18 JVN#10603428: — Android 版アプリ「JR東日本アプリ」における SSL サーバ証明書の検証不備の脆弱性
2014/03/17 JVN#16263849: — Android 版アプリ「出前館」における SSL サーバ証明書の検証不備の脆弱性
2014/02/26 JVN#48810179: — Android 版アプリ「デニーズ」における SSL サーバ証明書の検証不備の脆弱性
5
Copyright©2015 JPCERT/CC All rights reserved.
調査内容 日本のGooglePlayで公開されている5307件のアプリを簡易調査
—2015/01/27時点 —次のカテゴリの無料トップからアプリをピックアップ
ビジネス、通信、ファイナンス、健康&フィットネス、ライフスタイル、医療、仕事効率化、ショッピング、ソーシャルネットワーク、ツール、交通
7
Copyright©2015 JPCERT/CC All rights reserved.
調査内容 証明書検証の実装に問題がある可能性の判断
—X509TrustManagerをチェック
checkServerTrustedメソッド等を空にしているか —HostnameVerifierをチェック
verifyメソッドを常にtrueにしているか —ALLOW_ALL_HOSTNAME_VERIFIERをチェック
8
JSSEC セキュアコーディングガイド 5.4.3.3. 証明書検証を無効化する危険なコード (https://www.jssec.org/dl/android_securecoding.pdf)
Copyright©2015 JPCERT/CC All rights reserved.
調査結果 1930件のアプリに証明書検証不備の可能性 —ただし、あくまで簡易調査
証明書検証を無効にしているコードを含んでいるという意味 全てのアプリでこのコードが生きているかどうかは現時点で未調査
10
Copyright©2015 JPCERT/CC All rights reserved.
カテゴリ別割合
11
0% 10% 20% 30% 40% 50% 60% 70% 80% 90%
100%
各カテゴリにおける問題のコードを含むアプリの割合
Copyright©2015 JPCERT/CC All rights reserved.
アプリその1:実装その1 独自実装のTrustManagerを使用している
12
package xxxx.xxxx.xxxx.xxxx.xxxx.xxxx; import java.security.cert.X509Certificate; import javax.net.ssl.X509TrustManager; class g implements X509TrustManager { g(f arg1) { this.a = arg1; super(); } public void checkClientTrusted(X509Certificate[] arg1, String arg2) { } public void checkServerTrusted(X509Certificate[] arg1, String arg2) { } public X509Certificate[] getAcceptedIssuers() { return null; } }
Copyright©2015 JPCERT/CC All rights reserved.
アプリその2:実装その2
SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIERを使用して、どんなホスト名でも受け付けるようにしている
13
h.a = new HttpPost(v0); h.a.setHeader("Content-Type", "image/jpeg"); h.a.setHeader("Accept-Language", "ja-jp"); h.a.setHeader("Accept-Encoding", "gzip, deflate"); h.a.setHeader("Proxy-Connection", "keep-alive"); h.a.setHeader("User-Agent", v1); KeyStore v0_1 = KeyStore.getInstance(KeyStore.getDefaultType()); v0_1.load(v2, ((char[])v2)); f v1_1 = new f(v0_1); ((SSLSocketFactory)v1_1).setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); SchemeRegistry v0_2 = new SchemeRegistry(); v0_2.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80)); v0_2.register(new Scheme("https", ((SocketFactory)v1_1), 443));
Copyright©2015 JPCERT/CC All rights reserved.
アプリその3:実装その1 このアプリも先ほど同様、独自実装のTrustManagerを使用している
14
package xxxx.xxxx.xxxx.xxxx.xxxx; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import javax.net.ssl.X509TrustManager; class xxxx.xxxx.xxxx.xxxx.xxxx.MySSLSocketFactory$1 implements X509TrustManager { xxxx.xxxx.xxxx.xxxx.xxxx.MySSLSocketFactory$1(MySSLSocketFactory arg1) { MySSLSocketFactory.this = arg1; super(); } public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { } public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { } public X509Certificate[] getAcceptedIssuers() { return null; } }
Copyright©2015 JPCERT/CC All rights reserved.
アプリその4:実装その2
これも先ほどのアプリと同様にSSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIERを使用してどんなホスト名でも受け付けるようにしている
15
public HttpClient(StringBuilder _sb) throws Exception { super(); this.httpClient = new DefaultHttpClient(); this.httpContext = new BasicHttpContext(); this.stringBuilder = _sb; KeyStore v2 = KeyStore.getInstance(KeyStore.getDefaultType()); v2.load(null, null); MySSLSocketFactory v1 = new MySSLSocketFactory(v2); ((SSLSocketFactory)v1).setHostnameVerifier( SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); this.httpClient.getConnectionManager().getSchemeRegistry().register( new Scheme("https", ((SocketFactory) v1), 443));
Copyright©2015 JPCERT/CC All rights reserved.
その他
WebViewにおいて、SSL通信のエラーを無視してそのまま処理を実行するようにしている
16
mWebView.setWebViewClient(new WebViewClient() { @Override public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { handler.proceed(); } });
Copyright©2015 JPCERT/CC All rights reserved.
SSL/TLSサーバ証明書の検証をしていないと
中間者攻撃によって、HTTPS通信の内容を盗聴や改ざんされてしまう
18
Copyright©2015 JPCERT/CC All rights reserved.
修正済みアプリ その1 某ファイナンス系アプリA —https://play.google.com/store/apps/details?id=xxx.xxxxx.xx
xxxxx.xxxxxx すでに修正済み 古いバージョンではSSL/TLSサーバ証明書を検証していなかった
21
Copyright©2015 JPCERT/CC All rights reserved. 22
xxx.xxxx.xxxxx.xxxx.ApiRequest$1 v12 = new X509TrustManager() { public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { } public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { } public X509Certificate[] getAcceptedIssuers() { return null; } }; try { SSLContext v18 = SSLContext.getInstance("TLS"); v18.init(null, new TrustManager[]{v12}, null); ((HttpsURLConnection)v8).setSSLSocketFactory(v18.getSocketFactory()); ((HttpsURLConnection)v8).setSSLSocketFactory(v18.getSocketFactory()); ((HttpsURLConnection)v8).setHostnameVerifier(new HostnameVerifier() { public boolean verify(String string, SSLSession ssls) { return 1; } }); ((HttpURLConnection)v8).setRequestMethod("POST"); ((HttpURLConnection)v8).setDoOutput(true); ((HttpURLConnection)v8).setRequestProperty("Referer", ”xxx.xxx.xxxx.xxx"); ((HttpURLConnection)v8).setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); ((HttpURLConnection)v8).setRequestProperty("Content-Length", v16.length()); ((HttpURLConnection)v8).setRequestProperty("User-Agent", this.mUserAgent); ((HttpURLConnection)v8).setRequestProperty("X-Requested-With", "xmlHttpRequest"); ((HttpURLConnection)v8).setInstanceFollowRedirects(false); ((HttpURLConnection)v8).connect();
修正済みアプリ その1:修正前のコード
Copyright©2015 JPCERT/CC All rights reserved.
修正済みアプリ その1:修正後のコード
独自実装のTrustManagerやHostnameVerifierがなくなっている
23
try { URL v14 = new URL(url.split("¥¥?")[0]); String v12 = new URL(url).getQuery(); v5 = v14.openConnection(); ((HttpURLConnection)v5).setRequestMethod("POST"); ((HttpURLConnection)v5).setDoOutput(true); ((HttpURLConnection)v5).setRequestProperty("Referer", ”xxx.xxx.xxxx.xxx"); ((HttpURLConnection)v5).setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); ((HttpURLConnection)v5).setRequestProperty("Content-Length", v12.length()); ((HttpURLConnection)v5).setRequestProperty("User-Agent", this.mUserAgent); ((HttpURLConnection)v5).setRequestProperty("X-Requested-With", "xmlHttpRequest"); ((HttpURLConnection)v5).setInstanceFollowRedirects(false); ((HttpURLConnection)v5).connect();
Copyright©2015 JPCERT/CC All rights reserved.
修正済みアプリ その2 某ファイナンス系アプリB
https://play.google.com/store/apps/details?id=xx.xxx.xxxx.xxxxx すでに修正済み
24
Copyright©2015 JPCERT/CC All rights reserved.
修正済みアプリ その2:修正前のコード 1/3
SSLSocketFactoryを呼び出している
25
public class a extends b { public a(Context arg1, h arg2, URI arg3, List arg4) { super(arg1, arg2, arg3, arg4); } protected Void a(Void[] arg9) { HttpResponse v1; Void v2 = null; HttpPost v0 = new HttpPost(this.b); HttpClient v3 = k.a(); try { if(this.h > 0) { v0.getParams().setIntParameter("http.connection.timeout", this.h); v0.getParams().setIntParameter("http.socket.timeout", this.h); }
Copyright©2015 JPCERT/CC All rights reserved.
修正済みアプリ その2:修正前のコード 2/3
SSLSocketFactoryを継承して独自のTrustManagerを使用している
26
public class k { class a extends SSLSocketFactory { private SSLContext a; public a(KeyStore arg6) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException { super(arg6); this.a = SSLContext.getInstance("TLS"); this.a.init(null, new TrustManager[]{new l(this)}, null);
Copyright©2015 JPCERT/CC All rights reserved.
修正済みアプリ その2:修正前のコード 3/3
独自実装のTrustManagerを実装している
27
class l implements X509TrustManager { l(a arg1) { this.a = arg1; super(); } public void checkClientTrusted(X509Certificate[] arg1, String arg2) throws CertificateException { } public void checkServerTrusted(X509Certificate[] arg1, String arg2) throws CertificateException { } public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } }
Copyright©2015 JPCERT/CC All rights reserved.
修正済みアプリ その2:修正後のコード
独自実装のTrustManagerは使用しなくなっている
28
private Void c() { HttpResponse v2_1; HttpResponse v1; Void v2 = null; HttpPost v0 = new HttpPost(this.b); DefaultHttpClient v3 = new DefaultHttpClient(); try { if(this.h > 0) { v0.getParams().setIntParameter("http.connection.timeout", this.h); v0.getParams().setIntParameter("http.socket.timeout", this.h); }
Copyright©2015 JPCERT/CC All rights reserved.
まとめ SSL/TLSサーバ証明書の検証はバイパスしない デバッグのためなら、リリース時は元に戻す 不用意にサンプルをコピペしない —Exceptionは無視しない —TrustManagerやHostnameVerifierを独自実装するなら不用意なバイバスはしない
29
Copyright©2015 JPCERT/CC All rights reserved.
ツール紹介:mitmproxy mitmproxy —http://mitmproxy.org/
インストール方法は —Windows環境の場合、別途Pythonが必要
https://www.python.org/
31
pip install mitmproxy
Copyright©2015 JPCERT/CC All rights reserved.
ツール紹介:Fiddler Fiddler —http://www.telerik.com/fiddler
Androidアプリの通信をキャプチャするために設定を変更する —[Tools] > [Fiddler Options]
[HTTPS] > [Decrypt HTTPS traffic] [Connections] > [Allow remote computers to connect]
32
Copyright©2015 JPCERT/CC All rights reserved.
ツール紹介:apktool apktool —https://code.google.com/p/android-apktool/ —apkファイルを解析するためのツール
—機能
リソースファイルのデコード apkファイルの再構築 その他…
33
Copyright©2015 JPCERT/CC All rights reserved.
ツール紹介:dex2jar dex2jar —https://code.google.com/p/dex2jar/ —DEXファイルをJavaクラスファイルに変換するツール
34
Copyright©2015 JPCERT/CC All rights reserved.
ツール紹介:JD-GUI JD-GUI —http://jd.benow.ca/ —Javaクラスファイルをデコンパイルするツール
35
Copyright©2015 JPCERT/CC All rights reserved.
MalloDroid AndroidアプリのSSL/TLSサーバ証明書検証の実装に問題があるかどうかチェックしてくれるツール —https://github.com/sfahl/mallodroid
必要なもの —androguard
https://github.com/androguard/androguard インストールに必要なもの
—https://code.google.com/p/androguard/wiki/Installation
36
Copyright©2015 JPCERT/CC All rights reserved.
大事なことなので、もう一度 約36%のアプリに問題がある可能性 SSL/TLSサーバ証明書の検証はバイパスしない デバッグのためなら、リリース時は元に戻す 不用意にサンプルをコピペしない —Exceptionは無視しない —TrustManagerやHostnameVerifierを独自実装するなら不用意なバイバスはしない
38
Copyright©2015 JPCERT/CC All rights reserved.
Androidアプリのセキュア設計・セキュアコーディングガイド —https://www.jssec.org/report/securecoding.html
Android Pinning by Moxie Marlinspike —https://github.com/moxie0/AndroidPinning
Finding Android SSL Vulnerabilities with CERT Tapioca —https://www.cert.org/blogs/certcc/post.cfm?EntryID=204
Android apps that fail to validate SSL —https://docs.google.com/spreadsheets/d/1t5GXwjw82Syun
ALVJb2w0zi3FoLRIkfGPc7AMjRF0r4/edit?usp=sharing
40