Skip to content

Java: An experimental query for ignored hostname verification #6443

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Feb 14, 2022

Conversation

artem-smotrakov
Copy link
Contributor

The method HostnameVerifier.verify() checks that the hostname from the server's certificate matches the server hostname after an HTTPS connection is established. The method returns true if the hostname is acceptable and false otherwise. The contract of the method does not require it to throw an exception if the verification failed. Therefore, a caller has to check the result and drop the connection if the hostname verification failed.

I'd like to add an experimental query that checks whether the result of hostname verification is checked or not. Currently, the query requires en exception to be thrown based on the result of HostnameVerifier.verify(). This makes the query pretty strict. It may result in false positives when an application checks the result of hostname verification but reports a failure without throwing an exception right away. The requirement may be softened. For example, the query can only check that the returned value is evaluated in an if-block. Looking forward to your feedback.

The query detects CVE-2019-11777 in the Eclipse Paho Java client library version 1.2.0.

@intrigus-lgtm
Copy link
Contributor

Wouldn't it make sense to filter out cases where the "ignored" value is returned from/inside another HostnameVerifier?
https://lgtm.com/query/1603409360070422690/

@kevinbackhouse
Copy link
Contributor

@artem-smotrakov: there are currently quite a few false positives because the ignored predicate isn't working quite right. Here are some examples on alibaba/nacos: https://lgtm.com/query/8750717796235890424/

@artem-smotrakov artem-smotrakov force-pushed the ignored-hostname-verifier branch from 5978475 to 60a0021 Compare November 7, 2021 16:36
@artem-smotrakov
Copy link
Contributor Author

I've addressed your comments @Marcono1234 . Thank you!

@artem-smotrakov
Copy link
Contributor Author

Wouldn't it make sense to filter out cases where the "ignored" value is returned from/inside another HostnameVerifier? https://lgtm.com/query/1603409360070422690/

@intrigus-lgtm Makes sense to me. I've updated the query to ignore calls wrapped by another hostname verifier. Thank you for the suggestion!

@artem-smotrakov
Copy link
Contributor Author

@artem-smotrakov: there are currently quite a few false positives because the ignored predicate isn't working quite right. Here are some examples on alibaba/nacos: https://lgtm.com/query/8750717796235890424/

@kevinbackhouse I've addressed @intrigus-lgtm's comment above, and that fixes the false-positive in SelfHostnameVerifier. I am not sure that the query should be updated to address the false-positives in tests. Please let me know what you think.

@artem-smotrakov
Copy link
Contributor Author

Thank you all for the review, and sorry for the delay in addressing your comments!

@kevinbackhouse
Copy link
Contributor

@artem-smotrakov: I'm sorry for not looking at this until now. The number of false positives has gone down, but I'm still seeing a lot of similar results to before. For example: https://lgtm.com/query/6043668221389765828/

The vast majority of the false positives involve Assert.assertTrue or Assert.assertFalse, so just fixing those would take care of about 90% of the false positives. The others are slightly more subtle, like these:

https://lgtm.com/query/6215280566352377624/
https://lgtm.com/query/1673043917935932127/

@artem-smotrakov
Copy link
Contributor Author

artem-smotrakov commented Jan 9, 2022

@artem-smotrakov: I'm sorry for not looking at this until now.

@kevinbackhouse No problem. I am also replying pretty slow, unfurtunately.

The number of false positives has gone down, but I'm still seeing a lot of similar results to before. For example: https://lgtm.com/query/6043668221389765828/
The vast majority of the false positives involve Assert.assertTrue or Assert.assertFalse, so just fixing those would take care of about 90% of the false positives.

Those are alerts in tests. I suppose the database should have been built without the test code. Do you think it makes sense to make the query aware of testing/mocking frameworks?

The others are slightly more subtle, like these:
https://lgtm.com/query/6215280566352377624/

That is a good one. I've fixed it.

https://lgtm.com/query/1673043917935932127/

I am looking into this one.

@artem-smotrakov artem-smotrakov force-pushed the ignored-hostname-verifier branch from 84b5c47 to 825fe17 Compare January 16, 2022 18:56
@artem-smotrakov
Copy link
Contributor Author

https://lgtm.com/query/1673043917935932127/

@kevinbackhouse I've fixed this one. I am still now sure that it is right to make the query aware of Assert.assertTrue and Assert.assertFalse. I think test code should not be included in the database.

@kevinbackhouse
Copy link
Contributor

@artem-smotrakov: I'm afraid there's still a very large number of results, all of which look like false positives to me. I think it might be better to change this query so that it only warns when the verify() call is completely ignored. I.e. do not warn if the result is used in an expression, assigned to a variable, or passed as an argument to a method call. CVE-2019-11777 was caused by the result being completely ignored, so I think that would be the best pattern to look for.

