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.
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.
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.
"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.
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.
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:
- 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.)
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)
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
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
mimikatz # sekurlsa::minidump lsass.dmp
mimikatz # sekurlsa::dpapi
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..]
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