Stale Account Usage

Description

Detect long-inactive accounts that suddenly become active.


Use Case

Insider Threat, Security Monitoring

Category

Insider Threat, IAM Analytics, Account Compromise

Security Impact

There are two primary scenarios where the activation of a stale account can be a concern. The first, and usually the first to come to mind, is a former employee or contractor who wants to gather sensitive information or compromise systems. They may check to see if their old user account was accidentally left active, or if a service account that they managed did not have a password reset (or even if there were API keys they had access to that weren't terminated). The other, is that an adversary does a brute force to find some accounts laying around, often service accounts that aren't actively used and so no admin had the opportunity to think "say, we probably shouldn't use admin / changeme on our production system." In either case, you likely want to know.

Alert Volume

Low (?)

SPL Difficulty

Medium

Journey

Stage 2

MITRE ATT&CK Tactics

Privilege Escalation
Persistence
Initial Access

MITRE ATT&CK Techniques

Valid Accounts

MITRE Threat Groups

APT18
APT28
APT3
APT32
APT33
APT39
APT41
Carbanak
Dragonfly 2.0
FIN10
FIN4
FIN5
FIN6
FIN8
Leviathan
Night Dragon
OilRig
PittyTiger
Soft Cell
Stolen Pencil
Suckfly
TEMP.Veles
Threat Group-1314
Threat Group-3390
menuPass

Kill Chain Phases

Actions on Objectives

Data Sources

Windows Security
Authentication

   How to Implement

Building this detection is relatively straightforward. The actual data source is authentication logs, and the search automatically populates that lookup with content. The only step you'll need to take is to create a lookup called account_status_tracker, and have authentication data in Common Information Model format (or by default we'll just use Windows logs as well).

There is on key business decision that you must make to implement this detection -- the definition of stale. Most commonly we hear about the desire to detect logins from accounts that are more than 90 days old, though some organizations will also look at 45 days. Generally speaking you want to target more than once per month, as some services or tasks will only run that often.

One good note is that this search will automatically store usernames in lowercase format, as lookups are case sensitive by default (adjustable via transforms.conf). If you have case sensitive usernames (e.g., jsmith and Jsmith are different users) then.. first, I'm sorry to hear how you suffer.. but you can remove the lowercase eval function.

   Known False Positives

This search doesn't have false positives in the sense of accounts where the search would alert even though the account had recently been active. However, there can be scenarios where this alerts and it is not an account compromise or other malicious act. Particularly when this is a new detection for your organization, you may find accounts that are only active once every month, once every quarter, or even once per year (often associated with closing out the financial quarter / year). Beyond periodic accounts, the most likely scenario for a benign alert will be an employee who is on long term leave.

   How To Respond

When this search fires, your first instinct should be to look for legitimate cause. See the Known False Positives for more detail. Whenever a service account becomes active suddenly, your best bet is to reach out to the service owners to determine whether they expected it while simultaneously looking to ensure that the systems where that account is active (and/or the actions being taken if you have that visibility) are what would be expected. For suspicious scenarios or high risk services you may also wish to look for employees who left under unfriendly scenarios, who might have left a backdoor. For users where this fires, it should be relatively straightforward to see whether they still work at the company and if they do not, reach out to their manager to determine if there was a reason the account remained (or consult any notes about their separation).

   Help

Stale Account Usage Help

This example leverages the simple search assistant. Our dataset is an anonymized collection of authentication logs. For this analysis, we are creating a lookup with the historical record of the first and last recorded login time per user. When a user logs in on a day, but the last login we tracked was from months earlier, we create an alert.

SPL for Stale Account Usage

Demo Data

First we bring in our basic demo dataset. In this case, a list of anonymized Windows events. We're using a macro called Load_Sample_Log_Data to wrap around | inputlookup, just so it is cleaner for the demo data.
Next we calculate the earliest and latest time in our time range -- this is mostly for convenience of the search itself. You could also add additional fields (e.g. the hosts), but make sure to remove those fields later before outputting to the lookup.
Next we pull the historical baseline in, via lookup. We rename the baseline fields as prior_* so that we can distinguish between what we just saw, and what we've seen before.
Now we filter for just users where the latest login our baseline (effectively, the last login we saw *before* this search) was more than 60 days earlier.
Because we want to present usable results to the SOC, we provide an explanation of what we saw.
And we convert epoch times into friendly timestamps. These results will now show up to the user (or be made a notable event, etc.). You might note that in the demo version, we never actually update the lookup with the updated data. That's because we want you to be able to run this search multiple times -- check out the live or accelerated version of this search for a fully fleshed out version of this search.

Live Data

First we bring in our dataset of success Windows Security logins. We could use the same search for other datasets as well.
Next we calculate the earliest and latest time in our time range -- this is mostly for convenience of the search itself. You could also add additional fields (e.g. the hosts), but make sure to remove those fields later before outputting to the lookup.
Now we go off the grid. We're using an undocumented search command called multireport here, which effectively runs the end of your search multiple times, and appends each result. It's tricky, but powerful!
multireport Search One: multireport requires that you use a | stats command (or similar reporting command). We don't need to do any transformation of the data, so we are just doing a stats values(*) as * by user to meet the requirement.
multireport Search One: Next we pull the historical baseline in, via lookup. We rename the baseline fields as prior_* so that we can distinguish between what we just saw, and what we've seen before.
multireport Search One: Now we filter for just users where the latest login our baseline (effectively, the last login we saw *before* this search) was more than 90 days ago.
multireport Search One: Because we want to present usable results to the SOC, we provide an explanation of what we saw.
multireport Search One: And we convert epoch times into friendly timestamps. These results will now show up to the user (or be made a notable event, etc.), as this multireport search now ends.
multireport Search Two: We're now in the second search. This search functionally sits right after the stats command on line 2 (as if multireport search one didn't exist). The point of this portion is to update our baseline, so we don't have to maintain a separate search. We start that by appending our baseline to our recent results.
multireport Search Two: Now we can re-calculate the true count, earliest, and latest by user (including both our baseline and recent results).
multireport Search Two: outputlookup will update the baseline on disk with the most recent results, for the next time that we use this search.
multireport Search Two: We don't actually want any results from this portion to be returned to the user, so we check the value of a non-existent field -- effectively clearing out any results, and leaving just any results from multireport search one.

Accelerated Data

First we bring in our dataset of success authentication events. You could also add additional fields (e.g. the hosts), but make sure to remove those fields later before outputting to the lookup.
We rename Authentication.user to user just to make the search easier to read and easier to write.
Now we go off the grid. We're using an undocumented search command called multireport here, which effectively runs the end of your search multiple times, and appends each result. It's tricky, but powerful!
multireport Search One: multireport requires that you use a | stats command (or similar reporting command). We don't need to do any transformation of the data, so we are just doing a stats values(*) as * by user to meet the requirement.
multireport Search One: Next we pull the historical baseline in, via lookup. We rename the baseline fields as prior_* so that we can distinguish between what we just saw, and what we've seen before.
multireport Search One: Now we filter for just users where the latest login our baseline (effectively, the last login we saw *before* this search) was more than 90 days ago.
multireport Search One: Because we want to present usable results to the SOC, we provide an explanation of what we saw.
multireport Search One: And we convert epoch times into friendly timestamps. These results will now show up to the user (or be made a notable event, etc.), as this multireport search now ends.
multireport Search Two: We're now in the second search. This search functionally sits right after the stats command on line 2 (as if multireport search one didn't exist). The point of this portion is to update our baseline, so we don't have to maintain a separate search. We start that by appending our baseline to our recent results.
multireport Search Two: Now we can re-calculate the true count, earliest, and latest by user (including both our baseline and recent results).
multireport Search Two: outputlookup will update the baseline on disk with the most recent results, for the next time that we use this search.
multireport Search Two: We don't actually want any results from this portion to be returned to the user, so we check the value of a non-existent field -- effectively clearing out any results, and leaving just any results from multireport search one.

Screenshot of Demo Data