Skip to content

CPP: Add query for CWE-297: Improper Validation of Certificate with Host Mismatch #9086

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

Open
wants to merge 17 commits into
base: main
Choose a base branch
from

Conversation

ihsinme
Copy link
Contributor

@ihsinme ihsinme commented May 9, 2022

this query finds certificate situations without name validation.
I tried to take into account all cases of name processing, so there are old methods.

CVE-2010-1155
CVE-2013-7449
CVE-2016-10937

I had some confusion with the test file, the current one contains only good situations. I don't know how I can proceed in this case, I do a separate simple test without validation, but I'm not sure if it's common practice, two file tests in different folders. tell me how to be.

@ihsinme ihsinme requested a review from a team as a code owner May 9, 2022 13:04
@MathiasVP
Copy link
Contributor

Hi @ihsinme,

Thanks for another contribution. We will review get to review it soon!

@MathiasVP
Copy link
Contributor

Hi @ihsinme,

Can you add a comment to this query explaining how it's different from https://github.com/github/codeql/blob/main/cpp/ql/src/Security/CWE/CWE-295/SSLResultNotChecked.ql?

@MathiasVP MathiasVP self-assigned this May 23, 2022
@MathiasVP
Copy link
Contributor

I had some confusion with the test file, the current one contains only good situations. I don't know how I can proceed in this case, I do a separate simple test without validation, but I'm not sure if it's common practice, two file tests in different folders. tell me how to be.

I'm not sure what you mean. Do you want to add the same file to several folders?

@ihsinme
Copy link
Contributor Author

ihsinme commented May 25, 2022

Hi @ihsinme,

Can you add a comment to this query explaining how it's different from https://github.com/github/codeql/blob/main/cpp/ql/src/Security/CWE/CWE-295/SSLResultNotChecked.ql?

As I understand it, this request is looking for a situation where the certificate was not checked at all, for correctness. my query is looking for a situation where the domain name of the certificate is not verified.
if the program checks the validity of the certificate but does not check the name, it is possible to MITM by sending any valid certificate to the program.

@ihsinme
Copy link
Contributor Author

ihsinme commented May 25, 2022

I had some confusion with the test file, the current one contains only good situations. I don't know how I can proceed in this case, I do a separate simple test without validation, but I'm not sure if it's common practice, two file tests in different folders. tell me how to be.

I'm not sure what you mean. Do you want to add the same file to several folders?

the problem is that i have to store the good and bad example in two different files.
Perhaps you have rules for this case.

@MathiasVP
Copy link
Contributor

the problem is that i have to store the good and bad example in two different files.
Perhaps you have rules for this case.

You can have as many .c and .cpp test files as you want in the test directory as you want. codeql test run should pick them all up.

Does that solve your issue?

Copy link
Contributor

@MathiasVP MathiasVP left a comment

Choose a reason for hiding this comment

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

A couple of initial comments on the code.

import cpp
import semmle.code.cpp.valuenumbering.GlobalValueNumbering

predicate checkHostnameOlder1() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Reading this predicate out loud, this is what I'm reading:

This predicate holds if there exists a function call anywhere in the program that calls X509_get_ext_d2i, and either:

  • There exists a use of the macro sk_GENERAL_NAME_num or a use of the macro sk_GENERAL_NAME_value, or
  • There exists a call to the function sk_GENERAL_NAME_value or a call to the function sk_GENERAL_NAME_num.

That doesn't make a lot of sense to me. Can sk_GENERAL_NAME_num and sk_GENERAL_NAME_value really both be macros and function calls (maybe depending on versions?). Also: Shouldn't this predicate have any parameters? Right now, this is just a property of the entire codebase that either holds of doesn't hold (and I can make it hold by adding a random call to X509_get_ext_d2i, sk_GENERAL_NAME_num and sk_GENERAL_NAME_value anywhere in the program.

What are you really trying to model with this predicate?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sorry for the delay in replies.

in this predicate I have omitted call connectivity control due to the presence of macros, in the rest of the predicates I use GVN for call connectivity control.

)
}

