在实施onReceivedSslError时,Webview会避免谷歌播放的安全警报

cap*_*oid 58 android android-webview google-play android-security sslerrorhandler

我有一个链接将在webview中打开.问题是它不能打开,直到我覆盖onReceivedSslError,如下所示:

 @Override
        public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
            handler.proceed();
        }
Run Code Online (Sandbox Code Playgroud)

我从谷歌游戏获得安全警报说:

安全警报您的应用程序具有WebViewClient.onReceivedSslError处理程序的不安全实现.具体来说,该实现忽略了所有SSL证书验证错误,使您的应用程序容易受到中间人攻击.攻击者可以使用JavaScript更改受影响的WebView内容,读取传输的数据(例如登录凭据),以及在应用程序内部执行代码.

要正确处理SSL证书验证,只要服务器提供的证书符合您的期望,就更改代码以调用SslErrorHandler.proceed(),否则调用SslErrorHandler.cancel().包含受影响的应用和类别的电子邮件警报已发送到您的开发者帐户地址.

请尽快解决此漏洞并增加已升级APK的版本号.有关SSL错误处理程序的更多信息,请参阅开发人员帮助中心中的文档.对于其他技术问题,您可以发布到https://www.stackoverflow.com/questions并使用标签"android-security"和"SslErrorHandler."如果您使用的是负责此操作的第三方库,请通知第三方并与他们合作解决问题.

要确认您已正确升级,请将更新后的版本上传到开发者控制台,并在五小时后再回来查看.如果应用尚未正确升级,我们将显示警告.

请注意,虽然这些特定问题可能不会影响使用WebView SSL的每个应用程序,但最好保持所有安全补丁的最新状态.如果应用程序存在漏洞,使用户面临泄密风险,则可能会被视为违反内容政策和开发者分发协议第4.4节的危险产品.

请确保发布的所有应用均符合开发者分发协议和内容政策.如果您有任何疑问或疑虑,请通过Google Play开发者帮助中心与我们的支持小组联系.

如果我删除onReceivedSslError (handler.proceed()),则页面将无法打开.

无论如何我可以在webview中打开页面并避免安全警报.

sak*_*kiM 99

要正确处理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();
}
Run Code Online (Sandbox Code Playgroud)

更多关于电子邮件的解释.

具体来说,该实现忽略了所有SSL证书验证错误,使您的应用程序容易受到中间人攻击.

电子邮件说默认工具忽略了一个重要的SSL安全问题.所以我们需要在我们自己的使用WebView的应用程序中处理它.使用警报对话框通知用户是一种简单的方法.

  • 电子邮件实际上并没有说我们应该指定用户任何事情,我错了吗? (3认同)
  • 就我而言,我像@sakiM 所说的那样实现了这一点。但谷歌仍然拒绝了。有人给我其他解决方案吗? (2认同)
  • 有没有办法在没有警报对话框的情况下解决这个问题,是否可以从服务器添加用于 Web 视图的证书。 (2认同)

Nav*_*e P 6

对我onReceivedSslError有用的修复只是在中定义的禁用功能AuthorizationWebViewClient.在这种情况下,handler.cancel将在SSL错误的情况下调用.但是,它适用于One Drive SSL证书.在Android 2.3.7,Android 5.1上测试过.


小智 6

我需要在向用户显示任何消息之前检查我们的信任库,所以我这样做了:

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();
    }
}
}
Run Code Online (Sandbox Code Playgroud)


Arn*_*Fun 6

目前提出的解决方案只是绕过安全检查,所以它们并不安全。

我的建议是将证书嵌入应用程序中,当发生 SslError 时,检查服务器证书是否与嵌入的证书之一匹配。

