One of the most ubiquitous questions in DFIR, is the one that goes like “Has the user logged into this account with administrative privileges?”. This question has two parts. We will focus on the first one. One of the first places that I would normally dive into to determine if the user has logged into any account, would be Google Chrome’s “Login Data” database (if Google Chrome was present of course :P). I am sure that many DFIR forensicators out there, would do exactly the same. “Login Data” database, was my sure victory. I thought it was trivial to answer that question. I couldn’t be more wrong.
As you may have already experienced as a user, when you use Google Chrome to login to your account (any account you have), it prompts you with a message box, like the aforementioned one:
![](https://atropos4n6.com/wp-content/uploads/2020/09/popup2-1.png)
As you can see, you have three options, namely: 1) “Save” (a.k.a. Remember Me), 2) “Never” and 3) Click on the “x” button. Each one of them, informs Google Chrome to do something different. But also, each one of them, leaves a different trace inside “Login Data” database. We will explore all of these options in this research. The so-called “Login” feature of Google Chrome is extremely useful, as it offers to “remember” the user’s credentials, saving him the trouble of re-typing them, each time he wants to login to his account. But what happens, when the user enters invalid credentials? This question is the core of our today’s quest! Let’s start with our setup and our scenario.
All of my tests were run on a VM with Window 10. As far as the software I used, it is listed here:
As always the scenario was pretty basic. This time I tried:
Let’s see what did we find.
To start with, if you are trying to locate where Google Chrome store its files (files of forensic value), try looking at this path “C:\Users\<username>\AppData\Local\Google\Chrome\User Data\Default“. In this directory, you will find (among other super important artifacts) a database (SQLite 3 format) named “Login Data”. This is the database we will analyze here.
This database has 2 tables that hold information which a DFIR forensicator might use (it has more tables but not all of them are forensically useful). The 1st one we will examine is named “stats“. The “stats” table has 4 columns as you can see below:
![](https://atropos4n6.com/wp-content/uploads/2020/09/stats2.png)
This table is populated when a user choose Option 3 at the Message-Box (See Image 1). Let’s see an example. I have put in my correct credentials and logged into my Netflix account. When the Message-Box appeared, I clicked on the “x” button. A entry created in the “stats” table like this:
![](https://atropos4n6.com/wp-content/uploads/2020/09/stats3-1.png)
In the “origin_domain” field, you can see Netflix domain. In the “username_value”, my username (the username associated with my account) is now populating this field. As it is the first time I clicked on the “x” button, “dismissal_count” is now 1. Lastly, the “update_time” holds the timestamp of my login action. Hmm, easy peasy so far. But, what do these columns store exactly and how (if at all) they get updated/increased? After some stress-testing, I was able to answer that question:
Column Name | Type of Information it Stores |
---|---|
origin_domain | The domain where the user entered his credentials and logged in (or more correctly, “tried” to login). If another user (different username) tries to login to the same domain, a separate entry will be created in the stats table. |
username_value | The username that the user entered, when he tried to login to the domain. Again, if another user (different username) tries to login to the same domain, a separate entry will be created in the stats table. |
dismissal_count | How many times the user has clicked on the “x” (Option 3), when trying to login with the same username. If there is a 2nd time that the user tries to login with the same username and clicks on the “x” afterwards, the dismissal_count increases by one. However, it will not go higher than 3. If a user tries to login with the same username and clicks on the “x”, more than 3 times, the browser will no longer show the Message-Box and as a consequence this number, along with update_time do not get increased/updated. |
update_time | The last time the user has clicked on the “x” (Option 3), when logging with the same username. The time here is in “Google Chrome format” (You can use DCODE an open source tool to perform any conversions Link: https://www.digital-detective.net/dcode/). If the user tries to login again with the same username and clicks on the “x”, the update_time gets updated to the timestamp of that action. However, it will stop updating after the 3rd time the user tries to login with the same username and click on the “x”. |
Some points to take into consideration when dealing with this table are:
And with that being said, let’s move to the second table named “logins”.
With one option completely analyzed, there are 2 more to go. As you may know (or suspect), the other two options the user has when he tries to login to his account, are related to the table “logins“. This table has many columns. I didn’t manage to determine how each one of them operates, but I will try my best. Some of these columns are: “origin_url”,”action_url”,”username _element”,”username_value”,”password_element”, “password_value”,”signon_realm”,”date_created”,”blacklisted_by_user” ,”times_used”,”id”,”date_last_used”.
I will start with analyzing artifacts related to Option 2 and leave artifacts of Option 1 for last. If a user choose Option 2 “Never”, then a specific entry will populate “logins” table. Let’s see an example:
I logon successfully to my account in Outlook and when the Message-Box appeared, I chose Option 2 “Never”. Now if we open “Login Data” database, table “logins” will show:
![](https://atropos4n6.com/wp-content/uploads/2020/09/image-1.png)
As we can see, no password or at least username (as in “stats” table) populates the database. Instead, the only fields that get populated (protobufs are excluded here) are columns “signon_realm”,”date_created” and “blacklisted_by_user”, as seen below:
![](https://atropos4n6.com/wp-content/uploads/2020/09/image-2.png)
Well, you can easily tell what these columns store, but I will write it down anyways:
Column Name | Type of Information it Stores |
---|---|
origin_url | The domain where the user tries to login. |
signon_realm | The domain (or a webpage related to the login feature of that domain), |
date_created | The exact timestamp (also in Google Chrome format) when the user chose Option 2 “Never” at the Message-Box. |
blacklisted_by_user | True or False (1 or 0). Of course, if user choose Option 2, this value will be 1. |
A couple of points to keep in mind here:
Now that Option 2 is analyzed too, it’s time to get our hands on Option 1 “Save”. Better see how this works with an example. I logon to my Instagram account and chose Option 1 when the Message-Box appeared. As a result, this entry populated “logins” table and here is a glance in its contents:
![](https://atropos4n6.com/wp-content/uploads/2020/09/tempsnip.png)
Much more details than option 2, obviously. There are some columns that also store values in this scenario, but are not visible in the above screenshot. In general, when a user tries to login in his account and choose Option 1 “Save”, these details populate “logins” table:
Column Name | Type of Information it Stores |
---|---|
origin_url | The domain where the user tries to login. |
action_url | The domain (or a webpage related to the login feature of that domain), |
username_element | This value is domain-specific and depends on how the domain has named “user input” field in its code. So we can see various values like “username”, or “email”, or “identifier”. |
username_value | The username of the user who tried to login to his account will be stored. Different usernames in the same domain, will create separate entries. |
password_element | This value is domain-specific and depends on how the domain has named “password input” field in its code. So we can see various values like “password”, or “pass”, or “session[password]”. |
password_value | Here the password of the user who tried to login to his account will be stored. The password will not be in plain text but in blob format. You can use an open source tool to extract the plain text value of the password, name “Chrome Pass View” (Link:https://www.nirsoft.net/ utils/chromepass.html). |
signon_realm | The domain (or a webpage related to the login feature of that domain), |
date_created | The exact timestamp (also in Google Chrome format) when the user chose Option 1 “Save” at the Message-Box. |
blacklisted_by_user | True or False (1 or 0). Of course, if user choose Option 1, this value will be 0. |
id | The identifier of the specific entry in the “logins” table. Each increases the id by one (also starting from one). |
date_last_used | The last time when the user tried to auto-login (as his credentials are remembered) with this username to his account. Each time the tries to login with the same username and password, this timestamp gets updated. |
times_used | I believed that this field would get updated each time the user tries to auto-login with a stored username and password. However, this is not the case. Sometimes, when I tried to login (either successfully or not) to my account (where I already had my credentials stored in this table), it would get updated, but some other times it wouldn’t. I suspect that it is domain specific somehow. |
Some logical assumptions and key points to remember:
We finished analyzing all three Options. We have proven that these entries in “Login Data” do not mean necessarily that the user has successfully logon to his account. The user may have used illicit or false credentials and he wouldn’t have gotten access to this account, but still entries would be found inside this database. This may lead investigators to make wrong assumptions, as to if the user has successfully logon. However, everything is not lost and we can still prove in some cases that the user has successfully logon to his account, using the “Login Data” database. Let’s see when this happens.
During my tests, I visited well-known webpages and tried to login to my accounts (both successfully and not), in order to check whether or not “Login Data” database can help determine that the user has logon to his account. What I found out is that entries inside “Login Data” database can still prove that a user has logged in to his account. It basically depends solely on the webpage’s code implementation. Using false credentials, some webpages returned a response from the server that triggered Message-Box to appear and therefore leaded entries to be created and false assumptions to be made, while others didn’t returned that response and prevented Message-Box from appearing.
To sum up, I will write down which domains do not trigger Message-Box when trying to login with false credentials. Therefore, when you find entries inside the “logins” table or even “stats” table for these domains, you can safely make the assumption that the user logged in successfully to his account. On the other hand, I will write which domains still trigger Message-Box even when a user tries to login with false credentials to his account. Therefore, if you see these domains in the following tables, you should avoid make the assumption that the user logged in to his account successfully.
Domains that are safe to make assumptions for user’s successful logon (do not trigger Message-Box when using false creds) |
---|
https://www.facebook.com/ |
https://www.instagram.com/ |
https://www.login.live.com/ |
Domains that are NOT safe to make assumptions for user’s successful logon (trigger Message-Box when using false creds) |
---|
https://twitter.com/login |
https://accounts.google.com/signin/v2/challenge/pwd |
Keep in mind that not only “Login Data” database can help you with determining user successful logon to his account. Cookies play a big role too. For example, I logged in my Dominos Pizza account (got hungry after this research) and chose Option 2 “Never” at Message-Box. I closed the browser and then re-opened it and visited dominos.com again. Guess what? I was already logged in and ready to go. How can that be? Well, when I entered my credentials, I forgot checked the check-box “Remember Me” underneath. This let dominos create a beautiful cookie, which would have me authenticated for some time. But demystifying Chrome Cookies, will have to wait for some time.
To close this huge post, I would like to underline what takeouts should someone take from this post, about Chrome Forensics and more particularly “Login Data” database:
I hope you liked this post. I enjoyed myself during this research. I learned a lot. Even if forensic analysis for this browser, is not something new for the DFIR community. If you liked this post, do not forget to share it with others who might find it useful. We covered a lot in this post. But there is more there of course, as always. For example, what happens to the “Login Data” database, when a user is logged in Google Chrome? Stay tuned to find out! DFIRing is fun!