Sitemap
Detect FYI

Threat Detection Engineering and DFIR Insights

Threat Hunting - Suspicious User Agents

14 min readJan 1, 2024

--

Press enter or click to view image in full size

What is a User-Agent ?

A User-Agent string is a line of text that a browser or application sends to a web server to identify itself. It typically includes the name and version of the browser/application, the operating system, and the language. It’s constructed as a list of product tokens (keywords) with optional comments that provide further detail. Tokens are typically separated by spaces, and comments are enclosed in parentheses. Each part of the User-Agent string helps the server determine how to deliver content in a compatible format for the client’s software environment.

Back to the early days of the internet when browsers were competing for market share, it was straightforward, but as competition increased, browsers started to mimic each other’s strings to bypass compatibility issues. For example, Mozilla’s format, “Mozilla/5.0 (…)”, became a standard prefix for many browsers, regardless of their actual connection to Mozilla.

A classic example of how user-agents have been manipulated for broader web compatibility: Opera faced compatibility issues because some websites were optimized only for popular browsers like Internet Explorer. To ensure compatibility, Opera began to include the string “MSIE” (indicating Microsoft Internet Explorer) in its User-Agent, alongside its own identifier. This way, Opera could access websites that were exclusively designed for IE users.

These practices led to the complex and sometimes misleading User-Agent strings we see today

Example:

Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36

  • Mozilla/5.0: This is a general identifier used for compatibility (it has no real meaning anymore)
  • (Windows NT 10.0; Win64; x64): This part specifies the operating system as Windows 10, 64-bit edition, on an x64-based processor.
  • AppleWebKit/537.36: It signifies that the browser uses the AppleWebKit rendering engine, which is responsible for how web content is displayed.
  • (KHTML, like Gecko): This indicates that the browser is compatible with both KHTML and Gecko rendering engines, enhancing cross-browser compatibility.
  • Chrome/120.0.0.0: This specifies the browser as Chrome and gives the version number, which in this case is 120.0.0.0.
  • Safari/537.36: The inclusion of Safari with the same version number as AppleWebKit suggests compatibility with Safari's rendering standards.

For more detailed examples and variations used by different browsers, you can refer to the information provided by MDN Web Docs https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent

These sites can help you identify User-agents string:

Why Detecting User-Agent Strings ?

Threat actors frequently alter or fabricate User-Agent strings, sometimes aiming to camouflage their traffic within legitimate web requests.

[Malware Example] Raccoon Stealer

Press enter or click to view image in full size

A prime example is the Raccoon Stealer, notorious for using specific HTTP User-Agent strings when communicating with its C2 server. These User-Agent strings are unique and distinct, minimizing the chances of false positives during threat hunting sessions or in detection rules.

I added them in my hunting list https://github.com/mthcht/awesome-lists/blob/main/Lists/suspicious_http_user_agents_list.csv

Press enter or click to view image in full size
Raccoon Stealer strings

A detailed behavioral analysis of a Raccoon Stealer sample can be found here: https://tria.ge/230404-kmka5adg89/behavioral2

Press enter or click to view image in full size
User-Agent iMightJustPayMySelfForAFeature

Another recent sample analysis of Raccoon Stealer: https://www.joesandbox.com/analysis/1342102/0/html

Press enter or click to view image in full size
User-Agent SouthSide
Press enter or click to view image in full size
User-Agent SouthSide

[Malware Example] Bunny Loader:

A Malware-as-a-Service (MaaS) being discussed and sold on various underground forums. This loader is designed to distribute and execute various types of malware, making it a versatile tool for cybercriminals. Like Raccoon Stealer, Bunny Loader may also use unique User-Agent strings as part of its operation, further emphasizing the importance of monitoring and analyzing User-Agent strings in network traffic.

Press enter or click to view image in full size
Bunny Loader strings
Press enter or click to view image in full size
Bunny Loader strings

Raccoon Stealer and Bunny Loader are just two examples from a vast array of malware using unique User-Agent strings, as detailed in my extensive list. They highlight the importance of monitoring specific User-Agent strings in your SIEM.

For enhanced network security and visibility, it’s advisable to configure your workstations to exclusively use your company proxy for internet access. By doing so, you effectively restrict all web traffic to pass through the proxy, ensuring that it can be monitored and controlled.

List of Bad User agent

I’ve put together a list of suspicious User-Agents on GitHub that you can use to hunt in your environment, this same list will be the cornerstone of our hunting strategies discussed throughout this article

Structure of the file:

Each User-Agent is categorized with these fields:

  • http_user_agent: This field is used to match suspicious User-Agent strings in our SIEM logs. It supports wildcard entries for flexible matching and is not case-sensitive
  • metadata_description: Simple description of the User-Agent
  • metadata_link: Link to the source code, repository or article referencing the suspicious User-Agent
  • metadata_flow_direction: flow direction for the detection (Emphasize that this should primarily be employed for detecting internal threats)
  • metadata_category: Threat Category of the User-Agent (C2,Malware,RMM,Compliance,Phishing,Vulnerability Scanner,Exploitation…)
Press enter or click to view image in full size
  • metadata_priority: Priority assigned to the suspicious User-Agent (info → low → medium → high → critical)
Press enter or click to view image in full size
  • metadata_fp_risk: False positive risk assigned to the suspicious User-Agent (none → low → medium → high → very high)
Press enter or click to view image in full size
  • metadata_severity: threat severity risk assigned to the suspicious User-Agent (info → low → medium → high → critical)
Press enter or click to view image in full size
  • metadata_usage: This field is determined by the priority, false positive risk, and severity risk associated with a User-Agent. User-Agents likely to trigger high-confidence alerts are labeled as “Detection rule”, making them ideal for inclusion in scheduled detection rules on your SIEM. Conversely, User-Agents likely to produce low-confidence alerts (due to high false positive potential, low severity, or low priority) are categorized as “Hunting”, making them better suited for threat hunting activities rather than for use in detection rules.
Press enter or click to view image in full size

Detecting Threats inside your network

Requiered: HTTP Proxy logs (internal > external flow)

Hunting with a suspicious User-Agent List

Requiered: Suspicious User-Agent List

Press enter or click to view image in full size

let’s leverage the list of suspicious User-Agents in our Splunk environment, specifically focusing on our proxy logs. Here’s how you can proceed:

  1. Upload the List suspicious_http_user_agents_list.csv to your Splunk instance
  2. Create a Lookup Definition named suspicious_http_user_agents_list in Splunk. This will allow you to cross-reference the suspicious User-Agents in your logs faster against the list (Splunk’s settings menu under the ‘Lookups’ option)
Press enter or click to view image in full size
Not Case sensitive and wildcard match

3. Use the lookup definition in searches

  • Hunt for everything
`proxy`
| lookup suspicious_http_user_agents_list http_user_agent as http_user_agent OUTPUT http_user_agent as http_user_agent_pattern metadata_description metadata_link metadata_category metadata_priority metadata_fp_risk metadata_severity metadata_usage
| where isnotnull(http_user_agent_pattern)
| stats values(http_user_agent_pattern) values(metadata_category) values(http_method) values(status) dc(src_user) last(src_user) values(url) values(dest_host) values(metadata_description) values(metadata_link) values(metadata_priority) values(metadata_fp_risk) values(metadata_severity) earliest(_time) as firsttime latest(_time) as lasttime count by http_user_agent metadata_usage
| rename values(*) as *
| sort + count
  • High confidence searches (Detection rule)
`proxy`
NOT [|inputlookup Exclusion_List.csv | fields - "metadata_*"]
| lookup suspicious_http_user_agents_list http_user_agent as http_user_agent OUTPUT http_user_agent as http_user_agent_pattern metadata_description metadata_link metadata_category metadata_priority metadata_fp_risk metadata_severity metadata_usage
| where metadata_usage="Detection rule"
| stats values(http_user_agent_pattern) values(metadata_category) values(http_method) values(status) values(url) values(metadata_description) values(metadata_link) values(metadata_priority) values(metadata_fp_risk) values(metadata_severity) earliest(_time) as firsttime latest(_time) as lasttime count by src_user dest_host http_user_agent metadata_usage
| rename values(*) as *

Implement this search as a detection rule in your environment. You shouldn’t encounter these specific User-Agents in a secure network setting.

  • Low confidence searches (Threat Hunting)
`proxy`
| lookup suspicious_http_user_agents_list http_user_agent as http_user_agent OUTPUT http_user_agent as http_user_agent_pattern metadata_description metadata_link metadata_category metadata_priority metadata_fp_risk metadata_severity metadata_usage
| where metadata_usage="Hunting"
| stats values(http_user_agent_pattern) values(metadata_category) values(http_method) values(status) dc(src_user) last(src_user) values(url) values(dest_host) values(metadata_description) values(metadata_link) values(metadata_priority) values(metadata_fp_risk) values(metadata_severity) earliest(_time) as firsttime latest(_time) as lasttime count by http_user_agent metadata_usage
| rename values(*) as *
Press enter or click to view image in full size
Press enter or click to view image in full size
adding an exclusion list and filtering on internal source IP addresses
  • Hunt for LOLBIN usages
`proxy`
| lookup suspicious_http_user_agents_list http_user_agent as http_user_agent OUTPUT http_user_agent as http_user_agent_pattern metadata_description metadata_link metadata_category metadata_priority metadata_fp_risk metadata_severity metadata_usage
| where metadata_category="LOLBIN"
| stats values(http_user_agent_pattern) values(metadata_category) values(http_method) values(status) dc(src_user) last(src_user) values(url) values(dest_host) values(metadata_description) values(metadata_link) values(metadata_priority) values(metadata_fp_risk) values(metadata_severity) earliest(_time) as firsttime latest(_time) as lasttime count by http_user_agent metadata_usage
| rename values(*) as *
  • Hunt for C2
`proxy`
| lookup suspicious_http_user_agents_list http_user_agent as http_user_agent OUTPUT http_user_agent as http_user_agent_pattern metadata_description metadata_link metadata_category metadata_priority metadata_fp_risk metadata_severity metadata_usage
| where metadata_category="C2"
| stats values(http_user_agent_pattern) values(metadata_category) values(http_method) values(status) dc(src_user) last(src_user) values(url) values(dest_host) values(metadata_description) values(metadata_link) values(metadata_priority) values(metadata_fp_risk) values(metadata_severity) earliest(_time) as firsttime latest(_time) as lasttime count by http_user_agent metadata_usage
| rename values(*) as *
  • Hunt for Malwares
`proxy`
| lookup suspicious_http_user_agents_list http_user_agent as http_user_agent OUTPUT http_user_agent as http_user_agent_pattern metadata_description metadata_link metadata_category metadata_priority metadata_fp_risk metadata_severity metadata_usage
| where metadata_category="Malware"
| stats values(http_user_agent_pattern) values(metadata_category) values(http_method) values(status) dc(src_user) last(src_user) values(url) values(dest_host) values(metadata_description) values(metadata_link) values(metadata_priority) values(metadata_fp_risk) values(metadata_severity) earliest(_time) as firsttime latest(_time) as lasttime count by http_user_agent metadata_usage
| rename values(*) as *
  • Hunt for RMM
`proxy`
| lookup suspicious_http_user_agents_list http_user_agent as http_user_agent OUTPUT http_user_agent as http_user_agent_pattern metadata_description metadata_link metadata_category metadata_priority metadata_fp_risk metadata_severity metadata_usage
| where metadata_category="RMM"
| stats values(http_user_agent_pattern) values(metadata_category) values(http_method) values(status) dc(src_user) last(src_user) values(url) values(dest_host) values(metadata_description) values(metadata_link) values(metadata_priority) values(metadata_fp_risk) values(metadata_severity) earliest(_time) as firsttime latest(_time) as lasttime count by http_user_agent metadata_usage
| rename values(*) as *
  • Hunt for Scanners
`proxy`
| lookup suspicious_http_user_agents_list http_user_agent as http_user_agent OUTPUT http_user_agent as http_user_agent_pattern metadata_description metadata_link metadata_category metadata_priority metadata_fp_risk metadata_severity metadata_usage
| search metadata_category IN ("Bots & Vulnerability Scanner","Vulnerability Scanner","Discovery")
| stats values(http_user_agent_pattern) values(metadata_category) values(http_method) values(status) dc(src_user) last(src_user) values(url) values(dest_host) values(metadata_description) values(metadata_link) values(metadata_priority) values(metadata_fp_risk) values(metadata_severity) earliest(_time) as firsttime latest(_time) as lasttime count by http_user_agent metadata_usage
| rename values(*) as *
  • Hunt for critical severity and low false positives
`proxy`
| lookup suspicious_http_user_agents_list http_user_agent as http_user_agent OUTPUT http_user_agent as http_user_agent_pattern metadata_description metadata_link metadata_category metadata_priority metadata_fp_risk metadata_severity metadata_usage
| search metadata_severity IN ("high","critical") AND metadata_fp_risk IN ("none","low")
| stats values(http_user_agent_pattern) values(metadata_category) values(http_method) values(status) dc(src_user) last(src_user) values(url) values(dest_host) values(metadata_description) values(metadata_link) values(metadata_priority) values(metadata_fp_risk) values(metadata_severity) earliest(_time) as firsttime latest(_time) as lasttime count by http_user_agent metadata_usage
| rename values(*) as *
  • Compliance detections
`proxy`
| lookup suspicious_http_user_agents_list http_user_agent as http_user_agent OUTPUT http_user_agent as http_user_agent_pattern metadata_description metadata_link metadata_category metadata_priority metadata_fp_risk metadata_severity metadata_usage
| where metadata_category="Compliance"
| stats values(http_user_agent_pattern) values(metadata_category) values(http_method) values(status) dc(src_user) last(src_user) values(url) values(dest_host) values(metadata_description) values(metadata_link) values(metadata_priority) values(metadata_fp_risk) values(metadata_severity) earliest(_time) as firsttime latest(_time) as lasttime count by http_user_agent metadata_usage
| rename values(*) as *

3(bis). With Microsoft Sentinel, you can also use this query to hunt (found here):

let ThreatIntelFeed = externaldata(http_user_agent: string)[@"https://raw.githubusercontent.com/mthcht/awesome-lists/main/Lists/suspicious_http_user_agents_list.csv"] with (format="csv", ignoreFirstRecord=True);
let SuspiciousUserAgent = materialize (
ThreatIntelFeed
| distinct http_user_agent
);
DeviceNetworkEvents
| where ActionType == "HttpConnectionInspected"
| extend json = todynamic(AdditionalFields)
| extend direction = tostring(json.direction), method = tostring(json.method), user_agent = tostring(json.user_agent)
| where direction == "Out"
| where user_agent in (SuspiciousUserAgent)

4. Analyze and Investigate: Any matches found between your proxy logs and the suspicious User-Agents list should be thoroughly analyzed

Anomaly Detection in User-Agent Strings

Here are some search techniques for identifying User-Agent anomalies in your data without needing to rely on my hunting list

[Hunting] Unusually Long User Agent Strings

Simple Hunting search showing the User-Agent strings longer than 250 characters (you should ajust the threshold with something relevant the usual lenght of what you see in your environment)

⚠️ High number of false positives expected, you could filter throught the noise with dest and url category / severity and HTTP method fields. May catch benign cases where user agents are legitimately long due to plugins or toolbars (establish a baseline of legitimate known applications used in your environment for exclusion)

[Hunting] Unusually Short User Agent Strings

Simple Hunting search showing the User-Agent strings with less than 10 characters

Press enter or click to view image in full size

⚠️ High number of false positives expected (it will include empty user agent with the value ‘unknown’ in splunk), you could filter throught the noise with dest and url category / severity and HTTP method fields. You will catch lots of benign cases (establish a baseline of known application used in your environment for exclusion)

Get mthcht’s stories in your inbox

Join Medium for free to get updates from this writer.

Talking about empty User-Agent (which has the value ‘unknown’ in Splunk), it is included in my list. You should Hunt specifically on successful external sign-ins (excluding failed attempts) in cloud services like Microsoft Office 365, these empty User-Agent instances are relatively rare and searching for them is a good way of identifying unusual or suspicious activities.

[Hunting] Unusual User Agent Strings length and empty User Agent

A more advanced search compared to the two previous lentgh searches using Splunk command avg and stdev (much slower but could give more relevant results)

Press enter or click to view image in full size
  • eval length=len(http_user_agent): This creates a new field named length that stores the length of each user agent string.
  • eventstats avg(length) as avgLength, stdev(length) as stdevLength: This calculates the average length and standard deviation of the length field across the events.
  • where length > avgLength + (3*stdevLength) OR length < avgLength - (4*stdevLength): This condition filters out user agents whose lengths are more than 3 or 4 standard deviations away from the average, either longer or shorter.

[Hunting] Multiple User Agents from Same source in Short Time

Detects potential scanning or enumeration activities from a single source (either with the src_ip or the src_user)

Simple Hunting search to find sources making Web requests with more than 4 different User-Agent string on a domain (dest_host) in 10 minutes:

Press enter or click to view image in full size

⚠️ Alternative query: You can change src_user after the ‘count by’ for the src_ip and change ‘values(src_ip)’ to ‘values(src_user)’ but be aware of the Virtual IP address (VIP) used within your environment as you could have multiple users behind an IP address.

[Hunting] Rarest User-Agents

You can use Splunk’s rare command to identify the least common User-Agent strings in your data https://docs.splunk.com/Documentation/SplunkCloud/latest/SearchReference/Rare

This command will generate a list of the top 30 rarest User-Agents with count and percentage for each one. While a rare User-Agent doesn't inherently indicate suspicious activity, this information can be invaluable for threat hunting, providing insights into unusual patterns or anomalies in your network traffic.

You could also test the commands anomalydetection on your proxy logs and check the anomaly results for the field http_user_agent

Press enter or click to view image in full size

https://docs.splunk.com/Documentation/SplunkCloud/9.1.2308/SearchReference/Anomalydetection

These searches might reveal some interesting insights, though it’s not guaranteed (reserved for threat hunting)

Alex Teixeira also shared insightful techniques for identifying rare user-agents with splunk here https://opstune.com/2020/09/16/tracking-rare-http-agent-context-rich-alerts-splunk/

[Suspicious] Logged in with an empty user-agent

[Compliance] Mismatch between User Agent and Host OS

Requiered:

- Each request in the proxy logs should include the source IP address, or alternatively, the proxy logs should directly indicate the Host Operating System (without using the http user agent)

- CMDB logs in the SIEM to correlate with proxy logs (if only IP Address in the proxy logs)

- Splunk lookup with the association src_ip — host os (if only IP Address in the proxy logs)

If you can identify an host OS with either:

  • Configuration Management Database (CMDB) logs which should encompass details like IP address, hostname, services running and OS
  • A simple Splunk lookup with the relationship between IP address and OS
  • Your proxy logs (without using the http user agent), some proxy solution like Mcafee Web Gateway can give you process names and host OS infos.

To illustrate the hunt:

Press enter or click to view image in full size

You’ve successfully identified a Windows server using one of the mentioned methods and noticed it making a web request with a Linux User Agent (the first case in the illustration). This could indicate the presence of a virtual machine on the computer that’s not under our monitoring. We should verify the legitimacy of this activity on the specified machine. Ideally, if your Configuration Management Database (CMDB) is well-maintained, you should be able to determine the expected services running on that machine directly from your SIEM !

[Compliance] User Agents Indicating Outdated or Vulnerable Browsers

Requiered: Needs constant updating with the latest vulnerability information and versions to stay relevant.

Using the same example illustration, we identified that the last two Windows machines were using outdated and vulnerable browsers

Press enter or click to view image in full size

I’ve included extremely old user-agents in my list of bad user-agents. For instance, the presence of Internet Explorer 8.0 in 2023, as seen in the third machine from our example, is a clear anomaly and should raise immediate concerns.

Further parsing of user-agent strings may be required to correctly identify outdated versions. Typically, this is a compliance detection task that should be handled by software management tools.

Logging External threats

Requiered: (WAF logs | WebServer access Logs | Reverse Proxy Logs)
+ Firewall Logs

I want to clarify my stance on alerts for external Web attacks (with suspicious User-Agents or not) on our internet-exposed servers: it’s not a priority. Constantly monitoring these would lead to an overwhelming number of alerts, most of which will lead to nothing interesting. A more effective strategy would be to correlate low confidence alerts and only alert with a high confidence alert.

For example we could only log the external IP addresses associated with a blocked attack (or a vulnerability scanner User-Agent), then, we focus on generating an alert only when an internal machine initiates a connection to any of these flagged IP addresses (Having the TCP flag information in our Firewall logs will further enhance accuracy and reduce false positives) or when a succesful connection on one of our services is observed with these flagged IP addresses.

Logging

Illustration logging external attacks with suspicious User-Agents using a WAF

Press enter or click to view image in full size

This approach should be broadly applied across all types of logs and external attack vectors. It’s important to expand beyond just User-Agents, to include any traces or indicators that can pinpoint an attack with high confidence

Press enter or click to view image in full size

Alerting

Then we only alert if the attacker’s IP address:

  • Connected succesfully to any of our exposed services (AD, VPN, MAIL, SSH, Other Cloud authent services…)
  • Is contacted by an internal source after the attack (as explained earlier)
  • Is observed associated with an email sender/recipient address

Illustration:

Press enter or click to view image in full size

More detail on how to identify TCP flags with AWS network traffic logs (in this example):

TCP segment structure: https://en.wikipedia.org/wiki/Transmission_Control_Protocol#TCP_segment_structure

Applying this approach to any external service and not only User-Agent detection but any high confidence attack pattern:

Press enter or click to view image in full size

Conclusion

User-Agent string detection, already a known practice, is crucial for SOC teams to identify high confidence threat patterns. However, its role in threat hunting and weak signal is often underestimated, primarily due to concerns about dealing with false positives. With the list and examples i’ve provided, I hope to enhance your threat hunting process, helping you uncover more significant insights with fewer false alarms.

Happy Hunting !

--

--

mthcht
mthcht

Written by mthcht

Threat Hunting - DFIR - Detection Engineering

Detect FYI
Detect FYI

Published in Detect FYI

Threat Detection Engineering and DFIR Insights