@artem-smotrakov
Copy link
Contributor Author

artem-smotrakov commented Feb 6, 2022

I'm afraid there's still a very large number of results, all of which look like false positives to me.
I think it might be better to change this query so that it only warns when the verify() call is completely ignored. I.e. do not warn if the result is used in an expression, assigned to a variable, or passed as an argument to a method call.

GHSA-63qc-p2x4-9fgf was caused by the result being completely ignored, so I think that would be the best pattern to look for.

Hi @kevinbackhouse Sure, we can trade off TPs for FPs. Let me see what I can do. If you could share those results that you think might be FPs, that would be great.

@artem-smotrakov
Copy link
Contributor Author

@kevinbackhouse I've updated the query to check if the result of hostname verification is used in a general expression, if-statement and a method call. Technically, the result may be used in other statement like

while (!result) {
    throw new Exception("Failed");
}

but they look pretty exotic, so I am not sure if we want to cover them.

Please let me know what you think.

Copy link
Contributor

@atorralba atorralba left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added some inline comments, but otherwise LGTM.

@@ -0,0 +1,8 @@
SSLSocket socket = (SSLSocket) SSLSocketFactory.getDefault().createSocket(host, port);
socket.startHandshake();
boolean successful = verifier.verify(host, socket.getSession());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would make sense to clarify what verifier is. Like adding a line that at least makes clear it's a HostnameVerifier object. Likewise in IgnoredHostnameVerification.java.

Comment on lines 14 to 28

/** The `HostnameVerifier.verify()` method. */
private class HostnameVerifierVerifyMethod extends Method {
HostnameVerifierVerifyMethod() {
this.getDeclaringType().getASupertype*().hasQualifiedName("javax.net.ssl", "HostnameVerifier") and
this.hasStringSignature("verify(String, SSLSession)")
}
}

/** Defines `HostnameVerifier.verity()` calls that is not wrapped in another `HostnameVerifier`. */
private class HostnameVerificationCall extends MethodAccess {
HostnameVerificationCall() {
this.getMethod() instanceof HostnameVerifierVerifyMethod and
not this.getCaller() instanceof HostnameVerifierVerifyMethod
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's reuse already defined types.

Suggested change
/** The `HostnameVerifier.verify()` method. */
private class HostnameVerifierVerifyMethod extends Method {
HostnameVerifierVerifyMethod() {
this.getDeclaringType().getASupertype*().hasQualifiedName("javax.net.ssl", "HostnameVerifier") and
this.hasStringSignature("verify(String, SSLSession)")
}
}
/** Defines `HostnameVerifier.verity()` calls that is not wrapped in another `HostnameVerifier`. */
private class HostnameVerificationCall extends MethodAccess {
HostnameVerificationCall() {
this.getMethod() instanceof HostnameVerifierVerifyMethod and
not this.getCaller() instanceof HostnameVerifierVerifyMethod
}
import semmle.code.java.security.Encryption
/** Defines `HostnameVerifier.verity()` calls that is not wrapped in another `HostnameVerifier`. */
private class HostnameVerificationCall extends MethodAccess {
HostnameVerificationCall() {
this.getMethod() instanceof HostnameVerifierVerify and
not this.getCaller() instanceof HostnameVerifierVerify
}


/** Holds if the result of the call is not used. */
predicate isIgnored() {
not exists(Expr expr, IfStmt ifStmt, MethodAccess ma |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is relying on the CodeQL optimiser not to compute the product of all Exprs, IfStmts and MethodAccesses because there are disjuncts where each of them are in scope but unused.

Instead, do something like

not exists(Expr e | this = e.getAChildExpr()) and 
not exists(IfStmt ifStmt | ...

However you can also determine if a method call is unused like this by checking whether it's the direct child of an ExprStmt--

  this = any(ExprStmt es).getExpr()

All other kinds of use (assignment, passing to a method call, direct use in a conditional...) will be nested within some other statement or expression.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestions!

Co-authored-by: Tony Torralba <atorralba@users.noreply.github.com>
Co-authored-by: Chris Smowton <smowton@github.com>
@artem-smotrakov
Copy link
Contributor Author

@atorralba @smowton Thanks for the review. I've applied your suggestions.

@smowton
Copy link
Contributor

smowton commented Feb 14, 2022

I've removed the redundant conditions from isIgnored (an expression that is the direct child of an ExprStmt can't be an if condition or be the child of any expression)

@artem-smotrakov
Copy link
Contributor Author

I've removed the redundant conditions from isIgnored (an expression that is the direct child of an ExprStmt can't be an if condition or be the child of any expression)

Thanks!

@smowton smowton merged commit fd4dc95 into github:main Feb 14, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants