Development Tip

Webview는 onReceivedSslError 구현시 Google Play의 보안 경고를 방지합니다.

yourdevel 2020. 12. 29. 08:02
반응형

Webview는 onReceivedSslError 구현시 Google Play의 보안 경고를 방지합니다.


webview에서 열리는 링크가 있습니다. 문제는 다음과 같이 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"태그를 사용할 수 있습니다 . 이 문제를 담당하는 타사 라이브러리를 사용하는 경우 타사에 알리고 협력하여 문제를 해결하십시오.

올바르게 업그레이드했는지 확인하려면 업데이트 된 버전을 Developer Console에 업로드하고 5 시간 후에 다시 확인하세요. 앱이 올바르게 업그레이드되지 않은 경우 경고가 표시됩니다.

이러한 특정 문제가 WebView SSL을 사용하는 모든 앱에 영향을 미치는 것은 아니지만 모든 보안 패치를 최신 상태로 유지하는 것이 가장 좋습니다. 사용자를 손상 위험에 노출시키는 취약성이있는 앱은 콘텐츠 정책 및 개발자 배포 계약의 섹션 4.4를 위반하는 위험한 제품으로 간주 될 수 있습니다.

게시 된 모든 앱이 개발자 배포 계약 및 콘텐츠 정책을 준수하는지 확인하세요. 질문이나 우려 사항이있는 경우 Google Play 개발자 도움말 센터를 통해 지원팀에 문의하세요.

을 제거 onReceivedSslError (handler.proceed())하면 페이지가 열리지 않습니다.

어쨌든 webview에서 페이지를 열고 보안 경고를 피할 수 있습니까?


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를 사용하는 자체 앱에서 처리해야합니다. 경고 대화 상자로 사용자에게 알리는 것은 간단한 방법입니다.


나를 위해 작동하는 수정 onReceivedSslErrorAuthorizationWebViewClient. 이 경우 handler.cancelSSL 오류가 발생하면 호출됩니다. 그러나 One Drive SSL 인증서에서는 잘 작동합니다. Android 2.3.7, Android 5.1에서 테스트되었습니다.


Google 보안 경고 : X509TrustManager 인터페이스의 안전하지 않은 구현에 따르면 Google Play는 X509TrustManager2016 년 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의 안전하지 않은 구현을 포함하는 새로운 앱 또는 업데이트의 게시를 차단합니다.

올바르게 변경했는지 확인하려면 앱의 업데이트 된 버전을 개발자 콘솔에 제출하고 5 시간 후에 다시 확인하세요. 앱이 올바르게 업그레이드되지 않은 경우 경고가 표시됩니다.

이러한 특정 문제가 TrustManager 구현을 사용하는 모든 앱에 영향을주는 것은 아니지만 SSL 인증서 유효성 검사 오류를 무시하지 않는 것이 가장 좋습니다. 사용자를 손상 위험에 노출시키는 취약성이있는 앱은 콘텐츠 정책 및 개발자 배포 계약의 섹션 4.4를 위반하는 위험한 제품으로 간주 될 수 있습니다.

...


지금까지 제안 된 솔루션은 보안 검사를 우회 할 뿐이므로 안전하지 않습니다.

내가 제안하는 것은 앱에 인증서를 포함하고 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();
        }
    }
    
  3. SslError가 발생하면 서버 인증서가 하나의 임베디드 인증서와 일치하는지 확인하십시오. 인증서를 직접 비교할 수는 없으므로 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);
        }
    
    
    });
    

사용자에게 메시지를 표시하기 전에 신뢰 저장소를 확인해야하므로 이렇게했습니다.

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();
    }
}
}

You can use SslError for show, some information about the error of this certificated, and you can write in your dialog the string of the type error.

@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);    }

In my situation:This error occured when we try to updated apk uploaded into the Google Play store,and getting SSL Error: Then i have used following code

private class MyWebViewClient extends WebViewClient {
        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            view.loadUrl(url);
            return true;
        }

        @Override
        public void onPageFinished(WebView view, String url) {
            try {
                progressDialog.dismiss();
            } catch (WindowManager.BadTokenException e) {
                e.printStackTrace();
            }
            super.onPageFinished(view, url);
        }

        @Override
        public void onReceivedSslError(WebView view, final SslErrorHandler handler, SslError error) {

 final AlertDialog.Builder builder = new AlertDialog.Builder(PayNPayWebActivity.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();
        }
    }

ReferenceURL : https://stackoverflow.com/questions/36050741/webview-avoid-security-alert-from-google-play-upon-implementation-of-onreceiveds

반응형