Skip to content

Commit d2007bc

Browse files
Merge pull request #9663 from joefarebrother/android-certificate-validation
Java: Add query for improper webview certificate validation
2 parents f2767eb + dd83c17 commit d2007bc

File tree

114 files changed

+4970
-2871
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

114 files changed

+4970
-2871
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/** Definitions for the web view certificate validation query */
2+
3+
import java
4+
5+
/** A method that overrides `WebViewClient.onReceivedSslError` */
6+
class OnReceivedSslErrorMethod extends Method {
7+
OnReceivedSslErrorMethod() {
8+
this.overrides*(any(Method m |
9+
m.hasQualifiedName("android.webkit", "WebViewClient", "onReceivedSslError")
10+
))
11+
}
12+
13+
/** Gets the `SslErrorHandler` argument to this method. */
14+
Parameter handlerArg() { result = this.getParameter(1) }
15+
}
16+
17+
/** A call to `SslErrorHandler.proceed` */
18+
private class SslProceedCall extends MethodAccess {
19+
SslProceedCall() {
20+
this.getMethod().hasQualifiedName("android.webkit", "SslErrorHandler", "proceed")
21+
}
22+
}
23+
24+
/** Holds if `m` trusts all certificates by calling `SslErrorHandler.proceed` unconditionally. */
25+
predicate trustsAllCerts(OnReceivedSslErrorMethod m) {
26+
exists(SslProceedCall pr | pr.getQualifier().(VarAccess).getVariable() = m.handlerArg() |
27+
pr.getBasicBlock().bbPostDominates(m.getBody().getBasicBlock())
28+
)
29+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
class Bad extends WebViewClient {
2+
// BAD: All certificates are trusted.
3+
public void onReceivedSslError (WebView view, SslErrorHandler handler, SslError error) { // $hasResult
4+
handler.proceed();
5+
}
6+
}
7+
8+
class Good extends WebViewClient {
9+
PublicKey myPubKey = ...;
10+
11+
// GOOD: Only certificates signed by a certain public key are trusted.
12+
public void onReceivedSslError (WebView view, SslErrorHandler handler, SslError error) { // $hasResult
13+
try {
14+
X509Certificate cert = error.getCertificate().getX509Certificate();
15+
cert.verify(this.myPubKey);
16+
handler.proceed();
17+
}
18+
catch (CertificateException|NoSuchAlgorithmException|InvalidKeyException|NoSuchProviderException|SignatureException e) {
19+
handler.cancel();
20+
}
21+
}
22+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
<p>
7+
If the <code>onReceivedSslError</code> method of an Android <code>WebViewClient</code> always calls <code>proceed</code> on the given <code>SslErrorHandler</code>, it trusts any certificate.
8+
This allows an attacker to perform a machine-in-the-middle attack against the application, therefore breaking any security Transport Layer Security (TLS) gives.
9+
</p>
10+
11+
<p>
12+
An attack might look like this:
13+
</p>
14+
15+
<ol>
16+
<li>The vulnerable application connects to <code>https://example.com</code>.</li>
17+
<li>The attacker intercepts this connection and presents a valid, self-signed certificate for <code>https://example.com</code>.</li>
18+
<li>The vulnerable application calls the <code>onReceivedSslError</code> method to check whether it should trust the certificate.</li>
19+
<li>The <code>onReceivedSslError</code> method of your <code>WebViewClient</code> calls <code>SslErrorHandler.proceed</code>.</li>
20+
<li>The vulnerable application accepts the certificate and proceeds with the connection since your <code>WevViewClient</code> trusted it by proceeding.</li>
21+
<li>The attacker can now read the data your application sends to <code>https://example.com</code> and/or alter its replies while the application thinks the connection is secure.</li>
22+
</ol>
23+
</overview>
24+
25+
<recommendation>
26+
<p>
27+
Do not use a call <code>SslerrorHandler.proceed</code> unconditionally.
28+
If you have to use a self-signed certificate, only accept that certificate, not all certificates.
29+
</p>
30+
31+
</recommendation>
32+
33+
<example>
34+
<p>
35+
In the first (bad) example, the <code>WebViewClient</code> trusts all certificates by always calling <code>SslErrorHandler.proceed</code>.
36+
In the second (good) example, only certificates signed by a certain public key are accepted.
37+
</p>
38+
<sample src="ImproperWebViewCertificateValidation.java" />
39+
</example>
40+
41+
<references>
42+
<li>
43+
<a href="https://developer.android.com/reference/android/webkit/WebViewClient?hl=en#onReceivedSslError(android.webkit.WebView,%20android.webkit.SslErrorHandler,%20android.net.http.SslError)">WebViewClient.onReceivedSslError documentation</a>.
44+
</li>
45+
</references>
46+
</qhelp>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* @name Android `WebView` that accepts all certificates
3+
* @description Trusting all certificates allows an attacker to perform a machine-in-the-middle attack.
4+
* @kind problem
5+
* @problem.severity error
6+
* @security-severity 7.5
7+
* @precision high
8+
* @id java/improper-webview-certificate-validation
9+
* @tags security
10+
* external/cwe/cwe-295
11+
*/
12+
13+
import java
14+
import semmle.code.java.security.AndroidWebViewCertificateValidationQuery
15+
16+
from OnReceivedSslErrorMethod m
17+
where trustsAllCerts(m)
18+
select m, "This handler accepts all SSL certificates."
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: newQuery
3+
---
4+
* A new query "Android `WebView` that accepts all certificates" (`java/improper-webview-certificate-validation`) has been added. This query finds implementations of `WebViewClient`s that accept all certificates in the case of an SSL error.
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import android.webkit.WebViewClient;
2+
import android.webkit.WebView;
3+
import android.webkit.SslErrorHandler;
4+
import android.net.http.SslError;
5+
import android.net.http.SslCertificate;
6+
import android.app.AlertDialog;
7+
import android.content.DialogInterface;
8+
import android.app.Activity;
9+
10+
class Test {
11+
class A extends WebViewClient {
12+
public void onReceivedSslError (WebView view, SslErrorHandler handler, SslError error) { // $hasResult
13+
handler.proceed();
14+
}
15+
}
16+
17+
interface Validator {
18+
boolean isValid(SslCertificate cert);
19+
}
20+
21+
class B extends WebViewClient {
22+
Validator v;
23+
24+
public void onReceivedSslError (WebView view, SslErrorHandler handler, SslError error) {
25+
if (this.v.isValid(error.getCertificate())) {
26+
handler.proceed();
27+
}
28+
else {
29+
handler.cancel();
30+
}
31+
}
32+
}
33+
34+
class C extends WebViewClient {
35+
Activity activity;
36+
37+
public void onReceivedSslError (WebView view, SslErrorHandler handler, SslError error) {
38+
new AlertDialog.Builder(activity).
39+
setTitle("SSL error").
40+
setMessage("SSL error. Connect anyway?").
41+
setPositiveButton("Yes", new DialogInterface.OnClickListener() {
42+
@Override
43+
public void onClick(DialogInterface dialog, int which) {
44+
handler.proceed();
45+
}
46+
}).setNegativeButton("No", new DialogInterface.OnClickListener() {
47+
@Override
48+
public void onClick(DialogInterface dialog, int which) {
49+
handler.cancel();
50+
}
51+
}).show();
52+
}
53+
}
54+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
// semmle-extractor-options: --javac-args -cp ${testdir}/../../../../stubs/google-android-9.0.0

java/ql/test/query-tests/security/CWE-295/ImproperWebVeiwCertificateValidation/test.expected

Whitespace-only changes.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import java
2+
import semmle.code.java.security.AndroidWebViewCertificateValidationQuery
3+
import TestUtilities.InlineExpectationsTest
4+
5+
class WebViewTest extends InlineExpectationsTest {
6+
WebViewTest() { this = "WebViewTest" }
7+
8+
override string getARelevantTag() { result = "hasResult" }
9+
10+
override predicate hasActualResult(Location location, string element, string tag, string value) {
11+
exists(OnReceivedSslErrorMethod m |
12+
trustsAllCerts(m) and
13+
location = m.getLocation() and
14+
element = m.toString() and
15+
tag = "hasResult" and
16+
value = ""
17+
)
18+
}
19+
}

java/ql/test/stubs/google-android-9.0.0/android/app/ActionBar.java

Lines changed: 132 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)