Weak DPAPI encryption at rest in NordVPN

The NordVPN client leverages a DPAPI to save their user login credentials, but this makes the credentials vulnerable.

Weak DPAPI encryption at rest in NordVPN

The NordVPN  Windows client leverages DPAPI (Data Protection API) to effortlessly save the login credentials of its users and this is a suitable way for developers to avoid common pitfalls regarding cryptographic implementation and key management. The problem with DPAPI is that it renders the credentials vulnerable to a trivial plain-text password recovery. The goal of this article is to provide a walk-through on how easy is to dump the VPN credentials in a post exploitation scenario with the help of the mighty Mimikatz.

Analyzing NordVPN Encryption & Decryption Routines

I was playing with what I consider to be the best .NET debugger, dnSpy, and the first application I happened to open was NordVPN client v6.23.11.0. After a quick code review I decided to focus on the login routine that auto-connects the VPN at startup.

This routine resides in the third-party DLL 'Liberation.OS', which is developed in .NET.

Cryptographic routine implementation in NordVPN

Have a look for the documentation available of DPAPI ProtectData.Unprotect.

Overview of DPAPI

DPAPI is widely adopted in Windows (e.g. Credential Manager, IE, Chrome, Wi-Fi, etc.) because it abstracts the logic behind symmetric encryption by providing proven cryptographic algorithms (by default AES256 with SHA512 and PBKDF2), and by binding the encryption key to a specific scope: user or machine.

This key is referenced by a GUID and, in turn, stored encrypted in a 'master key file', which is named with the GUID value itself (things will become clearer). Moreover, the 'master key files' themself are encrypted with another secret derived from the SHA1 password hash of the user (user scope), or a secret linked to the system (local machine scope). To polish it off handsomely additional entropy can be provided by an optional parameter, but unfortunately, NordVPN does not use such option.

Digging into the DPAPI implementation is out of scope in this post. For well documented posts on the topic, have a look at the Reference section at the bottom. Later in this article more details will be shown about how to successfully dump "online" and decrypt offline the encrypted credentials with the help of Mimikatz.

Back to NordVPN client, as we can see the option 'LocalMachine' is the least restrictive and I was surprised that this was the default value used by NordVPN.

DPAPI different scopes explained

"The protected data is associated with the machine context. Any process running on the computer can unprotect data. This enumeration value is usually used in server-specific applications that run on a server where untrusted users are not allowed access."

Finding the encrypted DPAPI blob containing the credentials

We know that it will be trivial to recover the credentials (email address and password) but we actually still do not know where this credentials are stored at rest. Looking at the caller of the encryption/decryption routines the credentials are saved in a file along with other auto-connects settings. I do not like guessing and I resolved to use a more methodological approach relying on ProcMon.

Location of the configuration file containing the VPN credentials
Extract of 'user.config' file containing email and password stored in encrypted format

It is interesting to note that, even if the client was heavily obfuscated, it would have just been possible to find the 'user.config' file and identify that 'System.Security.Cryptographic.ProtectedData' methods was used from the base64 prefix "AQAAANCM...", which is the universal 'prepended string' of DPAPI encrypted blob (due to the dpapi blob headers starting with a 'costant value').

Realtime decryption on target system with custom script

DPAPI is implemented in such a way that the decryption is 'expected' to be performed on the exact machine on which the the encrypted file is stored. Running a decryption script on the target machine (with any user, even a low privileged 'Standard User') trivially allows to retrieve the clear-text credentials.

2-cent script to retrieve the clear-text value of email and password

Basically this means that being saved as clear-text in a file or being encrypted with DPAPI guarantees the same level of confidentiality in a post exploitation scenario if no additional entropy is specified as second parameter to 'ProtectData.Protect'.

Offline decryption with ProcDump and Mimikatz

Ok, but at least it is not possible to decrypt  the files in another system once they have been ex-filtrated, right? And we should assume that when handling DPAPI protected data we must immediately decrypt it one-shot while still having access to the compromised target?

The answer is clearly no if we have administrative privileges. Under such favorable condition it is possible to achieve the same result as above. Here are the detailed steps considering plaintext retrieval of the email address:

  1. ex-filtrate the 'user.config' file from the directory of the target user

2. decode the base64 blob to later analyze its DPAPI headers (that are useful to know the GUID of the 'master key' file used, the cryptographic algorithm, the hashing function, etc.)

hexadecimal encrypted blob

3. dump all the keys stored under C:\Windows\System32\Microsoft\Protect (in this path the master keys of the system are stored and in the LocalMachine scope exactly one of these was used)

This keys are the ones in use by DPAPI for LocaMachine scope

We do not know yet which one was used for encrypting the DPAPI password, probably it is the one specified in the 'Preferred' file (therefore {37..bbe}) but to know for sure we just need to parse the headers of the DPAPI encrypted blob with mimikatz (by executing mimikatz from a machine different from the target system):

mimikatz# dpapi::blob /in:C:\\Windows\\Temp\\yyy\\encrypted_email.bin
Mimikatz parses the blob and retrieves from the metadata the GUID of the 'master key' file necessary to decipher the email address

As reported by Mimikatz in the screenshot above, we confirm that the 'master key file' named '37..bbe' was used for this particular encrypted blob.

4. dump the lsass.exe memory with Procdump and retrieve  from the this dump the key stored inside 'master key file' directly with mimikatz (executing mimikatz from a machine different from the target system)

> procdump64.exe -ma lsass.exe lsass.dmp
Procdump to dump the memory of lsass.exe process
mimikatz # sekurlsa::minidump lsass.dmp
mimikatz # sekurlsa::dpapi
Retrieving the master key password that encrypts the 'master key file' with GUID {37..bbe}

5. decrypt the email address by providing to mimikatz the encrypted blob and the decrypted key obtained from the 'master key file' with GUID {37..bbe}

mimikatz # dpapi::blob /in:ENCRYPTED_BLOB /masterkey:db[..snipped..]
email address successfully decrypted offline

Obviously the procedure for the password and encrypted settings is the same.

Final thoughts

DPAPI is a valid technology for encryption at rest thanks to its proven cryptographic implementation. However, since it does not address properly the key management problem, as soon as the user account or system is compromised, its secrets can be easily recovered. To mitigate this pitfall, in my opinion, the applications should always make use of  the 'entropy' parameter. In addition, properly obfuscating the .NET executable is a must to raise the bar for reverse engineering, otherwise the entropy value can still be immediately recovered (be it universally hardcoded in the executable, stored in a registry key, derived from a configuration of the system, from the email address, or through other contrived logic).

Kudos to the authors behind dnSpy and Mimikatz.

Addendum

All the post focused on the scenario of a remote attacker with high privileges on the target system.

@gentilkiwi has kindly highlighted that it is also possible to retrieve the DPAPI_SYSTEM SECRETS (stored in the registry) with a disk forensic approach if the drive is unencrypted.
Under such favorable condition, it is trivial to retrieve both the file containing the credentials and the keys that encrypt them.For his PoC with the detailed steps on how to do it smoothly with his Mimikatz, please have a look at the screenshots available in his twitter thread: https://twitter.com/gentilkiwi/status/1178796512580702208

Special thanks to @gentilkiwi for the feedback on this post!

References

http://2018.offzone.moscow/getfile/?bmFtZT0xMi0wMF9XaW5kb3dzX0RQQVBJX1Nla3JldGlraS5wZGYmSUQ9NDEy

https://github.com/gentilkiwi/mimikatz/wiki/module-~-dpapi

https://github.com/0xd4d/dnSpy

The awesome image used in this article is called Yellow Dumpster and it was created by Chi Birmingham.