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
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@

...
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
}
...
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>Lack of certificate name validation allows any valid certificate to be used. And that can lead to disclosure.</p>

</overview>

<example>
<p>The following example shows working with and without certificate name verification.</p>
<sample src="ValidationCertificateHostMismatch.cpp" />

</example>
<references>

<li>
CERT Coding Standard:
<a href="https://wiki.sei.cmu.edu/confluence/pages/viewpage.action?pageId=87150715">DRD19. Properly verify server certificate on SSL/TLS - Android - Confluence</a>.
</li>

</references>
</qhelp>
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/**
* @name Validation certificate host mismatch.
* @description Lack of certificate name validation allows any valid certificate to be used. And that can lead to disclosure.
* @kind problem
* @id cpp/validation-certificate-host-mismatch
* @problem.severity warning
* @precision medium
* @tags correctness
* security
* external/cwe/cwe-297
*/

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.

exists(FunctionCall fctmp1 |
fctmp1.getTarget().hasName("X509_get_ext_d2i") and
(
exists(MacroInvocation mi1tmp, MacroInvocation mi2tmp |
mi1tmp.getMacroName() = "sk_GENERAL_NAME_num" and
mi2tmp.getMacroName() = "sk_GENERAL_NAME_value"
)
or
exists(FunctionCall fctmp2, FunctionCall fctmp3 |
fctmp2.getTarget().hasName("sk_GENERAL_NAME_num") and
fctmp3.getTarget().hasName("sk_GENERAL_NAME_value")
)
)
)
}

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.

exists(FunctionCall fctmp1, FunctionCall fctmp2, FunctionCall fctmp3, FunctionCall fctmp4 |
globalValueNumber(fctmp1) = globalValueNumber(fctmp3.getArgument(0)) and
globalValueNumber(fctmp2) = globalValueNumber(fctmp4.getArgument(0)) and
fctmp1.getTarget().hasName("X509_get_subject_name") and
fctmp2.getTarget().hasName("X509_get_subject_name") and
fctmp3.getTarget().hasName("X509_NAME_get_text_by_NID") and
fctmp4.getTarget().hasName("X509_NAME_get_text_by_NID")
)
}

predicate checkHostnameUp110() {
exists(FunctionCall fctmp1, FunctionCall fctmp2, FunctionCall fctmp3 |
globalValueNumber(fctmp1.getArgument(0)) = globalValueNumber(fctmp2.getArgument(0)) and
globalValueNumber(fctmp2.getArgument(0)) = globalValueNumber(fctmp3.getArgument(0)) and
fctmp1.getTarget().hasName("SSL_set_hostflags") and
fctmp2.getTarget().hasName("SSL_set1_host") and
fctmp3.getTarget().hasName("SSL_set_verify")
)
}

predicate checkHostnameUp102() {
exists(FunctionCall fctmp1, FunctionCall fctmp2, FunctionCall fctmp3, FunctionCall fctmp4 |
globalValueNumber(fctmp1.getArgument(0)) = globalValueNumber(fctmp4.getArgument(0)) and
globalValueNumber(fctmp1) = globalValueNumber(fctmp2.getArgument(0)) and
globalValueNumber(fctmp1) = globalValueNumber(fctmp3.getArgument(0)) and
fctmp1.getTarget().hasName("SSL_get0_param") and
fctmp2.getTarget().hasName("X509_VERIFY_PARAM_set_hostflags") and
fctmp3.getTarget().hasName("X509_VERIFY_PARAM_set1_host") and
fctmp4.getTarget().hasName("SSL_set_verify")
)
}

from FunctionCall fc
where
not checkHostnameOlder1() and
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.

select fc.getEnclosingFunction(), "You may have missed checking the name of the certificate."
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
experimental/Security/CWE/CWE-297/ValidationCertificateHostMismatch.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
#define X509_V_OK 0
#define NULL 0
#define X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS 1
#define SSL_VERIFY_PEER 1
# define STACK_OF(type) struct type

typedef unsigned long size_t;

struct SSL {};
struct X509_STORE_CTX {};
struct X509_VERIFY_PARAM {};
struct X509 {};
struct X509_NAME {};
struct X509_NAME_ENTRY {};

struct ASN1_STRING {};

struct GENERAL_NAME
{
ASN1_STRING *d_dNSName;
};

int cmpNames(char* name1,char* name2);
char* calloc( size_t num, size_t size );
size_t strlen( char * buf);

X509 *SSL_get_peer_certificate(const SSL *ssl);
int SSL_get_verify_result(const SSL *ssl);

