CVE-2023-40012 (published 2023-08-09T16:15Z)

uthenticode is a small cross-platform library for partially verifying Authenticode digital signatures. Versions of uthenticode prior to the 2.x series did not check Extended Key Usages in certificates, in violation of the Authenticode X.509 certificate profile. As a result, a malicious user could produce a "signed" PE file that uthenticode would verify and consider valid using an X.509 certificate that isn\'t entitled to produce code signatures (e.g., a SSL certificate). By design, uthenticode does not perform full-chain validation. However, the absence of EKU validation was an unintended oversight. The 2.0.0 release series includes EKU checks. There are no workarounds to this vulnerability.

CVSS v3 base score: 7.5, HIGH (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N)

Weakness information:
cwe_id: CWE-347
cwe_name: Improper Verification of Cryptographic Signature
description: The product does not verify, or incorrectly verifies, the cryptographic signature for data.
extended_description: 
            
            
         
         
cwe_id: CWE-325
cwe_name: Missing Cryptographic Step
description: The product does not implement a required step in a cryptographic algorithm, resulting in weaker encryption than advertised by the algorithm.
extended_description: 
            
            
         
         

Fix information:
repo_url: https://github.com/trailofbits/uthenticode
commit_details: repo_url: https://github.com/trailofbits/uthenticode
author: William Woodruff
author_date: 2023-07-28 16:44:36-04:00
msg: src: bump OpenSSL, EKU check (#78)

* src: bump OpenSSL, EKU check

Signed-off-by: William Woodruff <william@trailofbits.com>

* README: bump OpenSSL

Signed-off-by: William Woodruff <william@trailofbits.com>

* test: EKU test, testcase

Signed-off-by: William Woodruff <william@trailofbits.com>

---------

Signed-off-by: William Woodruff <william@trailofbits.com>
repository: repo_url: https://github.com/trailofbits/uthenticode
repo_name: trailofbits/uthenticode
description: A cross-platform library for verifying Authenticode signatures
repo_language: C++
owner: trailofbits
file_changes: filename: README.md
change_type: ModificationType.MODIFY
diff: @@ -49,7 +49,7 @@ complete.
 ## Building
 
 *μthenticode* depends on [pe-parse](https://github.com/trailofbits/pe-parse)
-and OpenSSL 1.1.0, which are installed via `vcpkg` by following these steps:
+and OpenSSL 3.0 or higher, which are installed via `vcpkg` by following these steps:
 
 ```bash
 cmake -B build -S . -DCMAKE_TOOLCHAIN_FILE=<vcpkg-path>/scripts/buildsystems/vcpkg.cmake

programming_language: Markdown
filename: CMakeLists.txt
change_type: ModificationType.MODIFY
diff: @@ -4,7 +4,7 @@ project(uthenticode)
 
 find_package(pe-parse REQUIRED)
 find_package(
-  OpenSSL 1.1
+  OpenSSL 3.0
   COMPONENTS Crypto
   REQUIRED
 )

programming_language: Shell
filename: uthenticode.cpp
change_type: ModificationType.MODIFY
diff: @@ -3,6 +3,7 @@
 #include <openssl/crypto.h>
 #include <openssl/err.h>
 #include <openssl/x509.h>
+#include <openssl/x509v3.h>
 
 #include <algorithm>
 #include <array>
@@ -209,6 +210,32 @@ bool SignedData::verify_signature() const {
     return false;
   }
 
+  auto *signers_stack_ptr = PKCS7_get0_signers(p7_, nullptr, 0);
+  if (signers_stack_ptr == nullptr) {
+    return false;
+  }
+  auto signers_stack = impl::STACK_OF_X509_ptr(signers_stack_ptr, impl::SK_X509_free);
+
+  /* NOTE(ww): Authenticode specification, page 13: the signer must have the
+   * codeSigning EKU, **or** no member of the signer's chain may have it.
+   *
+   * The check below is more strict than that: **every** signer must have
+   * the codeSigning EKU, and we don't check the embedded chain (since
+   * we can't do full chain verification anyways).
+   */
+  for (auto i = 0; i < sk_X509_num(signers_stack.get()); ++i) {
+    auto *signer = sk_X509_value(signers_stack.get(), i);
+
+    /* NOTE(ww): Ths should really be X509_check_purpose with
+     * X509_PURPOSE_CODE_SIGN, but this is inexplicably not present
+     * in even the latest releases of OpenSSL as of 2023-05.
+     */
+    auto xku_flags = X509_get_extended_key_usage(signer);
+    if (!(xku_flags & XKU_CODE_SIGN)) {
+      return false;
+    }
+  }
+
   /* NOTE(ww): What happens below is a bit dumb: we convert our SpcIndirectDataContent back
    * into DER form so that we can unwrap its ASN.1 sequence and pass the underlying data
    * to PKCS7_verify for verification. This displays our intent a little more clearly than

programming_language: C++
filename: helpers.h
change_type: ModificationType.MODIFY
diff: @@ -95,3 +95,19 @@ class AuthNest32PlusTest : public ::testing::Test {
 
   peparse::parsed_pe *pe{nullptr};
 };
+
+class MissingEKUTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    auto *file = UTHENTICODE_TEST_ASSETS "/A_SSLippery_Slope.exe";
+
+    pe = peparse::ParsePEFromFile(file);
+    ASSERT_TRUE(pe != nullptr);
+  }
+
+  void TearDown() override {
+    peparse::DestructParsedPE(pe);
+  }
+
+  peparse::parsed_pe *pe{nullptr};
+};

programming_language: C++
filename: signeddata-test.cpp
change_type: ModificationType.MODIFY
diff: @@ -116,3 +116,10 @@ TEST_F(AuthNest32PlusTest, SignedData_properties_nested) {
   ASSERT_STRCASEEQ(checksumstr.c_str(),
                    "ddc5b39c4292120745eb86a67eaa331032cc05a0dafaf6e28ec9aa0f189c408d");
 }
+
+TEST_F(MissingEKUTest, SignedData_missing_codesigning_EKU) {
+  auto certs = uthenticode::read_certs(pe);
+  auto signed_data = certs[0].as_signed_data();
+
+  ASSERT_FALSE(signed_data->verify_signature());
+}

programming_language: C++