Skip to content
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

Add modified certificate DNS check #1

Open
wants to merge 18 commits into
base: master
Choose a base branch
from

Conversation

KarthikaRajendran11
Copy link

No description provided.

tlscommon.c Outdated
@@ -626,13 +626,38 @@ static int certattr_matchip(GENERAL_NAME *gn, struct certattrmatch *match){
&& !memcmp(ASN1_STRING_get0_data(gn->d.iPAddress), &match->ipaddr, l)) ? 1 : 0 ;
}

static int compareWithModifiedHostname(char* certDNS, char* scriptHostName) {
int len = 0;
int len1 = 0, len2 = 0;

Choose a reason for hiding this comment

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

@KarthikaRajendran11 , these variables' names could be more meaningful. The current ones make it hard to follow the logic.

Copy link

@DaveTuchlinsky DaveTuchlinsky left a comment

Choose a reason for hiding this comment

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

Has this been tested with multiple DNS names in the certificate? For example, SingleDigits contains 3af521.net, *.3af521.net, 3af521.com, *.3af521.com in the SAN.

tlscommon.c Outdated Show resolved Hide resolved
tlscommon.c Outdated Show resolved Hide resolved
tlscommon.c Outdated Show resolved Hide resolved
@KarthikaRajendran11
Copy link
Author

Has this been tested with multiple DNS names in the certificate? For example, SingleDigits contains 3af521.net, *.3af521.net, 3af521.com, *.3af521.com in the SAN.

If the host name returned by the script is idp.3af521.net, then it will match only to *.3af521.net and idp.3af521.net
It wont match for idp.3af521.com or *.3af521.com

tlscommon.c Outdated
char* pos;
char* modifiedScriptHostName;
char *result;
char generic[] = "*";

Choose a reason for hiding this comment

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

Add null checks for the certDNS, and scriptHostName pointers. Initialize each variable (pos, modifiedScriptHostName, and result) to a known value, specifically NULL.

tlscommon.c Outdated
return 0;
}
len = strlen(pos);
modifiedScriptHostName = malloc(len + 1);

Choose a reason for hiding this comment

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