int verify_callback(int a, X509_STORE_CTX *b);
X509_VERIFY_PARAM *SSL_get0_param(SSL *ssl);
void X509_VERIFY_PARAM_set_hostflags(X509_VERIFY_PARAM *param,unsigned int flags);
int X509_VERIFY_PARAM_set1_host(X509_VERIFY_PARAM *param,char *name, size_t namelen);
void SSL_set_verify(SSL *s, int mode,int (*verify_callback)(int, X509_STORE_CTX *));

void SSL_set_hostflags(SSL *s, unsigned int flags);
int SSL_set1_host(SSL *s, const char *hostname);

GENERAL_NAME * X509_get_ext_d2i(const X509 *x, int nid, int *crit, int *idx);
int X509_NAME_get_text_by_NID(X509_NAME *name, int nid, char *buf,int len);
X509_NAME *X509_get_subject_name(const X509 *x);
int X509_NAME_get_index_by_NID(X509_NAME *name,int nid,int lastpos);
X509_NAME_ENTRY *X509_NAME_get_entry(X509_NAME *name, int loc);
ASN1_STRING *X509_NAME_ENTRY_get_data(const X509_NAME_ENTRY *ne);
char * ASN1_STRING_data(ASN1_STRING *x);

int SKM_sk_num(struct GENERAL_NAME * a);
#define sk_GENERAL_NAME_num(st) SKM_sk_num((st))
GENERAL_NAME* SKM_sk_value(struct GENERAL_NAME * a,int b);
#define sk_GENERAL_NAME_value(st, i) SKM_sk_value((st), (i))

bool goodTest1(SSL *ssl,char * host,int nid) // GOOD
{
X509 *cert;
int result,count,i;
char *data;
STACK_OF(GENERAL_NAME) *names = NULL;
GENERAL_NAME *altname;

result = SSL_get_verify_result(ssl);
if (result == X509_V_OK)
{
cert = SSL_get_peer_certificate(ssl);
names = X509_get_ext_d2i (cert, nid, NULL, NULL);
if(names==NULL) return false;
count = sk_GENERAL_NAME_num(names);
for(i=0;i<count;i++)
{
altname = sk_GENERAL_NAME_value (names, i);
data = ASN1_STRING_data (altname->d_dNSName);
if(cmpNames(host,data)==0) return true;
}
return false;
}
return false;
}
bool goodTest2(SSL *ssl,char * host,int nid) // GOOD
{
X509 *cert;
int result,len,i;
char *data;
STACK_OF(GENERAL_NAME) *names = NULL;
GENERAL_NAME *altname;
X509_NAME *name;

result = SSL_get_verify_result(ssl);
if (result == X509_V_OK)
{
cert = SSL_get_peer_certificate(ssl);
name = X509_get_subject_name (cert);
if (name == NULL) return false;
len = X509_NAME_get_text_by_NID (name, nid, NULL, 0);
if (len < 0) return false;
data = calloc (len + 1, 1);
if (data == NULL) return false;
X509_NAME_get_text_by_NID (name, nid, data, len + 1);
if(cmpNames(host,data)==0) return true;
}
return false;
}
bool goodTest3(SSL *ssl,char * host,int nid) // GOOD
{
X509 *cert;
int result,len,i;
char *data;
STACK_OF(GENERAL_NAME) *names = NULL;
GENERAL_NAME *altname;
X509_NAME *name;
X509_NAME_ENTRY * centry;
result = SSL_get_verify_result(ssl);
if (result == X509_V_OK)
{
cert = SSL_get_peer_certificate(ssl);
if (name == NULL) return false;
len = X509_NAME_get_index_by_NID (X509_get_subject_name (cert), nid, -1);
if (len < 0) return false;
centry = X509_NAME_get_entry(X509_get_subject_name((X509 *) cert), len);
if(centry == NULL) return false;
data = (char *) ASN1_STRING_data(X509_NAME_ENTRY_get_data(centry));
if(cmpNames(host,data)==0) return true;
}
return false;
}

bool goodTest4(SSL *ssl,char * host) // GOOD
{
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);
return true;
}
bool goodTest5(SSL *ssl,char * host) // GOOD
{
X509_VERIFY_PARAM *param = SSL_get0_param(ssl);
X509_VERIFY_PARAM_set_hostflags(param,X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);
if (!X509_VERIFY_PARAM_set1_host(param, host,strlen(host))) return false;
SSL_set_verify(ssl, SSL_VERIFY_PEER, NULL);
return true;
}