Medium watch ~ plausible CVSS 5.5 chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
5.5
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInappropriate implementation in App-Bound Encryption
DescriptionInappropriate implementation in App-Bound Encryption
ComponentApp-Bound Encryption
Sub-componentInternals>LocalDataProtection
Bug ClassLogic Error
Action tierwatch — information disclosure — security-relevant, below the RCE-class bar — factors set by independent review by AI
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Independent review by AIPlausible
The mapped commit may not contain the whole fix.
Reviewer notes: All file:line citations check out. Two unsupported assertions stated as fact: no pre-v3 protection (contradicted by PreProcessData at elevator.cc:83-91 and v2 key mention) and that v3 migration protects data (private, unverifiable).
A second, independent AI reviewer (not a human) re-derived this analysis from the fixing commit and the vendor advisory, looking for errors.
Tracker382234536
Fix commit06a2376446b5 (chromium/src) +19/-3
CWECWE-326
CVSS vectorCVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N
CISA KEVNot listed
CreditedAri Novick
Reported2024-12-04 (per vendor advisory)
Fix landed2025-07-22 (public git)
Disclosed2025-10-28
Patch gap98 days — fix public in source before the advisory

Background

App-Bound Encryption
Chrome on Windows protects data by sending it to a SYSTEM elevation service, which wraps it in two DPAPI layers: user context, then SYSTEM context (elevator.cc:116-143).
`Elevator::DecryptData`
the COM method that removes the SYSTEM DPAPI layer, then (impersonating the caller) the user layer, then validates the caller (elevator.cc:156-240).
`PreProcessData` / `PostProcessData`
Google Chrome-branded hooks that run on the plaintext inside the DPAPI layers (elevator.cc:83-91, 217-227). They are not defined in any file shown; they presumably come from the branded-only include chrome/elevation_service/internal/elevation_service_internal.h (elevator.cc:32-34) (inferred), which lives in a private repository that is not public, so this analysis does not describe their behavior.
Padding oracle
a side channel that reveals whether a tampered CBC ciphertext decrypts to correctly padded plaintext, which lets an attacker recover plaintext without the key. The reporter says the Windows DPAPI operational event log provides this signal (reporter’s claim).

Root Cause Analysis

Verify before building a PoC

This analysis is consistent with the fixing commit but not fully provable from it: part of the vulnerable path lives in code the fix commit does not touch. Confirm the trigger and the sink against the source before relying on it for exploit development.
What the commit does not establish: All file:line citations check out. Two unsupported assertions stated as fact: no pre-v3 protection (contradicted by PreProcessData at elevator.cc:83-91 and v2 key mention) and that v3 migration protects data (private, unverifiable).
Re-analyzed on 2026-10-03 against the actual fix commit 06a2376446b5. An earlier analysis was withdrawn because it had been written against a commit that was not the security fix (c4d7b188040c). Fix coverage caveat: v3 logic (PostProcessData/PreProcessData/InternalFlags) lives in private chrome/elevation_service/internal, which is not shown. Client re-encrypt is now off by default. Only affects Google Chrome branded builds. SYSTEM-context decrypt of caller blobs (elevator.cc:171) is unchanged.
In the pre-fix code, Elevator::DecryptData runs ::CryptUnprotectData under the service’s SYSTEM context on any caller-supplied BSTR (elevator.cc:164-175), and only afterwards impersonates the caller and runs ValidateData (elevator.cc:180-211). Any client can therefore make SYSTEM attempt DPAPI decryption of modified blobs. The reporter says that OS-side DPAPI event logging, which the encrypt path’s CRYPTPROTECT_AUDIT requests (elevator.cc:123, 139), turns this into a padding oracle; the logging behavior itself is outside the material (inferred). Recovering the SYSTEM-layer plaintext yields the inner user-DPAPI blob, which the low-privilege user can open in their own context (inferred). Pre-v3 data was not entirely unprocessed: pre-fix EncryptData already runs the branded PreProcessData on all plaintext (elevator.cc:83-91), and the commit message refers to an existing “v2 key (or earlier)”. What that pre-v3 processing does is private. That it did not withstand the oracle is only suggested by the fix forcing migration to v3 (inferred). The visible fix does not change this decrypt order: it replaces the default-constructed InternalFlags flags; with InternalFlags flags{.use_latest_encryption = true}; before the call to PostProcessData (elevator.cc:218-219). According to the commit message and test rationale, data under a v2-or-earlier key then sets post_process_should_reencrypt, and DecryptData returns kSuccessShouldReencrypt (elevator.cc:224-226, 239) so the browser re-encrypts it under v3 through EncryptData. What v3 actually does, and whether it defeats the oracle, lives in the private, non-public chrome/elevation_service/internal code (elevator.cc:32-34) and cannot be verified here. The same commit flips kAppBoundDataReencrypt to base::FEATURE_DISABLED_BY_DEFAULT (app_bound_encryption_win.cc:60-62), and that flag gates the browser-side re-encrypt (app_bound_encryption_win.cc:245-246), so migration of existing data rolls out through variations rather than being on by default.
Key insight
The elevation service decrypts arbitrary caller-supplied blobs with SYSTEM’s DPAPI key before checking who the caller is. The visible part of the fix does not change that order. It forces existing app-bound data to be migrated to a newer v3 scheme, which is implemented in private, non-public code that this analysis cannot inspect.