(1) Use calloc() instead (it creates a zero'd memory buffer instead of random bytes that malloc gives you).
(2) Check for memory allocation failures. Specifically malloc/calloc return NULL when the system is out of memory, check for this, and bail.
(3) You can eliminate L646 as well, since the array will be all zeros.

tlscommon.c Outdated
len = strlen(pos);
modifiedScriptHostName = malloc(len + 1);
memcpy(modifiedScriptHostName, pos, len);
modifiedScriptHostName[len] = '\0';

Choose a reason for hiding this comment

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

Remove (per earlier comment)

tlscommon.c Outdated
modifiedScriptHostName = malloc(len + 1);
memcpy(modifiedScriptHostName, pos, len);
modifiedScriptHostName[len] = '\0';
debug(DBG_DBG, "modifiedScriptHostName : %s", modifiedScriptHostName);

Choose a reason for hiding this comment

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

modifiedScriptHostName is an attacker (think "specially crafted malicious certificate") controlled. Let's not print out the certificate info, unless we REALLY need to.

tlscommon.c Outdated
debug(DBG_DBG, "modifiedScriptHostName : %s", modifiedScriptHostName);
lenOfGeneric = strlen(generic);
lenOfModifiedScriptHostName = strlen(modifiedScriptHostName);
result = malloc(lenOfGeneric + lenOfModifiedScriptHostName + 1);

Choose a reason for hiding this comment

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

Again, use calloc() here, and check for out of memory conditions.

tlscommon.c Outdated
result = malloc(lenOfGeneric + lenOfModifiedScriptHostName + 1);
memcpy(result, generic, lenOfGeneric);
memcpy(result + lenOfGeneric, modifiedScriptHostName, lenOfModifiedScriptHostName + 1);
if (strlen(certDNS) == strlen(result) && memcmp(result, certDNS, strlen(certDNS)) == 0) {

Choose a reason for hiding this comment

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

My preferred way to code this would be. It's less likely to leak memory as changes are made to the code base over time.

if (strlen(...) == strlen(...)....) {
ret = 1
} else {
ret = 0
}

if (result) {
free(result);
result = NULL;
}

if (modifiedScriptHostName) {
free(modifiedScriptHostName);
modifiedScriptHostName = NULL;
}

return ret;

tlscommon.c Outdated
lenOfModifiedScriptHostName = strlen(modifiedScriptHostName);
result = malloc(lenOfGeneric + lenOfModifiedScriptHostName + 1);
memcpy(result, generic, lenOfGeneric);
memcpy(result + lenOfGeneric, modifiedScriptHostName, lenOfModifiedScriptHostName + 1);

Choose a reason for hiding this comment

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

Remove the +1 at the end, since calloc will give you a zero'd array. If for some reason modifiedScriptHostName isn't null terminated, then you're not corrupting the result buffer along the way. This guarantees that result will be null terminated.

Copy link

@dave-greenfieldlabs dave-greenfieldlabs left a comment

Choose a reason for hiding this comment

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

Have you considered the following certificate/DNS testing issues?
Please read https://datatracker.ietf.org/doc/html/rfc6125#section-6.4

  • DNS comparisons are case insensitive ASCII, the code looks like idp.EXAMPLE.COM won't match to *.example.com and it should.
  • Wildcard certificates cannot match on strings like "bar.*.example.net". Only the first level should be compared.
  • Internationalized Domain Names have special rules, and pre-processing requirements.

tlscommon.c Outdated Show resolved Hide resolved
@@ -568,7 +543,6 @@ vY/uPjA=\n\
X509_free(certsanipv6);
X509_free(certsanipv6indns);
X509_free(certcomplex);
X509_free(certcomplexother);

Choose a reason for hiding this comment

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

Why was this certificate removed?

Choose a reason for hiding this comment

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

It is not being used in any tests. They are just allocating it and freeing

static int _general_name_regex_match(char *v, int l, struct certattrmatch *match) {
char *s;
if (l <= 0 )
return 0;
if (match->exact) {
if (l == strlen(match->exact) && memcmp(v, match->exact, l) == 0)
return 1;
if(compareWithModifiedHostname(v, match->exact)) {

Choose a reason for hiding this comment

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

Why is this in the match->exact block? If it must be here, then please include a comment on why since it mutates the expected behaviour of an exact match.

Choose a reason for hiding this comment

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

This is the original code format. If match->exact is set it means we have found a host/DNS (the script has returned a valid host).

tlscommon.c Outdated
@@ -626,13 +628,182 @@ static int certattr_matchip(GENERAL_NAME *gn, struct certattrmatch *match){
&& !memcmp(ASN1_STRING_get0_data(gn->d.iPAddress), &match->ipaddr, l)) ? 1 : 0 ;
}

static char* copy(char* s1, char* s2) {

Choose a reason for hiding this comment

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

This func is not copy. It is allocating & concatenating.

tlscommon.c Show resolved Hide resolved
tlscommon.c Show resolved Hide resolved
tlscommon.c Outdated
if(s1 == NULL && s2 == NULL) {
return NULL;
}
char* result = (char*) calloc(strlen(s1) + strlen(s2), sizeof(char));

Choose a reason for hiding this comment

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

Off by one error, should be strlen(s1)+strlen(s2)+1.

tlscommon.c Outdated
if(s2 == NULL) {
return s1;
}
if(s1 == NULL && s2 == NULL) {

Choose a reason for hiding this comment

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

Should be the first boolean test made.

tlscommon.c Outdated
@@ -626,13 +628,185 @@ static int certattr_matchip(GENERAL_NAME *gn, struct certattrmatch *match){
&& !memcmp(ASN1_STRING_get0_data(gn->d.iPAddress), &match->ipaddr, l)) ? 1 : 0 ;
}

static char* copy(char* s1, char* s2) {
if(s1 == NULL) {
return s2;

Choose a reason for hiding this comment

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

Dangerous, this breaks the schematics of the function. Returning s2 doesn't return a deep copy (or clone) of s2, but a pointer to s2. Doing do would cause the memory to be improperly deallocated as shown below...

char* mynewstring = copy("Foo", "Bar");
// use mynewstring for a while, then deallocate it.
free(mynewstring);
mynewstring=NULL;

char* thisisbad = copy(NULL, "Foo");
// use thisisbad
free(thisisbad);  // Attempt to free the pointer to the string "Foo" which ISN'T heap allocated.

You should allocate new memory for "Foo" from the heap (with calloc) and then copy it over.

tlscommon.c Outdated
char* result = NULL;
for(i = 0; i < n; i++) {
val = va_arg(ap, char*);
result = copy(result, val);

Choose a reason for hiding this comment

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

Where is result free'd? What happens if it's NULL?

Choose a reason for hiding this comment

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

append is called from compareWithModifiedHostname and calculateLenOfLabel (which is called from compareWithModifiedHostname). NULL check is done in compareWithModifiedHostname. And the memory used is also freed in compareWithModifiedHostname. modifiedHostName, pattern, lenRegex are the variables that are results of append. They are freed in compareWithModifiedHostname.

tlscommon.c Outdated

int reti = regcomp(&regex, pattern, REG_EXTENDED);
if (reti) {
regerror(reti, &regex, buffer, 100);

Choose a reason for hiding this comment

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

instead of using 100 the constant, should be sizeof(buffer) instead

tlscommon.c Outdated
return result;
}
int lenOfFirstPortion = strlen(dns) - strlen(firstPos);
result = (char*) calloc(lenOfFirstPortion, sizeof(char));

Choose a reason for hiding this comment

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

Off by 1, lenOfFirstPortion + 1

char* result = NULL;
for(i = 0; i < n; i++) {
val = va_arg(ap, char*);
result = concatenate(result, val);

Choose a reason for hiding this comment

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

Where does result get free()'d

tlscommon.c Outdated

static char* calculateLenOfLabel(int len) {
char str[3];
sprintf(str, "%d", len);

Choose a reason for hiding this comment

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

Buffer overflow here. len can (in general) be the size of an integer. So you need more than 3 bytes to store a 32-bit decimal value.

char str[3];
sprintf(str, "%d", len);
debug(DBG_DBG, "str : %s", str);
char* result = append(3, "{0,", str, "}");

Choose a reason for hiding this comment

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

Is 3 really what we want?

// Internationlized Domain Names: www.äöü.com converted to www.xn--4ca0bs.com. xn--4ca0bs is considered
// a valid DN and let through. diseñolatinoamericano.com => xn--diseolatinoamericano-66b.com will also be
// let through. So IDNs are passed in Punycode encoded and xn-- says everything that follows is unicode encoded.
static int compareWithModifiedHostname(char* certDNS, char* hostname) {

Choose a reason for hiding this comment

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

Karthika, there is a much safer memory pattern to use here. The reason for the pattern below is that it keeps all memory deallocations in one place for better readability, and better maintainability. This is 'C' version of exception handling (sort of). Consider the following pattern...

char* firstPorCertDNS = NULL;
char* firstPorHostName = NULL;
char* modifiedHostName = NULL;
int retcode = ERROR;

....
do stuff
....
if (I detected and error) {
  goto cleanup;
}

... do more stuff ...
   retcode = 1;

   return retcode;

cleanup:
  if (firstPorCertDNS != NULL) {
    free(firstPorCertDNS);
    firstPorCertDNS = NULL;
  }

  if (firstPorHostName != NULL) {
    free(firstPorHostName);
    firstPorHostName = NULL;
  }
 
  if (modifiedHostName != NULL) {
    free(modifiedHostName);
    modifiedHostName = NULL;
  }
  
  return retcode;
}

retCode = 1;
}

goto cleanup;

Choose a reason for hiding this comment

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

Unneeded & spacing below is off. Labels are outdented.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants