Tag Archives: SSL

Google Play 안전하지 않은 onReceivedSslError 경고 대응하기

logo-google-play-vetor

요즘 들어 구글플레이에 등록한 앱의 코드 레벨의 검증이 수행되고 있나 봅니다. 이번에는 웹뷰를 구현할때에 필히 사용되는 WebViewClient 클래스의 onReceivedSslError 의 처리가 안전하지 않다는 경고가 뜨는 사례가 발생하고 있는데요. 현재 이부분을 정상적으로 처리하지 않은 앱의 경우 업데이트가 이루어지지 않고 있습니다. 이 오류의 대상자가 되면 받는 메일의 내용은 다음과 같습니다.

애플리케이션에 WebViewClient.onReceivedSslError 핸들러가 안전하지 않은 방식으로 구현되었습니다. 특히 해당 구현은 모든 SSL 인증서 확인 오류를 무시하여 앱을 중개인 공격에 취약하게 만듭니다. 공격자는 영향을 받은 WebView의 콘텐츠를 변경하고, 전송된 데이터(예: 로그인 사용자 인증 정보)를 읽고, 자바스크립트를 사용해 앱 내부에서 코드를 실행할 수 있습니다.

SSL 인증서를 제대로 확인하려면 서버에서 제공하는 인증서가 요구사항을 충족할 때마다 코드를 변경하여 SslErrorHandler.proceed()을(를) 호출하고 그렇지 않으면 SslErrorHandler.cancel()을(를) 호출합니다. 대상 앱 및 클래스를 포함하는 이메일 알림이 개발자 계정 주소로 전송됩니다.

최대한 빨리 취약점을 해결하고 업그레이드된 APK의 버전 번호를 올리시기 바랍니다. SSL 오류 핸들러에 대한 자세한 내용은 개발자 도움말 센터의 Google 문서를 참조하시기 바랍니다. 다른 기술 관련 질문은https://www.stackoverflow.com/questions에 게시하고 ‘android-security’ 및 ‘SslErrorHandler’ 태그를 사용하세요. 이 문제에 대한 책임이 있는 타사 라이브러리를 사용하는 경우 타사에 문의하여 이 문제를 해결하시기 바랍니다.

제대로 업그레이드되었는지 확인하려면 업데이트된 버전을 개발자 콘솔에 업로드하고 5시간 후에 다시 확인하세요. 앱이 제대로 업그레이드되지 않은 경우 경고 메시지가 표시됩니다.

이러한 특정 문제가 WebView SSL을 사용하는 일부 앱에는 영향을 미치지 않을 수 있으나 모든 보안 패치를 최신 상태로 유지하는 것이 좋습니다. 사용자를 보안 위험에 노출시키는 취약점을 보유하고 있는 앱은 Google의 악의적 행위 정책 및 개발자 배포 계약의 섹션 4.4를 위반하는 것으로 간주될 수 있습니다.

게시된 모든 앱이 개발자 배포 계약 및 개발자 프로그램 정책을 준수하는지 확인하시기 바랍니다. 궁금한 점이나 우려되는 사항이 있으면 Google Play 개발자 도움말 센터를 통해 지원팀에 문의하세요.

혹은 영어 버전의 경고 메일의 내용은 다음과 같습니다.

Security alert Your application has an unsafe implementation of the WebViewClient.onReceivedSslError handler. Specifically, the implementation ignores all SSL certificate validation errors, making your app vulnerable to man-in-the-middle attacks. An attacker could change the affected WebView’s content, read transmitted data (such as login credentials), and execute code inside the app using JavaScript.

To properly handle SSL certificate validation, change your code to invoke SslErrorHandler.proceed() whenever the certificate presented by the server meets your expectations, and invoke SslErrorHandler.cancel() otherwise. An email alert containing the affected app(s) and class(es) has been sent to your developer account address.

Please address this vulnerability as soon as possible and increment the version number of the upgraded APK. For more information about the SSL error handler, please see our documentation in the Developer Help Center. For other technical questions, you can post to https://www.stackoverflow.com/questions and use the tags “android-security” and “SslErrorHandler.” If you are using a 3rd party library that’s responsible for this, please notify the 3rd party and work with them to address the issue.