Attack Path

  1. Obtain app-bound ciphertext A low-privilege local process reads the app-bound-encrypted blob that belongs to its own user (inferred; how the data is stored is not shown).
  2. Use the service as a SYSTEM decryptor The process submits modified versions of the blob to Elevator::DecryptData, which runs ::CryptUnprotectData under SYSTEM before any caller validation (elevator.cc:171-175).
  3. Read a side channel The attacker tells padding failures apart from MAC failures through OS DPAPI event logging, as the reporter claims. This is outside the code shown (inferred).
  4. Recover the inner layer Recovering the SYSTEM-layer plaintext exposes the inner user-DPAPI blob, which the same user can decrypt (inferred). Whether v3 post-processing makes that result useless is not visible because the code is private.

Impact Assessment

A low-privilege local process belonging to the same Windows user could bypass App-Bound Encryption and recover protected browser data such as the cookie key. The advisory classes this as an inappropriate implementation, rated medium, not exploited in the wild. The reporter also claims the oracle works for any SYSTEM-DPAPI blob. The public DecryptData path does accept arbitrary blobs (elevator.cc:164-172), and this diff does not change that. Any protection from this change can therefore apply at most to Chrome’s own data once it is migrated to v3, and only in Google Chrome-branded builds (elevator.cc:217). Whether v3 actually defeats the oracle lives in private code and cannot be verified here; the diff itself only sets use_latest_encryption = true (elevator.cc:218).

Files Changed

  • chrome/browser/os_crypt/app_bound_encryption_win.cc
  • chrome/browser/os_crypt/app_bound_encryption_win_browsertest.cc
  • chrome/elevation_service/elevator.cc
  • testing/variations/fieldtrial_testing_config.json

Audit Directions

  • Privileged decrypt before authorization
    In DecryptData, the SYSTEM-context CryptUnprotectData (elevator.cc:171) runs before caller validation (elevator.cc:206). Check whether caller validation, or limits on repeated failures, can happen before any privileged cryptographic operation on caller-controlled input.
  • Distinguishable failure signals
    Check whether separate error codes (kErrorCouldNotDecryptWithSystemContext and others) or OS audit records (CRYPTPROTECT_AUDIT) let a caller tell different kinds of decryption failure apart. Any such distinction can act as an oracle.
  • Migration behind disabled flags
    Track when kAppBoundDataReencrypt is enabled. Until it is, data encrypted under the older key stays in its pre-v3 form on disk.
diff --git a/chrome/browser/os_crypt/app_bound_encryption_win.cc b/chrome/browser/os_crypt/app_bound_encryption_win.cc
index 17fa966..f496fbbf 100644
--- a/chrome/browser/os_crypt/app_bound_encryption_win.cc
+++ b/chrome/browser/os_crypt/app_bound_encryption_win.cc
@@ -59,7 +59,7 @@
 namespace features {
 BASE_FEATURE(kAppBoundDataReencrypt,
              "AppBoundDataReencrypt",
-             base::FEATURE_ENABLED_BY_DEFAULT);
+             base::FEATURE_DISABLED_BY_DEFAULT);
 }  // namespace features
 
 SupportLevel GetAppBoundEncryptionSupportLevel(PrefService* local_state) {
diff --git a/chrome/elevation_service/elevator.cc b/chrome/elevation_service/elevator.cc
index 1335df5..d2d1759 100644
--- a/chrome/elevation_service/elevator.cc
+++ b/chrome/elevation_service/elevator.cc
@@ -215,7 +215,7 @@
   }
   bool should_reencrypt = false;
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
-  InternalFlags flags;
+  InternalFlags flags{.use_latest_encryption = true};
   auto post_process_result = PostProcessData(plaintext_str, &flags);
   if (!post_process_result.has_value()) {
     return post_process_result.error();
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 6822a8879..f066efa 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -1232,6 +1232,21 @@
             ]
         }
     ],
