web-dev-qa-db-ja.com

WebViewは、onReceivedSslErrorの実装時にGoogle Playからのセキュリティ警告を回避します

ウェブビューで開くリンクがあります。問題は、次のようにonReceivedSslErrorをオーバーライドするまでオープンできないことです。

 @Override
        public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
            handler.proceed();
        }

私はGoogle Playからセキュリティ警告を受け取っています:

セキュリティ警告アプリケーションには、WebViewClient.onReceivedSslErrorハンドラーの安全でない実装があります。具体的には、実装ではすべてのSSL証明書検証エラーが無視されるため、アプリは中間者攻撃に対して脆弱になります。攻撃者は、影響を受けるWebViewのコンテンツを変更し、送信されたデータ(ログイン資格情報など)を読み取り、JavaScriptを使用してアプリ内でコードを実行する可能性があります。

SSL証明書の検証を適切に処理するには、サーバーから提示された証明書が期待どおりの場合は必ずSslErrorHandler.proceed()を呼び出すようにコードを変更し、そうでない場合はSslErrorHandler.cancel()を呼び出します。影響を受けるアプリとクラスを含むメールアラートがデベロッパーアカウントのアドレスに送信されました。

できるだけ早くこの脆弱性に対処し、アップグレードされたAPKのバージョン番号を増やしてください。 SSLエラーハンドラの詳細については、デベロッパーヘルプセンターのドキュメントをご覧ください。他の技術的な質問については、 https://www.stackoverflow.com/questions に投稿して、タグ「Android-security」および「SslErrorHandler」を使用できます。サードパーティのライブラリを使用している場合これについては、サードパーティに通知し、サードパーティと協力して問題に対処してください。

正しくアップグレードされたことを確認するには、更新されたバージョンをデベロッパーコンソールにアップロードし、5時間後にもう一度確認してください。アプリが正しくアップグレードされていない場合は、警告が表示されます。

これらの特定の問題はWebView SSLを使用するすべてのアプリに影響するわけではありませんが、すべてのセキュリティパッチを最新の状態に保つことをお勧めします。ユーザーを侵害の危険にさらす脆弱性を持つアプリは、コンテンツポリシーおよびデベロッパー販売/配布契約のセクション4.4に違反する危険な製品と見なされる場合があります。

公開されているすべてのアプリがデベロッパー販売/配布契約とコンテンツポリシーに準拠していることを確認してください。ご質問やご不明な点がある場合は、Google Playデベロッパーヘルプセンターからサポートチームにお問い合わせください。

onReceivedSslError (handler.proceed())を削除すると、ページが開きません。

とにかくウェブビューでページを開くことができ、セキュリティ警告を回避できます。

56
captaindroid

SSL証明書の検証を適切に処理するには、サーバーから提示された証明書が期待に応えるたびにSslErrorHandler.proceed()を呼び出すようにコードを変更し、そうでない場合はSslErrorHandler.cancel()を呼び出します。

電子メールで述べたように、onReceivedSslErrorは、ユーザーが通知ダイアログなどの無効な証明書を持つページに移動するのを処理する必要があります。直接進めないでください。

たとえば、アラートダイアログを追加して、ユーザーが確認し、Googleが警告を表示しなくなったようにします。


@Override
public void onReceivedSslError(WebView view, final SslErrorHandler handler, SslError error) {
    final AlertDialog.Builder builder = new AlertDialog.Builder(this);
    builder.setMessage(R.string.notification_error_ssl_cert_invalid);
    builder.setPositiveButton("continue", new DialogInterface.OnClickListener() {
        @Override
        public void onClick(DialogInterface dialog, int which) {
            handler.proceed();
        }
    });
    builder.setNegativeButton("cancel", new DialogInterface.OnClickListener() {
        @Override
        public void onClick(DialogInterface dialog, int which) {
            handler.cancel();
        }
    });
    final AlertDialog dialog = builder.create();
    dialog.show();
}

メールについて詳しく説明します。

具体的には、実装はすべてのSSL証明書検証エラーを無視するため、アプリは中間者攻撃に対して脆弱になります。

電子メールは、デフォルトの実装が重要なSSLセキュリティ問題を無視したと言います。そのため、WebViewを使用する独自のアプリで処理する必要があります。警告ダイアログでユーザーに通知するのは簡単な方法です。

97
sakiM

私のために働く修正は、onReceivedSslErrorで定義されているAuthorizationWebViewClient関数を無効にするだけです。この場合、handler.cancelはSSLエラーの場合に呼び出されます。ただし、One Drive SSL証明書では正常に機能します。 Android 2.3.7、Android 5.1でテスト済み。

6
Naveen Prince P

Google Security Alert:インターフェースX509TrustManagerの安全でない実装 によると、Google Playは2016年7月11日からX509TrustManagerをサポートしません:

こんにちは、Google Playデベロッパー、