To confirm that you’ve upgraded correctly, upload the updated version to the Developer Console and check back after five hours. If the app hasn’t been correctly upgraded, we will display a warning.

Please note, while these specific issues may not affect every app that uses WebView SSL, it’s best to stay up to date on all security patches. Apps with vulnerabilities that expose users to risk of compromise may be considered dangerous products in violation of the Content Policy and section 4.4 of the Developer Distribution Agreement.

Please ensure all apps published are compliant with the Developer Distribution Agreement and Content Policy. If you have questions or concerns, please contact our support team through the Google Play Developer Help Center.

문제가 되는 코드는 WebViewClient를 구현한 다음과 같은 코드입니다. SSL 오류가 발생하면 묻지도 따지지도 않고 그것을 수용하는것을 확인하실 수 있습니다.

원래는 이곳에 이 유효하지 않은것으로 판단되는 인증서가 문제가 없는지 판단하고 문제가 없을 경우 proceed()를 호출하거나 아닌 경우 cancel()을 호출하도록 처리해 주시면 됩니다. 이 판단을 하는 부분을 어떻게 구현할지는 각 앱마다 혹은 회사마다 다르겠지만 스택오버플로우에서 좋은 예를 발견하였습니다.

invalid_ssl_certificate_theeye

위의 스크린샷은 웹브라우저에서 유효하지 않은 인증서를 가진 HTTPS 홈페이지에 진입할 때 볼 수 있는 경고입니다. 여기에 주목할 부분은 “안전하지 않음(으)로 이동”이라는 문구인데요. 안전하지 않지만 유저가 진입할지 말지를 결정할 수 있게 함으로써 브라우저의 책임을 다했다는 것일까요?

마찬가지로 Android에서도 유효하지 않은 인증서로 판단되었을 경우 유저에게 직접 물어보면 됩니다.

R.string.notification_error_ssl_cert_invalid 에는 유효하지 않은 사이트에 진입할것인지 물어보는 글이 들어가 있으면 되겠네요. 이와 같은 대응을 한 뒤 버전을 업그레이드 하여 구글 플레이에 등록하게 되면 5시간정도 뒤에 해당 경고가 사라지게 됩니다.

참고 : http://stackoverflow.com/questions/36050741/webview-avoid-security-alert-from-google-play-upon-implementation-of-onreceiveds

Google Play 안전하지 않은 TrustManager 경고 대응하기

logo-google-play-vetor

구글이 기존의 수많은 앱들의 X509TrustManager를 무력화 시켜 구현한 것에 대한 대응을 나선 것 같습니다. 현재 HTTPS 통신을 위해 SSL 관련된 코드를 구현하였지만 그 방법이 잘못된 경우에 대해 구글 플레이에서 다음과 같은 경고가 뜨고 있습니다.

앱이 Apache HTTP 클라이언트가 있는 X509TrustManager 인터페이스의 안전하지 않은 구현을 사용하고 있어 보안 취약성에 노출되었습니다. 취약성 수정 마감일 등 자세한 내용은 Google 도움말 센터의 이 도움말을 참조하세요

영문판의 경우의 경고 메시지는 다음과 같습니다.

Your app is using an unsafe implementation of the X509TrustManager interface with an Apache HTTP client, resulting in a security vulnerability. Please see this Google Help Center article for details, including the deadline for fixing the vulnerability.

구글플레이에 앱을 출시하고 5시간(저의경우에는 하루정도 뒤에 확인 가능했었습니다)정도 뒤에 구글플레이에 앱 리스트에 경고 표시가 다음과같이 표기됩니다.

google_play_warning_x509trustmanager

이는 다음과 같은 코드가 앱내에 존재할 경우 발생하는 경고이며 2016년 5월 17일부터 해당 문제가 해결되지 않은 앱들을 차단할 예정이라고 합니다.

문제가 되는 부분은 checkServerTrusted 메소드의 내부 구현이 아무것도 없는것에서 기인합니다; 정확하게는 위의 코드는 SSL의 동작을 완벽하게 무력화 시켜 인증 여부를 보장할 수 없는 누구와도 통신할 수 있게 된 상태를 야기합니다. 만약 TrustManager를 커스터마이징해서 사용해야 하는 경우  내부에 원치 상황에 대해 CertificateException 또는 IllegalArgumentException 예외를 발생시키는 코드를 구현해야 합니다.

