I consider hunting for insider data theft to be the apex in user behavioral analysis. I recently gave a presentation on this at an internal conference that my team holds once a year. The talk was titled "How I spent my pandemic" and focused on the things that I've built, discovered and learned over these past months as they relate to this topic. I'd like to share some of those things in this post.
If you've paid attention to the many indictments handed down by the DoJ over the past few years you can see that espionage via insider theft is real and happens quite often. I don't see that companies are as well positioned to identify evidence of this type of theft. Some reasons for this I believe are that there are no public repositories of knowledge. No security companies blogging about cases they've investigated and nobody publicly talking about what works and doesn't work in the detection realm. I liken it to how the APT was viewed and discussed 10 years ago.
The end goal of insiders and many state sponsored external actors are the same, but how they get there can be very different. Generally speaking, there is no exploitation to gain access to a target network. No malware to maintain or facilitate further access. No internal recon, or the many other actions that are typically taken by external actors during active intrusions. To the contrary. Insiders will often use approved applications to target data they typically access on devices they are assigned. What we are left with are changes in their behavior. That's great. We have changes in behavior, but changes can happen all of the time for many different reasons. How do we find the changes that matter is the question. Here is where we begin.
I've written previous blogs around behavior anomalies at an individual level, but have not discussed measuring behavior of a population of people. The thought around this is that data theft in general should be an anomaly, so when this occurs the user should end up in a cluster that is far outside the norm. Here's the Splunk query I'm using to generate the numbers I'm using for scoring and clustering:
index=a_summary_index |stats values(phase_id) as phase_id count(phase_id) as detection_count dc(phase_id) as dc_phase_count values(source_id) as source_id dc(source_id) as dc_detection_count by user |eventstats count(user) as user_count by phase_id |nomv phase_id |nomv source_id |eval userhash=md5(user) |eval phase_hash=md5(phase_id) |eval source_hash=md5(source_id) |eventstats count(user) as user_source_count by source_id |table user,userhash,phase_hash,source_hash,phase_id,source_id,user_count,user_source_count,detection_count,dc_phase_count,dc_detection_count |eval user_count_mult=case(user_count=1, 200, user_count<5, 150, user_count<10, 50, user_count>=10, 0) |eval dc_phase_count_mult=case(dc_phase_count<2, 0, dc_phase_count>=2, 100) |eval dc_detection_count_mult=case(dc_detection_count>2, 100, dc_detection_count=2, 50, dc_detection_count=1, 0) |eval addedweight = (user_count_mult+dc_phase_count_mult+dc_detection_count_mult)
To explain the search:
index=a_summary_index |stats values(phase_id) as phase_id count(phase_id) as detection_count dc(phase_id) as dc_phase_count values(source_id) as source_id dc(source_id) as dc_detection_count by user |eventstats count(user) as user_count by phase_id |nomv phase_id |nomv source_id |eval userhash=md5(user) |eval phase_hash=md5(phase_id) |eval source_hash=md5(source_id) |eventstats count(user) as user_source_count by source_id |table user,userhash,phase_hash,source_hash,phase_id,source_id,user_count,user_source_count,detection_count,dc_phase_count,dc_detection_count
- Each detection is logged to a summary index so that I can search over past events.
- phase_id is a name given to a stage that the user is at in relation to data exfil
- source_id is the name of the search that generated the detection
- dc_phase_count is the distinct count of phases
- dc_detection_count is the distinct count of different detection names
- user_count is the count of users that were seen in a distinct "phase_id".
- nomv phase_id: converts multivalue field to a single value (used for hashing)
- nomv source_id: converts multivalue field to a single value (used for hashing)
- userhash is the md5 hash of the username
- phase_hash is the md5 hash of the distinct phases that the user was seen in
- source_hash is the md5 hash of the distinct detection names that was generated by the user
- user_source_count is the count of users with matching detections
|eval user_count_mult=case(user_count=1, 200, user_count<5, 150, user_count<10, 50, user_count>=10, 0) |eval dc_phase_count_mult=case(dc_phase_count<2, 0, dc_phase_count>=2, 100) |eval dc_detection_count_mult=case(dc_detection_count>2, 100, dc_detection_count=2, 50, dc_detection_count=1, 0) |eval addedweight = (user_count_mult+dc_phase_count_mult+dc_detection_count_mult)
- user_count_mult is a value given to numbers of users with pahse_id's. Fewer users = higher score
- dc_phase_count_mult is a value given to the count of a distinct phase
- dc_detection_count_mult is a value given to to the number of users that generated a detection name
- addedweight is the sum of the above values
I can then use kmeans to cluster the output:
The outlier in this case was cluster number 5 which generated a risk score of 400:
I feel that when a user begins to execute on their intent to steal data they will generate anomalous clusters of detections. These detections can be measured, scored and highlighted against the overall population of users. Looking for these higher scores is a great way to hunt for anomalous behavior patterns. This method could also be applied to external threats as well as an external attacker will likely generate anomalous behavior patterns when moving laterally within your environment.
No comments:
Post a Comment
Note: Only a member of this blog may post a comment.