Detect Purchase Technical Data in Microsoft Sentinel
Adversaries may purchase technical information about victims that can be used during targeting. Information about victims may be available for purchase within reputable private sources and databases, such as paid subscriptions to feeds of scan databases or other data aggregation services. Adversaries may also purchase information from less-reputable sources such as dark web or cybercrime blackmarkets. Purchased data may include employee credentials, session tokens, infrastructure details, exposed certificates, or vulnerability scan results. LAPSUS$ is a documented threat actor known to purchase credentials and session tokens from criminal underground forums. Because the purchase itself occurs entirely outside the victim's environment, detection must focus on downstream indicators: use of purchased credentials in authentication events, impossible travel patterns, logins from anonymizing infrastructure (Tor, VPN exit nodes), and Identity Protection risk signals.
MITRE ATT&CK
- Tactic
- Reconnaissance
- Technique
- T1597 Search Closed Sources
- Sub-technique
- T1597.002 Purchase Technical Data
- Canonical reference
- https://attack.mitre.org/techniques/T1597/002/
KQL Detection Query
// T1597.002 — Purchase Technical Data: Downstream Indicators
// Detects authentication patterns consistent with use of purchased credentials or session tokens
// Focus: risky sign-ins, impossible travel, Tor/anonymizer logins, and credential stuffing patterns
let TorExitNodes = externaldata(ip: string)
[@"https://check.torproject.org/exit-addresses"]
with (format="txt", ignoreFirstRecord=true);
let LookbackPeriod = 7d;
let CredentialStuffingThreshold = 5; // distinct accounts from same IP
// Branch 1: Entra ID Protection high-risk sign-ins (purchased creds often trigger risk engine)
let RiskySignIns = SigninLogs
| where TimeGenerated > ago(LookbackPeriod)
| where RiskLevelDuringSignIn in ("high", "medium")
or RiskEventTypes_V2 has_any ("leakedCredentials", "anonymizedIPAddress", "unfamiliarFeatures", "malwareLinkedIPAddress", "investigationsThreatIntelligence")
| extend DetectionSource = "EntraID_RiskEngine"
| project TimeGenerated, UserPrincipalName, IPAddress, Location, RiskLevelDuringSignIn, RiskEventTypes_V2, AppDisplayName, AuthenticationRequirement, ConditionalAccessStatus, DetectionSource, CorrelationId;
// Branch 2: Impossible travel — sign-ins from geographically distant locations within short window
let ImpossibleTravel = SigninLogs
| where TimeGenerated > ago(LookbackPeriod)
| where ResultType == 0 // successful sign-in
| extend Country = tostring(LocationDetails.countryOrRegion)
| summarize Locations = make_set(Country), SignInTimes = make_list(TimeGenerated), IPAddresses = make_set(IPAddress) by UserPrincipalName, bin(TimeGenerated, 6h)
| where array_length(Locations) > 1
| extend DetectionSource = "ImpossibleTravel"
| project TimeGenerated, UserPrincipalName, Locations, IPAddresses, DetectionSource;
// Branch 3: Credential stuffing — single IP attempting authentication against many accounts
let CredentialStuffing = SigninLogs
| where TimeGenerated > ago(LookbackPeriod)
| summarize DistinctAccounts = dcount(UserPrincipalName), AttemptCount = count(), ResultCodes = make_set(ResultType) by IPAddress, bin(TimeGenerated, 1h)
| where DistinctAccounts >= CredentialStuffingThreshold
| extend DetectionSource = "CredentialStuffing"
| project TimeGenerated, IPAddress, DistinctAccounts, AttemptCount, ResultCodes, DetectionSource;
// Branch 4: Successful sign-ins from first-time ASN or country for established accounts
let NewLocationSignIns = SigninLogs
| where TimeGenerated > ago(LookbackPeriod)
| where ResultType == 0
| extend Country = tostring(LocationDetails.countryOrRegion)
| join kind=leftanti (
SigninLogs
| where TimeGenerated between (ago(90d) .. ago(LookbackPeriod))
| where ResultType == 0
| distinct UserPrincipalName, Country = tostring(LocationDetails.countryOrRegion)
) on UserPrincipalName, Country
| extend DetectionSource = "NewCountrySignIn"
| project TimeGenerated, UserPrincipalName, IPAddress, Country, AppDisplayName, DetectionSource;
// Combine branches
RiskySignIns
| union (ImpossibleTravel | extend IPAddress = tostring(IPAddresses[0]), RiskLevelDuringSignIn = "computed", RiskEventTypes_V2 = dynamic([]), AppDisplayName = "", AuthenticationRequirement = "", ConditionalAccessStatus = "", CorrelationId = "")
| union (NewLocationSignIns | extend RiskLevelDuringSignIn = "new_location", RiskEventTypes_V2 = dynamic([]), AppDisplayName, AuthenticationRequirement = "", ConditionalAccessStatus = "", CorrelationId = "")
| sort by TimeGenerated desc Detects authentication patterns consistent with the downstream use of credentials or session tokens purchased from dark web markets or data brokers. T1597.002 itself occurs entirely outside the victim environment and generates no direct telemetry; this query pivots to victim-side indicators using Microsoft Entra ID (Azure AD) SigninLogs. Covers four distinct detection branches: (1) Entra ID Protection risk engine alerts for leaked credentials and anonymized IP addresses, (2) impossible travel — successful logins from geographically inconsistent locations within a short window, (3) credential stuffing patterns — a single IP attempting authentication against five or more distinct accounts within an hour, and (4) successful sign-ins from a country never previously observed for an established account. Confidence is low-to-medium as each indicator has benign explanations; correlation of multiple branches against the same user substantially increases confidence.
Data Sources
Required Tables
False Positives & Tuning
- Legitimate user travel to a new country or use of a corporate VPN exit node in an unfamiliar region will trigger the impossible travel and new-country branches
- Users sharing a corporate NAT or proxy will cause multiple accounts to appear from the same IP, triggering the credential stuffing threshold even for legitimate logins
- Entra ID Protection risk events may fire for legitimate users connecting from cloud provider IP ranges (AWS, Azure, GCP) that are also abused by attackers
- Red team or penetration testing exercises using purchased breach data to validate detection coverage will produce high-confidence true positives that are authorized
- Password reset or IT helpdesk-initiated logins from shared admin workstations may appear as unusual geographic or ASN origins
Other platforms for T1597.002
Testing Methodology
Validate this detection against 5 adversary techniques from Atomic Red Team. Each test below lists the behaviour to exercise and the telemetry you should expect to see. Executable commands and cleanup steps are available with Pro.
- Test 1Check Organization Domain Against Have I Been Pwned Enterprise API
Expected signal: Network connection to api.haveibeenpwned.com:443 from the executing host. DNS resolution of api.haveibeenpwned.com. HTTP GET request visible in proxy/web gateway logs. No endpoint telemetry generated for the API call itself.
- Test 2Simulate Credential Stuffing Authentication Pattern
Expected signal: Web application access logs: 15 entries with HTTP 401 from the source IP across distinct usernames, followed by 1 HTTP 200. If web app logs feed into Splunk or Sentinel: Authentication datamodel events with action=failure for the 15 attempts and action=success for the final attempt. All events from the same source IP within a 30-second window.
- Test 3Query Tor Exit Node List and Check Against Authentication Logs
Expected signal: DNS resolution of check.torproject.org. Outbound HTTPS connection to check.torproject.org:443. File creation at /tmp/tor_exit_nodes.txt. If cross-referencing login logs, no additional network telemetry is generated for the grep operations.
- Test 4Enumerate Exposed Credentials for Domain Using Dehashed API
Expected signal: DNS resolution of api.dehashed.com. Outbound HTTPS connection to api.dehashed.com:443 with Basic Authentication header. HTTP GET request visible in proxy/web gateway logs. Response body contains credential records if any are found.
- Test 5Generate Impossible Travel Event for Detection Validation
Expected signal: Two SigninLogs entries for the same UserPrincipalName within a short time window, each with a different IPAddress and LocationDetails.countryOrRegion value. The time delta between entries divided by the physical distance between locations should exceed the speed of any known transportation (i.e., physically impossible travel).
References (10)
- https://attack.mitre.org/techniques/T1597/002/
- https://www.zdnet.com/article/a-hacker-group-is-selling-more-than-73-million-user-records-on-the-dark-web/
- https://www.microsoft.com/en-us/security/blog/2022/03/22/dev-0537-criminal-actor-targeting-organizations-for-data-exfiltration-and-destruction/
- https://learn.microsoft.com/en-us/entra/id-protection/overview-identity-protection
- https://learn.microsoft.com/en-us/azure/azure-monitor/reference/tables/signinlogs
- https://haveibeenpwned.com/API/v3
- https://spycloud.com/blog/what-is-credential-stuffing/
- https://www.cisa.gov/sites/default/files/2023-03/threat-actor-leveraging-multiple-ransomware-families_0.pdf
- https://owasp.org/www-community/attacks/Credential_stuffing
- https://www.recordedfuture.com/blog/dark-web-credential-markets
Unlock Pro Content
Get the full detection package for T1597.002 including response playbook, investigation guide, and atomic red team tests.