所以这里是步骤:

  1. 从网站检索证书。

    • 在 Safari 上打开网站
    • 单击网站名称附近的挂锁图标
    • 点击显示证书
    • 将证书拖放到文件夹中

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();
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)
  3. 发生 SslError 时,请检查服务器证书是否与一个嵌入式证书匹配。注意,不能直接比较证书,所以我使用 SslCertificate.saveState 将证书数据放入一个 Bundle,然后我比较所有的 bundle 条目。

    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);
        }
    
    
    });
    
    Run Code Online (Sandbox Code Playgroud)


Uda*_*tne 6

我遇到了同样的问题,并尝试了上述所有建议,如下所示。

  1. 通过让用户有机会决定 handler.proceed() 来实现 onReceivedSslError(); 或处理程序.cancel(); 当发生 SSL 错误时
  2. 实现 onReceivedSslError() 来调用 handler.cancel(); 每当发生 SSL 问题而不考虑用户的决定时。
  3. 除了检查 error.getPrimaryError() 并让用户决定 handler.proceed() 之外,还实现 onReceivedSslError() 以在本地验证 SSL 证书;或处理程序.cancel(); 仅当 SSL 证书有效时。如果不是,只需调用 handler.cancel();
  4. 删除 onReceivedSslError() 的实现并让 Android 默认行为发生。

即使在尝试了上述所有尝试之后,Google Play 仍不断发送相同的通知邮件,提及相同的错误和旧的 APK 版本(即使在上述所有尝试中,我们在 Gradle 中更改了版本代码和版本名称)

我们遇到了巨大的麻烦,通过邮件联系了 Google 支持并询问

“我们正在上传更高版本的 APK,但审核结果显示同样的错误,提到旧的有缺陷的 APK 版本。这是什么原因?”

几天后,谷歌支持回复了我们的请求,如下。

请注意,您必须完全替换生产轨道中的版本 12。这意味着您必须全面推出更高版本才能停用版本 12

在游戏控制台或任何论坛中从未找到或提及突出显示的点。

根据经典 Google Play 视图中的指南,我们检查了生产轨迹,发现既有 bug 版本,也有最新的 bug 修复版本,但 bug 修复版本的推出百分比为 20%。因此,将其全面推出,然后有缺陷的版本从生产轨道中消失。经过24小时的审核时间版本已经回来了。

注意:当我们遇到这个问题时,Google 刚刚迁移到其 Play 控制台的新 UI 版本,并且错过了之前 UI 版本或经典视图中的一些视图。由于我们使用的是最新视图,因此我们无法注意到发生了什么。简单来说,谷歌审查了之前有问题的 APK 版本,因为新版本尚未全面推出。


小智 5

根据Google Security Alert:界面X509TrustManager的不安全实施,Google Play将不会X509TrustManager从2016年7月11日起支持:

您好Google Play开发者,

您在此电子邮件末尾列出的应用使用了界面X509TrustManager的不安全实现.具体而言,在与远程主机建立HTTPS连接时,实现会忽略所有SSL证书验证错误,从而使您的应用容易受到中间人攻击.攻击者可以读取传输的数据(例如登录凭据),甚至可以更改HTTPS连接上传输的数据.如果您的帐户中有超过20个受影响的应用,请查看开发者控制台以获取完整列表.

要正确处理SSL证书验证,请在自定义X509TrustManager接口的checkServerTrusted方法中更改代码,以便在服务器提供的证书不符合您的期望时引发CertificateException或IllegalArgumentException.对于技术问题,您可以发布到Stack Overflow并使用标签"android-security"和"TrustManager".

请尽快解决此问题,并增加已升级的APK的版本号.从2016年5月17日开始,Google Play将阻止发布包含界面X509TrustManager的不安全实现的任何新应用或更新.

要确认您已进行了正确的更改,请将应用的更新版本提交给开发者控制台,并在五小时后再回来查看.如果应用尚未正确升级,我们将显示警告.

虽然这些特定问题可能不会影响使用TrustManager实现的每个应用程序,但最好不要忽略SSL证书验证错误.如果应用程序存在漏洞,使用户面临泄密风险,则可能会被视为违反内容政策和开发者分发协议第4.4节的危险产品.

...