predicate checkHostnameOlder2() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Same question here: This predicate is a property of the entire codebase that either holds or doesn't hold. And I can make it hold by adding a couple of calls to some functions anywhere in the codebase. Please describe what are you trying to do here, and I'm sure we can fix it up.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have implemented a search for all possible ways to validate the name of the certificate specified in https://wiki.openssl.org/index.php/Hostname_validation.

I'm looking for code where the developer doesn't use any of these name validation methods at all, but uses certificate validation. In this case, it's definitely a threat.
I skip when there is an error in the implementation of one of the methods, or the method is not implemented for all connections.

not checkHostnameOlder2() and
not checkHostnameUp110() and
not checkHostnameUp102() and
fc.getTarget().hasName(["SSL_set_verify", "SSL_get_peer_certificate", "SSL_get_verify_result"])
Copy link
Contributor

Choose a reason for hiding this comment

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

Right now you're raising an alert on all uses of the functions whose name is SSL_set_verify, SSL_get_peer_certificate or SSL_get_verify_result. Is that really what you intend?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

at the moment I want to find a place to work with connection verification, when in the code I did not find a way to check the name of the certificate. every place is a mistake.

@ihsinme
Copy link
Contributor Author

ihsinme commented Jun 2, 2022

I have added another test file, could you please run the checks

@MathiasVP
Copy link
Contributor

CI failures:

Executing tests in /home/runner/work/semmle-code/semmle-code/ql/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-297/semmle/tests.
2022-06-20T13:03:53.7182630Z --- expected
2022-06-20T13:03:53.7183311Z +++ actual
2022-06-20T13:03:53.7183720Z @@ -1,1 +1,1 @@
2022-06-20T13:03:53.7184595Z -| test1.cpp:11:6:11:13 | badTest1 | You may have missed checking the name of the certificate. |
2022-06-20T13:03:53.7184980Z +
2022-06-20T13:03:53.7190687Z ##[error][5386/5505 comp 6.1s eval 599ms] FAILED(RESULT) /home/runner/work/semmle-code/semmle-code/ql/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-297/semmle/tests/ValidationCertificateHostMismatch.qlref

It looks like the testcase in test1.cpp isn't being flagged.

@MathiasVP
Copy link
Contributor

Hi @ihsinme,

I've discussed the status of this query with the GitHub Security Lab team, and they've pointed me to this query in their repo: https://github.com/github/securitylab/tree/main/CodeQL_Queries/cpp/OpenSSL-hostname-validation

This query also covers a verify callback from Boost's Asio library. It would make sense for this query to also cover this API. If you want to improve your final score on the bug bounty program, I suggest the following extensions to your query:

To be clear: You don't have to do this, but adding the above extensions will likely improve the final score on your bug bounty submission.

@ihsinme
Copy link
Contributor Author

ihsinme commented Jun 22, 2022

To be clear: You don't have to do this, but adding the above extensions will likely improve the final score on your bug bounty submission.

Hi @MathiasVP.

of course I will try to improve this query.
I need some time for this.

Thank you.

fixing the work with the test file, I'll deal with it after the request has been improved.

@ihsinme
Copy link
Contributor Author

ihsinme commented Jun 22, 2022

@MathiasVP, i made additions.

but my local environment is not able to work with multiple test files, can you show the results of working with test files?

Pay attention to the test files, I talked about this at the beginning of the request.
if the query logic is looking for something that is missing somewhere in the code, then building the test files is very difficult. the presence of one correct example excludes detection (
Is there any trick for this?

@MathiasVP
Copy link
Contributor

@MathiasVP, i made additions.

Fantastic!

but my local environment is not able to work with multiple test files, can you show the results of working with test files?

Pay attention to the test files, I talked about this at the beginning of the request. if the query logic is looking for something that is missing somewhere in the code, then building the test files is very difficult. the presence of one correct example excludes detection ( Is there any trick for this?

There really isn't any tricks to this. The tests highlights the behavior of the query as you wrote it. I described the problem here:

This predicate is a property of the entire codebase that either holds or doesn't hold. And I can make it hold by adding a couple of calls to some functions anywhere in the codebase. Please describe what are you trying to do here, and I'm sure we can fix it up.

I think the only way to fix the tests is to change the query logic so that the above isn't the case anymore (i.e., so that a single call in some function anywhere in the code doesn't fix all the alerts in the codebase).

@ihsinme
Copy link
Contributor Author

ihsinme commented Jun 26, 2022

I have established a call chain connection, please have a look.

I would like to make a few points:

  1. in the boost, I still couldn't make the correct calls, because I don't know how to define a callback call in the test file. (if you have any thoughts please push me)
  2. in file test.cpp in function goodTest3 on line 113 if you change the call to X509_NAME_get_index_by_NID (X509_get_subject_name (cert), nid, -1);. then there will be no error, but this call will not be considered X509_NAME_get_index_by_NID either. if you could tell me why this is happening, that would be great.
  3. when I wrote about the complexity of working with several test files, I thought that there is a mechanism that allows each test file to be considered as a separate project.

@MathiasVP
Copy link
Contributor

MathiasVP commented Jun 30, 2022

I have established a call chain connection, please have a look.

Great! Thanks for fixing this :)

in the boost, I still couldn't make the correct calls, because I don't know how to define a callback call in the test file. (if you have any thoughts please push me)

You should be able to do something similar to what they do in this example:

class verify_context { /* ... */ };

bool my_verify_callback(
  bool preverified,
  verify_context& ctx
) {
  /* ... */
}

void test(boost::asio::ssl::stream sock){
  // ...
  sock.set_verify_callback(my_verify_callback);
  // ...
}

or using a lambda:

class verify_context { /* ... */ };

void test(boost::asio::ssl::stream sock){
  // ...
  sock.set_verify_callback([&](bool preverified, verify_context& ctx) {
    // ...
  });
  // ...
}

Does that make sense?

in file test.cpp in function goodTest3 on line 113 if you change the call to X509_NAME_get_index_by_NID (X509_get_subject_name (cert), nid, -1);. then there will be no error, but this call will not be considered X509_NAME_get_index_by_NID either. if you could tell me why this is happening, that would be great.

I don't completely follow what you're saying here. In particular:

  • What do you mean by "this call will not be considered X509_NAME_get_index_by_NID either"?
  • You're saying that if you change the line:
len = X509_NAME_get_text_by_NID (X509_get_subject_name (cert), nid, NULL, 0);

to

len = X509_NAME_get_index_by_NID (X509_get_subject_name (cert), nid, -1);

then there will not be an alert? There shouldn't be an alert in this function anyway, right? (since it's a good test).

when I wrote about the complexity of working with several test files, I thought that there is a mechanism that allows each test file to be considered as a separate project.

Different .cpp files for a qltest are compiled as different compilation units (like you'd normally do for a C/C++ project), but they're all linked together before running the test.

@ihsinme
Copy link
Contributor Author

ihsinme commented Jul 3, 2022

You should be able to do something similar to what they do in this example:

thanks, added two goodTest functions.

then there will not be an alert? There shouldn't be an alert in this function anyway, right? (since it's a good test).

I was surprised that I didn't declare a function with three parameters in this test. and expected an error with such a replacement.