우선 문제가 되는 Apache의 HttpClient를 사용하는 올드한 형태의 예제를 확인해 보겠습니다.

그리고 다음의 코드는 개발하신 스타일에 따라 다른 모습이겠지만 SSLSocketFactory와 X509TrustManager를 구현한 코드입니다. 위의 코드에서 MySSLSocketFactory라는 이름으로 가져다 사용하고 있습니다.

SSLSocketFactory를 상속하여 내부적으로 커스터마이징 된 X509TrustManager를 사용한 SSLContext를 통하여 소켓을 생성하도록 구현되어있습니다. 이렇게 생성한 SSL Socket은 보안에 취약한 상태입니다.

사실 정상적인 CA가 발급한 SSL 인증서로 구축된 서버라면 다음과 같은 코드만으로 통신에 문제가 없습니다.

하지만 TrustManager를 무력화 시키면서까지 HTTPS 통신을 하려 했다는건 인증서 비용등의 문제로 자체 발급한 Self-Signed 인증서를 사용중이기 때문이 아닐까 생각됩니다. 확인 할 수 없는 인증서로 인한 통신 불가 상황에서는 다음과 같은 예외가 발생합니다.

이 문제를 해결하는 가장 간단한 방법은 정상적인 CA가 발급한 인증서를 구매하여 서버에 채택하고 위쪽의 예제 코드처럼 SSLSocketFactory 를 기본 형태로 사용하는 방법입니다.

하지만 여전히 자체 발급한 인증서로도 구글의 권고안을 따라 문제를 해결할 수 있습니다. TrustManager를 재구현하면서 무력화 시킨 코드가 문제가 되기 때문에 TrustManager를 무력화 시키지 않는 방법으로도 해결을 해보도록 하겠습니다.

우선 자체 발급한 인증서는 일반적인 공인된 CA들이 인증해 주지 않는다는것이 중요하므로 자체적인 CA의 인증서를 앱내에 탑재하여 인증을 가능케 하는 방법이 있습니다. 인증서 자체 발급에 대해서는 [Self-Signed SSL로 Apache HTTPS 구현하기]를 참고해주시기 바랍니다.

앱내에 탑재될 인증서는 CA의 PEM 포맷의 인증서이며 위의 참고글에서 언급되는 rootCA.crt 파일이 여기에 해당됩니다. 이 인증서의 내용은 BASE64 인코딩이 되어있는 형태입니다. 이것을 이용하여 코드를 수정해 보겠습니다.

다른 많은 예제들은 인증서를 앱내에 파일로 포함하여 FileInputStream등으로 읽어서 쓰는 예시가 많았습니다만 파일을 앱에 포함할 정도면 텍스트를 그대로 하드코딩 해도 문제가 되지 않겠다고 생각되어 위와 같이 작성 해 보았습니다. 위에서 언급되었던 잘못된 MySSLSocketFactory의 구현은 다음과 같이 변경되었습니다.

위와 같이 수정 후 통신 테스트를 해보면 잘 되는 것을 확인할 수 있습니다. 해당 CA로 부터 발급된 모든 인증서가 적용된 서버들과 통신이 문제없이 가능합니다. 만약에 지정한 CA 인증서와 연관이 없는 인증서가 적용된 서버일 경우 다음과 같은 오류가 발생합니다.

만약에 서버의 CA인증서 존재 여부가 불분명하고 자체 발급하여 사용중인 인증서 역시 확인하기가 어렵다면 아쉬운대로 다음과 같은 명령을 사용하여 자체 발급된 인증서를 확인 하실 수 있습니다.

s_client 명령을 사용하여 통신하고자 하는 서버의 인증서를 확인 할 수 있습니다. 이는 CA 인증서는 아니지만 이렇게 조회된 인증서를 이용해도 1:1간의 통신에는 문제가 없습니다.

문제를 해결하고 Version Code를 올린 APK를 재등록하고 하루정도가 지나면 해당 경고메시지가 사리지는것을 확인하실 수 있습니다.

스크린샷 2016-02-22 오후 3.26.24

참고 :

  • https://support.google.com/faqs/answer/6346016
  • https://developer.android.com/intl/ko/training/articles/security-ssl.html#UnknownCa