CVE-2024-39698: Improper Certificate Validation (CWE-295)
value: electron-updater allows for automatic updates for Electron apps. The file `packages/electron-updater/src/windowsExecutableCodeSignatureVerifier.ts` implements the signature validation routine for Electron applications on Windows. Because of the surrounding shell, a first pass by `cmd.exe` expands any environment variable found in command-line above. This creates a situation where `verifySignature()` can be tricked into validating the certificate of a different file than the one that was just downloaded. If the step is successful, the malicious update will be executed even if its signature is invalid. This attack assumes a compromised update manifest (server compromise, Man-in-the-Middle attack if fetched over HTTP, Cross-Site Scripting to point the application to a malicious updater server, etc.). The patch is available starting from 6.3.0-alpha.6.
CWE description: The product does not validate, or incorrectly validates, a certificate.
CVSS v3 base score: 7.5
Published: 2024-07-09T18:15Z
Repository: https://github.com/electron-userland/electron-builder
Language: Markdown
Files changed: .changeset/nervous-carrots-begin.md, packages/electron-updater/src/windowsExecutableCodeSignatureVerifier.ts
Commit date: 2024-07-05 09:06:33 -0700
Commit message: fix: verify LiteralPath of update file during windows signature verification (#8295)


Diff: diff --git a/.changeset/nervous-carrots-begin.md b/.changeset/nervous-carrots-begin.md
new file mode 100644
index 000000000..c96020eab
--- /dev/null
+++ b/.changeset/nervous-carrots-begin.md
@@ -0,0 +1,5 @@
+---
+"electron-updater": patch
+---
+
+fix: verify LiteralPath of update file during windows signature verification
diff --git a/packages/electron-updater/src/windowsExecutableCodeSignatureVerifier.ts b/packages/electron-updater/src/windowsExecutableCodeSignatureVerifier.ts
index ab52ef6e8..121f2db5e 100644
--- a/packages/electron-updater/src/windowsExecutableCodeSignatureVerifier.ts
+++ b/packages/electron-updater/src/windowsExecutableCodeSignatureVerifier.ts
@@ -1,9 +1,10 @@
 import { parseDn } from "builder-util-runtime"
 import { execFile, execFileSync } from "child_process"
 import * as os from "os"
 import { Logger } from "./main"
+import * as path from "path"
 
 // $certificateInfo = (Get-AuthenticodeSignature 'xxx\yyy.exe'
 // | where {$_.Status.Equals([System.Management.Automation.SignatureStatus]::Valid) -and $_.SignerCertificate.Subject.Contains("CN=siemens.com")})
 // | Out-String ; if ($certificateInfo) { exit 0 } else { exit 1 }
 export function verifySignature(publisherNames: Array<string>, unescapedTempUpdateFile: string, logger: Logger): Promise<string | null> {
@@ -46,10 +47,22 @@ export function verifySignature(publisherNames: Array<string>, unescapedTempUpda
             resolve(null)
             return
           }
           const data = parseOut(stdout)
           if (data.Status === 0) {
+            try {
+              const normlaizedUpdateFilePath = path.normalize(data.Path)
+              const normalizedTempUpdateFile = path.normalize(unescapedTempUpdateFile)
+              logger.info(`LiteralPath: ${normlaizedUpdateFilePath}. Update Path: ${normalizedTempUpdateFile}`)
+              if (normlaizedUpdateFilePath !== normalizedTempUpdateFile) {
+                handleError(logger, new Error(`LiteralPath of ${normlaizedUpdateFilePath} is different than ${normalizedTempUpdateFile}`), stderr, reject)
+                resolve(null)
+                return
+              }
+            } catch (error: any) {
+              logger.warn(`Unable to verify LiteralPath of update asset due to missing data.Path. Skipping this step of validation. Message: ${error.message ?? error.stack}`)
+            }
             const subject = parseDn(data.SignerCertificate.Subject)
             let match = false
             for (const name of publisherNames) {
               const dn = parseDn(name)
               if (dn.size) {
@@ -94,11 +107,10 @@ function parseOut(out: string): any {
     delete signerCertificate.Handle
     delete signerCertificate.HasPrivateKey
     // duplicates data.SignerCertificate (contains RawData)
     delete signerCertificate.SubjectName
   }
-  delete data.Path
   return data
 }
 
 function handleError(logger: Logger, error: Error | null, stderr: string | null, reject: (reason: any) => void): void {
   if (isOldWin6()) {

Vulnerable code:   delete data.Path
Fixed code: ---
"electron-updater": patch
---

fix: verify LiteralPath of update file during windows signature verification
import * as path from "path"
            try {
              const normlaizedUpdateFilePath = path.normalize(data.Path)
              const normalizedTempUpdateFile = path.normalize(unescapedTempUpdateFile)
              logger.info(`LiteralPath: ${normlaizedUpdateFilePath}. Update Path: ${normalizedTempUpdateFile}`)
              if (normlaizedUpdateFilePath !== normalizedTempUpdateFile) {
                handleError(logger, new Error(`LiteralPath of ${normlaizedUpdateFilePath} is different than ${normalizedTempUpdateFile}`), stderr, reject)
                resolve(null)
                return
              }
            } catch (error: any) {
              logger.warn(`Unable to verify LiteralPath of update asset due to missing data.Path. Skipping this step of validation. Message: ${error.message ?? error.stack}`)
            }