Drilling down on phishing campaigns with UrlClickEvents

Introduction

On March 2nd 2022, I observed a new Advanced Hunting table in Microsoft 365 Defender: UrlClickEvents

Figure 1 – UrlClickEvents table

At time of writing, this table is not yet present in every Office 365 tenant, and the official documentation does not contain information about it. A quick peak at the events it contains shows that it logs URLs on which users clicked from Office applications, such as Outlook and Teams. It also logs if the click was allowed or blocked by Safe links, and if the user clicked through the potential warning page (if this setting is configured in Safe Links).

Here is the table format:

  • Timestamp: the timestamp at which the user clicked on the link;
  • Url: the URL that was clicked on by the user;
  • ActionType: indicates whether the click was allowed by Safe Links or not (values observed: ClickAllowed, ClickBlocked, UrlErrorPage, ClickBlockedByTenantPolicy);
  • AccountUpn: the User Principal Name of the account that clicked on the link;
  • Workload: the application from which the user clicked on the link (values observed: Email, Office, Teams);
  • NetworkMessageId: the unique identifier for the email that contains the clicked link, generated by Microsoft 365;
  • IPAddress: public IP address from which the user clicked on the link;
  • IsClickedThrough: indicates whether the user clicked through the potential Safe Links warning page (if this setting is configured in Safe Links);
  • UrlChain: appears to contain the list of redirect URLs, from our test data;
  • ReportId: value that “enables lookups for the original records”, according to the official documentation.

While URL clicks were already available in 365 Defender’s Threat Explorer dashboard for investigation (formerly in Office 365 ATP Threat Explorer), the availability of this data in Advanced Hunting opens new opportunities for hunting queries, custom detection rules and investigation.

Hunting Queries

Click on link that contains an unusual port

UrlClickEvents
| where ActionType == "ClickAllowed"
| extend Redirects = (array_length(todynamic(UrlChain))) - 1
| extend ParsedUrl = parse_url(tostring(Url))
| where ParsedUrl.Port !in ("", "443")
| where ParsedUrl.Host !endswith "<yourdomain>"
| project Timestamp, AccountUpn, Workload, NetworkMessageId, Url, Redirects, UrlChain

In this query, the following is performed:

  • Filter on clicks that were allowed by SafeLinks;
  • Store the number of redirect URLs in an array (later displayed in the results);
  • Parse the URL to extract the host, port, path, etc.;
  • Exclude URLs whose TCP port is empty, or equal to 443;
  • Exclude URLs whose host ends with the domain dame of your organisation (this is to limit false-positive results);
  • Display the results.

Click on link where the host is a public IP address

UrlClickEvents
| where ActionType == "ClickAllowed"
| extend Redirects = (array_length(todynamic(UrlChain))) - 1
| extend ParsedUrl = parse_url(tostring(Url))
| where ipv4_is_private(tostring(ParsedUrl.Host)) == False
| project Timestamp, AccountUpn, Workload, NetworkMessageId, Url, Redirects, UrlChain

In this query, the following is performed:

  • Filter on clicks that were allowed by SafeLinks;
  • Store the number of redirect URLs in an array (later displayed in the results);
  • Parse the URL to extract the host, port, path, etc.;
  • Filter on URLs where the host is not a private IP address;
  • Display the results.

Custom Detection Rule

Click on link containing your domain name in base64-encoded format

UrlClickEvents
| where ActionType == "ClickAllowed"
| extend Redirects = (array_length(todynamic(UrlChain))) - 1
| where Redirects > 0
| where Url contains "<your_base64_encoded_domain>"
| project Timestamp, AccountUpn, Workload, NetworkMessageId, Url, Redirects, UrlChain

In this query, the following is performed:

  • Filter on clicks that were allowed by SafeLinks;
  • Store the number of redirect URLs in an array (later displayed in the results);
  • Filter on URLs which redirected the user at least once to another URL (as is often the case in phishing campaigns);
  • Filter on URLs which contain your organization’s domain name in base64-encoded format (as phishing URLs often contain the recipient’s email address in base64-encoded format);
  • Display the results.

Investigation Query (emails)

UrlClickEvents
| <your conditions>
| project Click_Time = Timestamp, NetworkMessageId, Clicked_Url = Url
| join EmailEvents on NetworkMessageId
| project Delivery_Time = Timestamp,  Click_Time, Clicked_Url, RecipientEmailAddress, SenderMailFromAddress, SenderFromAddress, SenderDisplayName, Subject, AttachmentCount, UrlCount

In this query, the following is performed:

  • Filter the UrlClickEvents logs using your conditions, depending on the investigation;
  • Rename columns for better comprehension in the final results, and project the necessary value (NetworkMessageId) used for the future Join operation;
  • Join the EmailEvents table to display additional information for each URL click (e.g. email delivery time, sender details, subject, etc.);
  • Display the results.

Conclusion

This new UrlClickEvents table is an additional tool SOC and threat hunting teams can use to detect phishing campaigns missed by built-in technologies, through hunting and custom detection rules. Additionally, this will help incident responders flag users who accessed phishing links faster than by using Microsoft 365 Defender’s GUI, especially for extensive phishing campaigns.

About the author

Thibaut Flochon
Thibaut is an intrusion analyst within NVISO’s CSIRT & SOC team. He enjoys investigating security incidents, writing detection rules, and talking about preventive security controls.

Leave a Reply