このメールの最後にリストされているアプリは、X509TrustManagerインターフェースの安全でない実装を使用しています。具体的には、実装は、リモートホストへのHTTPS接続を確立するときにすべてのSSL証明書検証エラーを無視します。これにより、アプリが中間者攻撃に対して脆弱になります。攻撃者は、送信されたデータ(ログイン資格情報など)を読み取り、HTTPS接続で送信されたデータを変更することもできます。アカウントに20を超える影響を受けるアプリがある場合は、デベロッパーコンソールで完全なリストを確認してください。

SSL証明書の検証を適切に処理するには、カスタムX509TrustManagerインターフェイスのcheckServerTrustedメソッドのコードを変更して、サーバーによって提示された証明書が期待を満たしていない場合にCertificateExceptionまたはIllegalArgumentExceptionを発生させます。技術的な質問については、Stack Overflowに投稿して、タグ「Android-security」および「TrustManager」を使用できます。

できるだけ早くこの問題に対処し、アップグレードされたAPKのバージョン番号を増やしてください。 2016年5月17日から、Google PlayはインターフェースX509TrustManagerの安全でない実装を含む新しいアプリやアップデートの公開をブロックします。

正しい変更を行ったことを確認するには、アプリの更新バージョンをデベロッパーコンソールに送信し、5時間後にもう一度確認してください。アプリが正しくアップグレードされていない場合は、警告が表示されます。

これらの特定の問題は、TrustManagerを実装したすべてのアプリに影響するわけではありませんが、SSL証明書の検証エラーを無視しないことが最善です。ユーザーを侵害の危険にさらす脆弱性を持つアプリは、コンテンツポリシーおよびデベロッパー販売/配布契約のセクション4.4に違反する危険な製品と見なされる場合があります。

...

5
sathish baddam

これまでに提案されたソリューションはセキュリティチェックをバイパスするだけなので、安全ではありません。

証明書をアプリに埋め込むことをお勧めします。SslErrorが発生したら、サーバー証明書が埋め込まれた証明書のいずれかと一致することを確認します。

手順は次のとおりです。

  1. Webサイトから証明書を取得します。

    • Safariでサイトを開く
    • Webサイト名の近くにある南京錠のアイコンをクリックします
    • [証明書を表示]をクリックします
    • 証明書をフォルダーにドラッグアンドドロップします

