脆弱性診断技術やサイバーセキュリティの最新情報を発信セキュリティブログ by イエラエセキュリティ

OOBE: Story of plaintext password in registry(CVE-2023-21726) 2023/04/26

OOBE: Story of plaintext password in registry(CVE-2023-21726) 2023/04/26



A vulnerability [1] (CVE-2023-21726) was found in CredUI (CredPackAuthenticationBufferW [2]) Windows API function that lead to a plaintext password leakage. While we will only discuss one example of possible attack, in theory any application that used Windows API CredPackAuthenticationBufferW (CRED_PACK_PROTECTED_CREDENTIALS, ...) could have saved plaintext password somewhere on the system.

Besides CredUI, we also would like to mention strange behavior of OOBE (Out-of-Box Experience [3]), that is trying to cache credentials of a newly created user account in registry (accessible by unprivileged Medium integrity user). This is what lead us to finding the problem in the first place.

Finding the password

It is common to applications to store their configuration data on a disk. Usually, it will be stored in some encrypted form. Algorithm may vary from DPAPI’s CryptProtectData data blob to some self-made encryption.

But occasionally, because of bad security model design or developer’s mistakes, the important data may end up being stored in a plaintext format.

That’s why, sometimes it’s tempting to scan raw storage/memory for some interesting strings including plaintext passwords. In most cases you will get false positive hits inside random junk of data, but once in a while you will also find something unexpected.

So, what we did, is just installed a fresh Windows and created first password protected account as was offered to us by OOBE. After scanning Virtual Machine’s disk drive, we found our newly created password as a plaintext in blocks allocated for following file: C:\Windows\system32\config\DEFAULT. And the corresponding registry key is (readable by unprivileged users):


Technical details (CredUI side)

Main problem was located in credui.dll. Despite user supplying CRED_PACK_PROTECTED_CREDENTIALS flag, no encryption was done for password whatsoever.

We can create a simple program to demonstrate this problem:

#include <windows.h>
#include <stdio.h>
#include <wincred.h>

#pragma comment(lib,"Credui.lib")

void HexTable(pCred, dwCredSize) {
    unsigned char *ptr = (unsigned char *)pCred;
    for (size_t i = 0; i < dwCredSize; i++) {
        if (i % 16 == 0) {
            printf("%08zx: ", i);
        printf("%02x ", ptr[i]);

        if (i % 16 == 15) {
            printf(" |");
            for (size_t j = i - 15; j <= i; j++) {
                printf("%c", isprint(ptr[j]) ? ptr[j] : '.');

int main() {
    DWORD dwCredSize = 0x1000;
    PBYTE pCred = (PBYTE)malloc(0x1000);
    if (CredPackAuthenticationBufferW(CRED_PACK_PROTECTED_CREDENTIALS, (LPWSTR) L"UserName", (LPWSTR)L"P@ssw0rd123", pCred, &dwCredSize)) {
        HexTable(pCred, dwCredSize);
    } else {
        printf("CredUnPackAuthenticationBufferW LastError=%08x", GetLastError());

Let’s run it before and after a fix:

Fig 1. Test program output for unpatched system.
Fig 2. Test program output for protected system.

As we can see password is now seems encrypted. And it is actually done by internal call to analog of CryptProtectMemory (CRYPTPROTECTMEMORY_SAME_LOGON) [4], which prevents users with LUID not equal to the one which used during encryption from decrypting the password. And even more than that, after reboot password is becoming impossible to decrypt, not even in the same security context (because global key values used for encryption are randomly generated by cng.sys on computer start).

Current call flow:

CredPackAuthenticationBufferW (cryptui.dll)
| - CredPackKerbBufferFromStrings (cryptui.dll)
| -- CredProtectEx (sechost.dll)
| --- CredpEncryptAndMarshalBinaryBlobEx (sechost.dll)
| ---- CredpEncodeSecretEx (sechost.dll)
| ------ SystemFunction040 / RtlEncryptMemory(RTL_ENCRYPT_OPTION_SAME_LOGON) (cryptbase.dll)
| ------- NtDeviceIoControlFile ("\\??\KSecDD", 0x39001E, ...)
| ....
| -------- CngEncryptMemoryEx (cng.sys)

You can find a very detailed description on how CngEncryptMemoryEx works in [5] (flare-on, KeePass writeup).

We can also highlight the place in cryptui.dll which received the fix:

Fig 3. CredUI, CredPackKerbBufferFromStrings before fix.

After fix:

Fig 4. CredUI, CredPackKerbBufferFromStrings after fix.

So now password of the first local administrator can be considered securely encrypted for use during first computer start.

Technical details (OOBE side)

Here is an example of what we call UserOOBE (Windows 11):

Fig 5. OOBE (Out-of-box experience).

This screen appears right after Windows installation and allows user to set up account as well as apply some other configurations.

It is hosted in explorer.exe process and implemented by UserOOBE.dll library. Everything is done under defaultuser0 account.

Notice: We are only looking in now non default installation flow (as of Windows 11) where user creates local account and rejects offer to use Microsoft Account (oobe\\bypassnro).

After user inputs password and security questions, a WinRT call is made to CloudExperienceHostBroker::Account::LocalAccountManager::CreateLocalAccountWithRecoveryKindAsync in CloudExperienceHostBroker.dll hosted by process RuntimeBroker.exe.

This function creates a new administrative account with specified details and also trying to cache the plaintext password for non obvious reason (we may assume it is somehow related to OneDrive account registration as one of related interfaces is called IOOBEOneDriveOptin).

Fig 6. CloudExperienceHostBroker::Account::LocalAccountManager::s_PackAndCacheAuthBufferAsLocalSystem function.

Caching is done via calls to in-proc COM server implemented in msoobeplugins.dll.

Fig 7. PackAuthBuffer function.

PackAuthBuffer is effectively just doing a call to CredPackAuthentificationBufferW with CRED_PACK_PROTECTED_CREDENTIALS. At the time of report, this function did not encrypt provided credentials, only converted them to serialized format.

Fig 8. CredPackAuthentificationBufferW output.

Finally, resulting buffer saved into registry by calling CacheAuthBuffer.

Fig 9. CacheAuthBuffer function.
Fig 10. Class initialization function inside msoobeplugins.dll reveals path in registry.

At the end we were unable to locate any usage of this stored password.

Official mitigation

We already showed a general CredUI fix which will prevent future leakage of credentials. Still the question remains on what will be with the stations that already have saved plaintext password in the registry key.

Here is where new OOBE-Maintenance.exe binary comes into play.

It seems that security update packages from January 2023 create a scheduled task \Microsoft\Windows\Registry\OOBE-Maintenance which will execute this binary once.

The logic of binary is simple:
1. Remove any cached passwords in LocalSystemAuthBuffer
2. Save DeviceMigitationStatus 1
3. Remove scheduled task OOBE-Maintenance

Fig 11. Logic inside OOBE-Maintenance.exe
Fig 12. Registry before OOBE-Maintenance.exe execution.
Fig 13. Registry after OOBE-Maintenance.exe execution.

For a fresh patched installations password is still stored in that registry key, but is now encrypted and from the start accompanied by value DeviceMigitationStatus=1.

Fig 14. Registry after installing fresh patched installation (password is encrypted).

Testing and exploitation

・To get the value in HEX string:

[string]::join(' ',((Get-Item -Path
Registry::HKEY_USERS\.Default\SOFTWARE\Microsoft\Windows\CurrentVersion\OOBE\Broker).GetValue('LocalSystemAuthBuffer')| ForE
ach{'{0:x2}' -f $_}))

・To get the value in ASCII string:

[System.Text.Encoding]::ASCII.GetString((Get-Item -Path Registry::HKEY_USERS\.Default\SOFTWARE\Microsoft\Windows\CurrentVersion\OOBE\Broker).GetValue('LocalSystemAuthBuffer'))


• 2021/12/24 - Report create on MSRC
• 2022/03/23 - MSRC informed us that the fix is expected after 2023/01/11
• 2023/01/23 - Fix released with CVE-2023-21726


[1] https://msrc.microsoft.com/update-guide/vulnerability/CVE-2023-21726
[2] https://learn.microsoft.com/en-us/windows/win32/api/wincred/nf-wincred-credpackauthenticationbufferw
[3] https://learn.microsoft.com/en-us/windows-hardware/customize/desktop/customize-oobe-in-windows-11
[4] https://learn.microsoft.com/en-us/windows/win32/api/dpapi/nf-dpapi-cryptprotectmemory
[5] https://github.com/eleemosynator/writeups/blob/master/flare-on-6/12%20-%20help/readme.md#7-the-shortening-of-the-way