+    "AppBoundDataReencrypt": [
+        {
+            "platforms": [
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "AppBoundDataReencrypt"
+                    ]
+                }
+            ]
+        }
+    ],
     "AppListLaunchRecorder": [
         {
             "platforms": [
diff --git a/chrome/browser/os_crypt/app_bound_encryption_win_browsertest.cc b/chrome/browser/os_crypt/app_bound_encryption_win_browsertest.cc
index 9ee4935..6337d6ae0 100644
--- a/chrome/browser/os_crypt/app_bound_encryption_win_browsertest.cc
+++ b/chrome/browser/os_crypt/app_bound_encryption_win_browsertest.cc
@@ -512,9 +512,10 @@
   std::string ciphertext;
   DWORD last_error;
   base::HistogramTester histograms;
+  elevation_service::EncryptFlags flags{.use_latest_key = true};
   HRESULT hr =
       EncryptAppBoundString(ProtectionLevel::PROTECTION_PATH_VALIDATION,
-                            plaintext, ciphertext, last_error);
+                            plaintext, ciphertext, last_error, &flags);
 
   ASSERT_HRESULT_SUCCEEDED(hr);
 
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/chrome/browser/os_crypt/app_bound_encryption_win_browsertest.cc b/chrome/browser/os_crypt/app_bound_encryption_win_browsertest.cc
index 9ee4935..6337d6ae0 100644
--- a/chrome/browser/os_crypt/app_bound_encryption_win_browsertest.cc
+++ b/chrome/browser/os_crypt/app_bound_encryption_win_browsertest.cc
@@ -512,9 +512,10 @@
   std::string ciphertext;
   DWORD last_error;
   base::HistogramTester histograms;
+  elevation_service::EncryptFlags flags{.use_latest_key = true};
   HRESULT hr =
       EncryptAppBoundString(ProtectionLevel::PROTECTION_PATH_VALIDATION,
-                            plaintext, ciphertext, last_error);
+                            plaintext, ciphertext, last_error, &flags);
 
   ASSERT_HRESULT_SUCCEEDED(hr);
Loading diff…

Original Bug Report

reported by [email protected]

AppBound Decryption with Padding Oracle Attack


Report description

AppBound Decryption with Padding Oracle Attack


Bug location

Where do you want to report your vulnerability?

Chrome VRP – Report security issues affecting the Chrome browser. See program rules

Which URL (or repository) have you found the vulnerability in?

https://github.com/chromium/chromium/tree/main/chrome/elevation_service, https://github.com/chromium/chromium/tree/main/chrome/browser/os_crypt


The problem

Please describe the technical details of the vulnerability

Hello Chrome VRP team :)

I found a vulnerability in the implementation of AppBound Encryption with the elevation service that would allow a low-privilege user to decrypt cookies and other sensitive data. I realize local attacks aren’t generally considered in the chrome threat model. But since the App Bound feature is meant specifically to handle local attacks, and since this attack impacts Windows security as a whole, I though it’s important enough to bring up.

The technique implements an attack called “Padding Oracle Attack,” which allows decrypting ciphertexts without a key under very specific circumstances. For the attack to work, two things are necessary. First, the encryption has to use a vulnerable type of encryption mode like AES-CBC, which is what DPAPI uses. The other thing required is a “Padding Oracle,” a black box that takes a ciphertext as input and returns it if the plaintext that matches the ciphertext has valid padding.

In this implementation, the “Padding Oracle” comes from the Windows Event Viewer in a channel called Microsoft-Windows-Crypto-DPAPI/Operational. This channel records events related to DPAPI. DPAPI blobs have serval fields, including the ciphertext and a signature, aka MAC, to ensure no tampering was done to the DPAPI blob.

If an attacker modifies the blob randomly before trying to decrypt it, then we will usually be able to see an error event in the event viewer that says the reason for failure is an invalid MAC. However, if an attacker modifies the ciphertext in the blob so that the padding of the plaintext is invalid, we can see a different error event where the reason for failure is unknown. This turns the event viewer into a “Padding Oracle” and allows an attacker to use the “Padding Oracle Attack” to decrypt a DPAPI encrypted blob.

The root cause of the issue is that the elevation service allows a low-privilege user to try to decrypt a DPAPI blob encrypted by SYSTEM. Typically, users can only try to decrypt blobs encrypted by the same user, so they don’t need the “Padding Oracle Attack.” If a user tries to decrypt a blob that was encrypted by a different user, such as SYSTEM, the key can’t be found, and then the “Padding Oracle Attack” can’t be implemented. It’s only in this hybrid situation that the attack is viable. An important thing to mention is that the attack does take several hours. The creation of the event logs is slow. The VM I tested this on took about a second for each log. In theory, decrypting each byte should take an average of a little over 2 minutes. But specifically when decrypting the app bound encrypted key, a lot of the plain text is known ahead of time (it’s just another DPAPI encrypted blob except this time for a low privileged user), so it’s possible to save some of the time by just assuming those values are fixed instead of decrypting them.

I’ll be attaching a zip file with a demo and the code is used to make it. Sorry I know it’s explicitly written not to upload a zip. But I was having trouble getting python to function as a COM client so the code is divided between python that does most of the work and a cpp program to do the COM requests to the elevation service. I also attached a video to show that it works but since it takes several hours I skip ahead a lot in the video.

Please briefly explain who can exploit the vulnerability, and what they gain when doing so

These issues can be exploited by any process running with low privileges. It allows them to bypass the AppBound Encryption mechanism. Furthermore, it allows a low-privilege user to decrypt anything encrypted with DPAPI from the SYSTEM user.


The cause

What version of Chrome have you found the security issue in?

127 stable and above

No

Choose the type of vulnerability

Permissions Bypass

How would you like to be publicly acknowledged for your report?

ari

View on issue tracker