mirror of
https://github.com/open62541/open62541.git
synced 2026-10-17 04:42:09 +08:00
fix(plugin): replace substring URI match with exact comparison in verifyApplicationUri
UA_CertificateUtils_verifyApplicationUri() checked the certificate's Subject Alternative Name URI field using a substring search: the function returned success whenever the expected applicationURI appeared anywhere inside the certificate's URI string. An attacker who can present a certificate trusted by the peer (e.g. a self-signed certificate added to the trust list, or any certificate issued by a trusted CA) can craft a URI SAN of the form "urn:attacker.example:padding:<legitimateURI>:suffix". The substring search accepts this certificate as if it belonged to the legitimate application, allowing impersonation of any OPC UA endpoint whose ApplicationUri is known. OpenSSL backend: replace the UA_Bstrstr() call with a length-and-memcmp exact match against the first URI SAN entry extracted by OpenSSL. mbedTLS backend (< 3.4.0): replace the raw-blob UA_Bstrstr() call with a minimal DER walker (verifySanUri) that locates the Subject Alternative Name extension by OID (2.5.29.17), iterates the GeneralNames SEQUENCE, and performs an exact length+memcmp comparison against each URI ([6] IMPLICIT IA5String) GeneralName entry, using the existing named mbedTLS constants for the tag class and SAN type instead of raw literals. mbedTLS >= 3.4.0 already used UA_String_equal() and is unaffected. Internal Vulnerability Advisory: open62541-SA-2026-0019 Co-authored-by: Joerg Fischer <joerg.fischer@sick.de> Thanks-to: Chuan Wei [SCU] for the responsible disclosure.
This commit is contained in:
committed by
Julius Pfrommer
parent
3ba918e009
commit
f4c2dc2aa4
@@ -701,20 +701,79 @@ cleanup:
|
||||
|
||||
#if MBEDTLS_VERSION_NUMBER < 0x03040000
|
||||
|
||||
static const unsigned char *
|
||||
UA_Bstrstr(const unsigned char *s1, size_t l1, const unsigned char *s2, size_t l2) {
|
||||
if(l2 == 0)
|
||||
return s1;
|
||||
if(l1 < l2)
|
||||
return NULL;
|
||||
size_t limit = l1 - l2 + 1;
|
||||
for(size_t i = 0; i < limit; ++i) {
|
||||
if(s1[i] == s2[0]) {
|
||||
if(memcmp(s1 + i, s2, l2) == 0)
|
||||
return s1 + i;
|
||||
/* Walks the raw v3_ext DER blob and performs an exact match of each URI entry
|
||||
* in the Subject Alternative Name extension against applicationURI.
|
||||
* Used only for mbedTLS < 3.4.0, which does not expose a parsed SAN URI. */
|
||||
static UA_StatusCode
|
||||
verifySanUri(const mbedtls_x509_buf *v3_ext, const UA_String *applicationURI) {
|
||||
unsigned char *p = v3_ext->p;
|
||||
const unsigned char *end = p + v3_ext->len;
|
||||
size_t len;
|
||||
|
||||
/* Extensions ::= SEQUENCE OF Extension */
|
||||
if(mbedtls_asn1_get_tag(&p, end, &len,
|
||||
MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE) != 0)
|
||||
return UA_STATUSCODE_BADCERTIFICATEURIINVALID;
|
||||
const unsigned char *ext_end = p + len;
|
||||
|
||||
while(p < ext_end) {
|
||||
/* Extension ::= SEQUENCE { extnID OID, critical BOOLEAN OPTIONAL,
|
||||
* extnValue OCTET STRING } */
|
||||
if(mbedtls_asn1_get_tag(&p, ext_end, &len,
|
||||
MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE) != 0)
|
||||
break;
|
||||
unsigned char *entry_end = p + len;
|
||||
|
||||
/* Read OID */
|
||||
if(mbedtls_asn1_get_tag(&p, entry_end, &len, MBEDTLS_ASN1_OID) != 0)
|
||||
break;
|
||||
const unsigned char *oid_p = p;
|
||||
p += len;
|
||||
|
||||
/* Skip anything that is not the SAN extension OID (2.5.29.17) */
|
||||
if(len != MBEDTLS_OID_SIZE(MBEDTLS_OID_SUBJECT_ALT_NAME) ||
|
||||
memcmp(oid_p, MBEDTLS_OID_SUBJECT_ALT_NAME, len) != 0) {
|
||||
p = entry_end;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Skip optional critical BOOLEAN */
|
||||
if(p < entry_end && *p == MBEDTLS_ASN1_BOOLEAN) {
|
||||
if(mbedtls_asn1_get_tag(&p, entry_end, &len, MBEDTLS_ASN1_BOOLEAN) != 0)
|
||||
break;
|
||||
p += len;
|
||||
}
|
||||
|
||||
/* extnValue ::= OCTET STRING containing the encoded GeneralNames */
|
||||
if(mbedtls_asn1_get_tag(&p, entry_end, &len, MBEDTLS_ASN1_OCTET_STRING) != 0)
|
||||
break;
|
||||
const unsigned char *val_end = p + len;
|
||||
|
||||
/* GeneralNames ::= SEQUENCE OF GeneralName */
|
||||
if(mbedtls_asn1_get_tag(&p, val_end, &len,
|
||||
MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE) != 0)
|
||||
break;
|
||||
const unsigned char *san_end = p + len;
|
||||
|
||||
/* GeneralName ::= CHOICE { ..., uniformResourceIdentifier [6] IA5String, ... } */
|
||||
const unsigned char uriTag =
|
||||
MBEDTLS_ASN1_CONTEXT_SPECIFIC | MBEDTLS_X509_SAN_UNIFORM_RESOURCE_IDENTIFIER;
|
||||
while(p < san_end) {
|
||||
unsigned char tag = *p;
|
||||
if(mbedtls_asn1_get_tag(&p, san_end, &len, tag) != 0)
|
||||
break;
|
||||
if(tag == uriTag &&
|
||||
len == applicationURI->length &&
|
||||
memcmp(p, applicationURI->data, len) == 0)
|
||||
return UA_STATUSCODE_GOOD;
|
||||
p += len;
|
||||
}
|
||||
|
||||
/* SAN extension found but no URI matched — do not fall through to other extensions */
|
||||
return UA_STATUSCODE_BADCERTIFICATEURIINVALID;
|
||||
}
|
||||
return NULL;
|
||||
|
||||
return UA_STATUSCODE_BADCERTIFICATEURIINVALID;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -753,12 +812,7 @@ UA_CertificateUtils_verifyApplicationUri(const UA_ByteString *certificate,
|
||||
}
|
||||
}
|
||||
#else
|
||||
/* Poor man's ApplicationUri verification. mbedTLS does not parse all fields
|
||||
* of the Alternative Subject Name. Instead test whether the URI-string is
|
||||
* present in the v3_ext field in general. */
|
||||
if(UA_Bstrstr(remoteCertificate.v3_ext.p, remoteCertificate.v3_ext.len,
|
||||
applicationURI->data, applicationURI->length) == NULL)
|
||||
retval = UA_STATUSCODE_BADCERTIFICATEURIINVALID;
|
||||
retval = verifySanUri(&remoteCertificate.v3_ext, applicationURI);
|
||||
#endif
|
||||
|
||||
mbedtls_x509_crt_free(&remoteCertificate);
|
||||
|
||||
@@ -793,8 +793,8 @@ UA_CertificateUtils_verifyApplicationUri(const UA_ByteString *certificate,
|
||||
}
|
||||
|
||||
UA_StatusCode ret = UA_STATUSCODE_GOOD;
|
||||
if(UA_Bstrstr(subjectURI.data, subjectURI.length,
|
||||
applicationURI->data, applicationURI->length) == NULL)
|
||||
if(subjectURI.length != applicationURI->length ||
|
||||
memcmp(subjectURI.data, applicationURI->data, applicationURI->length) != 0)
|
||||
ret = UA_STATUSCODE_BADCERTIFICATEURIINVALID;
|
||||
|
||||
X509_free(certificateX509);
|
||||
|
||||
Reference in New Issue
Block a user