Different .cpp files for a qltest are compiled as different compilation units (like you'd normally do for a C/C++ project), but they're all linked together before running the test.

thanks.

P.S. could you show me the result of working on three file tests?

@ihsinme
Copy link
Contributor Author

ihsinme commented Aug 3, 2022

good afternoon @MathiasVP.
any news on this PR?

@MathiasVP
Copy link
Contributor

good afternoon @MathiasVP.
any news on this PR?

Hi! I've just come back from vacation now and I'm catching up with all of the open PRs of yours that I'm assigned to. I promise to have an update on all of those today.

@MathiasVP
Copy link
Contributor

CI failure:

--- expected
2022-08-03T09:16:17.3406473Z +++ actual
2022-08-03T09:16:17.3407144Z @@ -1,1 +1,5 @@
2022-08-03T09:16:17.3408765Z -| test1.cpp:11:6:11:13 | badTest1 | You may have missed checking the name of the certificate. |
2022-08-03T09:16:17.3410313Z +| test1.cpp:47:5:47:12 | badTest1 | You may have missed checking the name of the certificate. |
2022-08-03T09:16:17.3411826Z +| test1.cpp:53:5:53:12 | badTest2 | You may have missed checking the name of the certificate. |
2022-08-03T09:16:17.3413306Z +| test1.cpp:59:5:59:12 | badTest3 | You may have missed checking the name of the certificate. |
2022-08-03T09:16:17.3414764Z +| test2.cpp:41:6:41:13 | badTest1 | You may have missed checking the name of the certificate. |
2022-08-03T09:16:17.3416365Z +| test.cpp:138:6:138:13 | badTest1 | You may have missed checking the name of the certificate. |
2022-08-03T09:16:17.3425768Z ##[error][6893/7565 comp 7.5s eval 925ms] FAILED(RESULT) ql/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-297/semmle/tests/ValidationCertificateHostMismatch.qlref

Looks like the .expected files need to be updated.

@ihsinme
Copy link
Contributor Author

ihsinme commented Aug 8, 2022

@MathiasVP please show me what is the error.

@ihsinme
Copy link
Contributor Author

ihsinme commented Aug 29, 2022

good afternoon @MathiasVP.
maybe I didn’t do something for this PR?

)
}

predicate checkHostnameGnuTLS(FunctionCall fc) {

Check warning

Code scanning / CodeQL

Acronyms should be PascalCase/camelCase.

Acronyms in checkHostnameGnuTLS should be PascalCase/camelCase
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do I understand correctly that I need to correct the name to CheckHostnameGnuTLS ?

Copy link
Contributor

Choose a reason for hiding this comment

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

No. The Code Scanning alert is saying that QL names must preferably be in PascalCase (for classes and modules) or camelCase (for predicates and variables). So checkHostnameGnuTLS should ideally be checkHostnameGnuTls to match our style guide. However, one could argue that, since TLS is a common way to write Transport Layer Security, this name is actually fine.

So I don't think you need to change your name to make the Code Scanning alert disappear.

)
}

predicate checkHostnameBOOST1(FunctionCall fc) {

Check warning

Code scanning / CodeQL

Acronyms should be PascalCase/camelCase.

Acronyms in checkHostnameBOOST1 should be PascalCase/camelCase
)
}

predicate checkHostnameBOOST2(FunctionCall fc) {

Check warning

Code scanning / CodeQL

Acronyms should be PascalCase/camelCase.

Acronyms in checkHostnameBOOST2 should be PascalCase/camelCase
@MathiasVP
Copy link
Contributor

Hi @ihsinme,

Apologies for not getting back to this one. There was a technical issue that caused CI to complain. I hope we've fixed it by merging in the latest main.

@ihsinme
Copy link
Contributor Author

ihsinme commented Oct 4, 2022

Hi @MathiasVP.
I have to do something for this PR?

@MathiasVP
Copy link
Contributor

Hi @ihsinme,

I'm trying to resolve the CI failure. Not sure why you keep being a victim of this particular issue. Hopefully merging in main will resolve it 🤞.

@github-actions
Copy link
Contributor

github-actions bot commented Oct 4, 2022

QHelp previews:

cpp/ql/src/experimental/Security/CWE/CWE-297/ValidationCertificateHostMismatch.qhelp

Validation certificate host mismatch.

Lack of certificate name validation allows any valid certificate to be used. And that can lead to disclosure.

Example

The following example shows working with and without certificate name verification.

...
  SSL_set_hostflags(ssl, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);
  if (!SSL_set1_host(ssl, host)) return false;
  SSL_set_verify(ssl, SSL_VERIFY_PEER, NULL); // GOOD
...
  result = SSL_get_verify_result(ssl);
  if (result == X509_V_OK)
  {
    cert = SSL_get_peer_certificate(ssl);
    if(cert) return true; // BAD
  }
...

References

@ihsinme
Copy link
Contributor Author

ihsinme commented Oct 17, 2022

Hi @MathiasVP
The check didn't start.

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