-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
base: main
Are you sure you want to change the base?
Changes from 1 commit
ed755bd
4a58910
39bd9e9
49d962e
cec3555
52ade48
8bf704f
5bdd521
6b7440f
d09de26
26b87b8
6c9936c
1502506
2257206
03f532d
90de155
588b80e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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() { | ||
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() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
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"]) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
} |
There was a problem hiding this comment.
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:sk_GENERAL_NAME_num
or a use of the macrosk_GENERAL_NAME_value
, orsk_GENERAL_NAME_value
or a call to the functionsk_GENERAL_NAME_num
.That doesn't make a lot of sense to me. Can
sk_GENERAL_NAME_num
andsk_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 toX509_get_ext_d2i
,sk_GENERAL_NAME_num
andsk_GENERAL_NAME_value
anywhere in the program.What are you really trying to model with this predicate?
There was a problem hiding this comment.
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.