T1597.002

Purchase Technical Data

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.

Microsoft Sentinel / Defender
kusto
// 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
high severity low confidence

Data Sources

Authentication: Authentication Logs User Account: User Account Authentication Microsoft Entra ID Protection Risk Events SigninLogs

Required Tables

SigninLogs

False Positives

  • 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

Unlock Pro Content

Get the full detection package for T1597.002 including response playbook, investigation guide, and atomic red team tests.

Response PlaybookInvestigation GuideHunting QueriesAtomic Red Team TestsTuning Guidance

Related Detections