https://www.markbrilman.nl/2012/03/howto-save-a-certificate-via-safari-on-mac/ を参照してください

  1. 証明書(.cerファイル)をアプリのres/rawフォルダーにコピーします

  2. コードで、loadSSLCertificates()を呼び出して証明書をロードします

    private static final int[] CERTIFICATES = {
            R.raw.my_certificate,   // you can put several certificates
    };
    private ArrayList<SslCertificate> certificates = new ArrayList<>();
    
    private void loadSSLCertificates() {
        try {
            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
            for (int rawId : CERTIFICATES) {
                InputStream inputStream = getResources().openRawResource(rawId);
                InputStream certificateInput = new BufferedInputStream(inputStream);
                try {
                    Certificate certificate = certificateFactory.generateCertificate(certificateInput);
                    if (certificate instanceof X509Certificate) {
                        X509Certificate x509Certificate = (X509Certificate) certificate;
                        SslCertificate sslCertificate = new SslCertificate(x509Certificate);
                        certificates.add(sslCertificate);
                    } else {
                        Log.w(TAG, "Wrong Certificate format: " + rawId);
                    }
                } catch (CertificateException exception) {
                    Log.w(TAG, "Cannot read certificate: " + rawId);
                } finally {
                    try {
                        certificateInput.close();
                        inputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        } catch (CertificateException e) {
            e.printStackTrace();
        }
    }
    
  3. SslErrorが発生した場合、サーバー証明書が1つの埋め込み証明書と一致することを確認してください。証明書を直接比較することはできないため、SslCertificate.saveStateを使用して証明書データをバンドルに入れてから、すべてのバンドルエントリを比較します。

    webView.setWebViewClient(new WebViewClient() {
    
        @Override
        public void onReceivedSslError(WebView view, final SslErrorHandler handler, SslError error) {
    
            // Checks Embedded certificates
            SslCertificate serverCertificate = error.getCertificate();
            Bundle serverBundle = SslCertificate.saveState(serverCertificate);
            for (SslCertificate appCertificate : certificates) {
                if (TextUtils.equals(serverCertificate.toString(), appCertificate.toString())) { // First fast check
                    Bundle appBundle = SslCertificate.saveState(appCertificate);
                    Set<String> keySet = appBundle.keySet();
                    boolean matches = true;
                    for (String key : keySet) {
                        Object serverObj = serverBundle.get(key);
                        Object appObj = appBundle.get(key);
                        if (serverObj instanceof byte[] && appObj instanceof byte[]) {     // key "x509-certificate"
                            if (!Arrays.equals((byte[]) serverObj, (byte[]) appObj)) {
                                matches = false;
                                break;
                            }
                        } else if ((serverObj != null) && !serverObj.equals(appObj)) {
                            matches = false;
                            break;
                        }
                    }
                    if (matches) {
                        handler.proceed();
                        return;
                    }
                }
            }
    
            handler.cancel();
            String message = "SSL Error " + error.getPrimaryError();
            Log.w(TAG, message);
        }
    
    
    });
    
4
Arnaud SmartFun

ユーザーにメッセージを表示する前にトラストストアを確認する必要があったため、次のようにしました。

public class MyWebViewClient extends WebViewClient {
private static final String TAG = MyWebViewClient.class.getCanonicalName();

Resources resources;
Context context;

public MyWebViewClient(Resources resources, Context context){
    this.resources = resources;
    this.context = context;
}

@Override
public void onReceivedSslError(WebView v, final SslErrorHandler handler, SslError er){
    // first check certificate with our truststore
    // if not trusted, show dialog to user
    // if trusted, proceed
    try {
        TrustManagerFactory tmf = TrustManagerUtil.getTrustManagerFactory(resources);

        for(TrustManager t: tmf.getTrustManagers()){
            if (t instanceof X509TrustManager) {

                X509TrustManager trustManager = (X509TrustManager) t;

                Bundle bundle = SslCertificate.saveState(er.getCertificate());
                X509Certificate x509Certificate;
                byte[] bytes = bundle.getByteArray("x509-certificate");
                if (bytes == null) {
                    x509Certificate = null;
                } else {
                    CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
                    Certificate cert = certFactory.generateCertificate(new ByteArrayInputStream(bytes));
                    x509Certificate = (X509Certificate) cert;
                }
                X509Certificate[] x509Certificates = new X509Certificate[1];
                x509Certificates[0] = x509Certificate;

                trustManager.checkServerTrusted(x509Certificates, "ECDH_RSA");
            }
        }
        Log.d(TAG, "Certificate from " + er.getUrl() + " is trusted.");
        handler.proceed();
    }catch(Exception e){
        Log.d(TAG, "Failed to access " + er.getUrl() + ". Error: " + er.getPrimaryError());
        final AlertDialog.Builder builder = new AlertDialog.Builder(context);
        String message = "SSL Certificate error.";
        switch (er.getPrimaryError()) {
            case SslError.SSL_UNTRUSTED:
                message = "O certificado não é confiável.";
                break;
            case SslError.SSL_EXPIRED:
                message = "O certificado expirou.";
                break;
            case SslError.SSL_IDMISMATCH:
                message = "Hostname inválido para o certificado.";
                break;
            case SslError.SSL_NOTYETVALID:
                message = "O certificado é inválido.";
                break;
        }
        message += " Deseja continuar mesmo assim?";

        builder.setTitle("Erro");
        builder.setMessage(message);
        builder.setPositiveButton("Sim", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                handler.proceed();
            }
        });
        builder.setNegativeButton("Não", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                handler.cancel();
            }
        });
        final AlertDialog dialog = builder.create();
        dialog.show();
    }
}
}
3
AmandaHLA

SslErrorをshowに使用して、この証明書のエラーに関するいくつかの情報を取得し、ダイアログにタイプエラーの文字列を書き込むことができます。

@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
    final SslErrorHandler handlerFinal;
    handlerFinal = handler;
    int mensaje ;
    switch(error.getPrimaryError()) {
        case SslError.SSL_DATE_INVALID:
            mensaje = R.string.notification_error_ssl_date_invalid;
            break;
        case SslError.SSL_EXPIRED:
            mensaje = R.string.notification_error_ssl_expired;
            break;
        case SslError.SSL_IDMISMATCH:
            mensaje = R.string.notification_error_ssl_idmismatch;
            break;
        case SslError.SSL_INVALID:
            mensaje = R.string.notification_error_ssl_invalid;
            break;
        case SslError.SSL_NOTYETVALID:
            mensaje = R.string.notification_error_ssl_not_yet_valid;
            break;
        case SslError.SSL_UNTRUSTED:
            mensaje = R.string.notification_error_ssl_untrusted;
            break;
        default:
            mensaje = R.string.notification_error_ssl_cert_invalid;
    }

    AppLogger.e("OnReceivedSslError handel.proceed()");

    View.OnClickListener acept = new View.OnClickListener() {

        @Override
        public void onClick(View v) {
            dialog.dismiss();
            handlerFinal.proceed();
        }
    };

    View.OnClickListener cancel = new View.OnClickListener() {

        @Override
        public void onClick(View v) {
            dialog.dismiss();
            handlerFinal.cancel();
        }
    };

    View.OnClickListener listeners[] = {cancel, acept};
    dialog = UiUtils.showDialog2Buttons(activity, R.string.info, mensaje, R.string.popup_custom_cancelar, R.string.popup_custom_cancelar, listeners);    }
2
Pabel