Account Takeover Fraud
1. Introduction
Account Takeover Fraud (ATO) represents a sophisticated form of identity theft where cybercriminals gain unauthorised access to legitimate user accounts. This growing threat affects various account types, from financial services to social media platforms, with significant impact on both individuals and organisations. According to recent studies, 22% of U.S. adults have fallen victim to ATO fraud, with average individual losses reaching $12,000. The fraud typically involves credential theft through methods like phishing, data breaches, or social engineering, followed by account exploitation for unauthorised transactions or further fraudulent activities. As digital services expand, robust detection and prevention strategies become increasingly crucial for protecting against this evolving threat.
2. Scenarios
Types of Account Takeover
-
Financial account fraud: Unauthorised transfers, fraudulent purchases, and credit card applications
-
Email account compromise: Access to personal information, password resets, and further account takeovers
-
Social media hijacking: Identity impersonation, scam distribution, and social engineering
Extent of the Problem
-
Widespread impact: 22% of U.S. adults have been victims of account takeover
-
Financial losses: Average individual losses amount to $12,000 per incident
-
Business impact: Significant reputational damage and potential legal consequences
-
Rising sophistication: Increasing use of automated tools and AI for large-scale attacks
Challenges
-
Complex attack vectors: Multiple entry points through phishing, malware, and social engineering
-
Credential stuffing: Automated attacks using stolen username/password combinations
-
Device spoofing: Fraudsters using advanced techniques to bypass device fingerprinting
-
Authentication bypass: Sophisticated methods to circumvent multi-factor authentication
3. Solution
Graph databases provide a powerful approach to detecting and preventing Account Takeover Fraud. By modelling the complex web of user behaviours, device interactions, and account activities as a connected network, graph technology can identify suspicious patterns that traditional systems might miss. This approach is particularly effective for ATO fraud, where multiple data points and relationships must be analysed simultaneously.
3.1. How Graph Databases Can Help?
-
Device Fingerprinting: Neo4j can track relationships between user accounts, devices, and IP addresses to identify suspicious login patterns and potential credential-stuffing attacks.
-
Behavioural Analysis: Graph databases excel at modeling normal user behaviour patterns and detecting anomalies, such as:
-
Unusual login times or locations
-
Suspicious changes in transaction patterns
-
Unexpected account setting modifications
-
Abnormal navigation patterns within applications
-
-
Identity Verification Networks: Create comprehensive identity graphs that connect:
-
User accounts and associated email addresses
-
Phone numbers and authentication methods
-
Device fingerprints and login locations
-
Transaction patterns and beneficiary relationships
-
-
Real-time Detection: Neo4j enables:
-
Instant validation of login attempts against known patterns
-
Real-time analysis of transaction sequences
-
Immediate identification of suspicious IP addresses or devices
-
Dynamic risk scoring based on graph patterns
-
-
Network Analysis: Uncover sophisticated fraud rings by:
-
Identifying shared attributes between compromised accounts
-
Detecting clusters of suspicious activity
-
Tracing the spread of credential stuffing attacks
-
Mapping relationships between known fraudulent entities
-
4. Modelling
This section provides examples of Cypher queries to demonstrate how to structure your data for detecting Account Takeover Fraud. The example graph will include nodes for users, devices, sessions, locations, and authentication events, with relationships showing how these entities interact during normal and suspicious account access patterns.
4.1. Data Model
4.1.1 Required Fields
Person
Node:
-
id
: Unique identifier for the person -
email
: Primary email address
Device
Node:
-
deviceID
: Unique identifier for the device -
deviceType
: Type of device (mobile, desktop, tablet) -
userAgent
: Browser/app user agent string
IP
Node:
-
ip
: IP address
ISP
Node:
-
isp
: Internet Service Provider
Location
Node:
-
city
: City name -
country
: Country code
Session
Node:
-
sessionID
: Unique session identifier -
status
: Session status (success, failed, suspicious)
Event
Node:
-
eventID
: Unique event identifier -
eventType
: Type of authentication event -
timestamp
: Event timestamp -
status
: Authentication status
Account
Node:
-
accountNumber
: Unique account number
Relationships:
-
USED_BY
: Person uses device -
PARTICIPATED_IN_EVENT
: Person participated in event -
USES_IP
: Event uses IP -
HAS_LOCATION
: Event has location -
SESSION_USES_DEVICE
: Session uses device -
OWNS
: Person owns account -
IS_ALLOCATED_TO
: IP is allocated to ISP -
LOCATED_IN
: IP is located in Location -
HAS_EVENT
: Session has event
4.2. Demo Data
The following Cypher statement will create an example graph demonstrating typical account access patterns:
//
// Create Person nodes
//
CREATE (p1:Person {id: "P001", email: "user1@example.com"})
CREATE (p2:Person {id: "P002", email: "user2@example.com"})
CREATE (p3:Person { id: "P003", email: "user3@example.com"})
//
// Create Device nodes
//
CREATE (d1:Device {id: "WEB001", deviceType: "desktop", userAgent: "Mozilla/5.0 Chrome/91.0"})
CREATE (d2:Device {id: "WEB002", deviceType: "mobile", userAgent: "Mozilla/5.0 Mobile Safari/537.36"})
CREATE (d3:Device {id: "SUSPICIOUS001", deviceType: "desktop", userAgent: "Mozilla/5.0 Firefox/89.0"})
//
// Create IP nodes
//
CREATE (ip1:IP {ip: "192.168.1.1"})
CREATE (ip2:IP {ip: "10.0.0.1"})
CREATE (ip3:IP {ip: "203.0.113.1"})
CREATE (ip4:IP {ip: "198.51.100.1"})
CREATE (ip5:IP {ip: "172.16.0.1"})
//
// Create ISP nodes
//
CREATE (isp1:ISP {isp: "BT"})
CREATE (isp2:ISP {isp: "Orange"})
CREATE (isp3:ISP {isp: "Verizon"})
CREATE (isp4:ISP {isp: "China Telecom"})
//
// Create Location nodes
//
CREATE (l1:Location {city: "London", country: "UK"})
CREATE (l2:Location {city: "Paris", country: "France"})
CREATE (l3:Location {city: "Beijing", country: "China"})
CREATE (l4:Location {city: "Lagos", country: "Nigeria"})
CREATE (l5:Location {city: "New York", country: "USA"})
//
// Create Session nodes
//
CREATE (s1:Session {id: "SESS001", status: "success"})
CREATE (s2:Session {id: "SESS002", status: "success"})
CREATE (s3:Session {id: "SESS003", status: "failed"})
CREATE (s4:Session {id: "SESS004", status: "failed"})
CREATE (s5:Session {id: "SESS005", status: "failed"})
//
// Create Event nodes
//
CREATE (e1:Event {created: datetime("2024-03-01T10:00:00"), id: "EVT001", sessionKey: "SESS001", status: "success"})
CREATE (e2:Event {created: datetime("2024-03-01T10:05:00"), id: "EVT002", sessionKey: "SESS002", status: "success"})
CREATE (e3:Event {created: datetime("2024-03-01T11:00:00"), id: "EVT003", sessionKey: "SESS003", status: "failed"})
CREATE (e4:Event {created: datetime("2024-03-01T11:05:00"), id: "EVT004", sessionKey: "SESS004", status: "failed"})
CREATE (e5:Event {created: datetime("2024-03-01T11:10:00"), id: "EVT005", sessionKey: "SESS005", status: "failed"})
//
// Create Account nodes
//
CREATE (a1:Account {accountNumber: "ACC001"})
CREATE (a2:Account {accountNumber: "ACC002"})
CREATE (a3:Account {accountNumber: "ACC003"})
//
// Create Relationships
//
// Pattern 1: Single device logging into multiple accounts
CREATE (d3)-[:USED_BY]->(p1)
CREATE (d3)-[:USED_BY]->(p2)
CREATE (d3)-[:USED_BY]->(p3)
// Pattern 2: Different locations logging into single account
CREATE (p1)-[:PARTICIPATED_IN_EVENT]->(e1)
CREATE (e1)-[:USES_IP]->(ip1)
CREATE (e1)-[:HAS_LOCATION]->(l1)
CREATE (p1)-[:PARTICIPATED_IN_EVENT]->(e2)
CREATE (e2)-[:USES_IP]->(ip3)
CREATE (e2)-[:HAS_LOCATION]->(l3)
// Pattern 3: Multiple failed login attempts from different IPs
CREATE (p2)-[:PARTICIPATED_IN_EVENT]->(e3)
CREATE (e3)-[:USES_IP]->(ip2)
CREATE (e3)-[:HAS_LOCATION]->(l2)
CREATE (p2)-[:PARTICIPATED_IN_EVENT]->(e4)
CREATE (e4)-[:USES_IP]->(ip4)
CREATE (e4)-[:HAS_LOCATION]->(l4)
CREATE (p2)-[:PARTICIPATED_IN_EVENT]->(e5)
CREATE (e5)-[:USES_IP]->(ip5)
CREATE (e5)-[:HAS_LOCATION]->(l5)
// Additional contextual relationships
CREATE (ip1)-[:IS_ALLOCATED_TO]->(isp1)
CREATE (ip2)-[:IS_ALLOCATED_TO]->(isp2)
CREATE (ip3)-[:IS_ALLOCATED_TO]->(isp4)
CREATE (ip4)-[:IS_ALLOCATED_TO]->(isp3)
CREATE (ip5)-[:IS_ALLOCATED_TO]->(isp3)
// Connect IP to Location
CREATE (ip1)-[:LOCATED_IN]->(l1)
CREATE (ip2)-[:LOCATED_IN]->(l2)
CREATE (ip3)-[:LOCATED_IN]->(l3)
CREATE (ip4)-[:LOCATED_IN]->(l4)
CREATE (ip5)-[:LOCATED_IN]->(l5)
// Session device relationships
CREATE (s1)-[:SESSION_USES_DEVICE]->(d1)
CREATE (s2)-[:SESSION_USES_DEVICE]->(d3)
CREATE (s3)-[:SESSION_USES_DEVICE]->(d2)
CREATE (s4)-[:SESSION_USES_DEVICE]->(d2)
CREATE (s5)-[:SESSION_USES_DEVICE]->(d2)
// Connect Session to Event
CREATE (s1)-[:HAS_EVENT]->(e1)
CREATE (s2)-[:HAS_EVENT]->(e2)
CREATE (s3)-[:HAS_EVENT]->(e3)
CREATE (s4)-[:HAS_EVENT]->(e4)
CREATE (s5)-[:HAS_EVENT]->(e5)
// Update Relationships
CREATE (p1)-[:OWNS]->(a1)
CREATE (p2)-[:OWNS]->(a2)
CREATE (p3)-[:OWNS]->(a3)
4.3. Neo4j Schema
If you call:
// Show neo4j schema
CALL db.schema.visualization()
You will see the following response:
5. Cypher Queries
5.1. Single device logging into multiple different accounts
In this query, we will identify devices that have been used to access multiple different user accounts, which is a common pattern in credential stuffing attacks and account takeover attempts.
View Graph:
// Show the relationships between suspicious devices and multiple accounts
MATCH path=(d:Device)-[:USED_BY]->(p:Person)-[:OWNS]->(a:Account)
WITH d, count(p) as accountCount
WHERE accountCount > 1
MATCH path=(d)-[:USED_BY]->(p:Person)-[:OWNS]->(a:Account)
RETURN path
View Statistics:
// Get detailed statistics about devices accessing multiple accounts
MATCH (d:Device)-[:USED_BY]->(p:Person)-[:OWNS]->(a:Account)
WITH d,
count(p) as uniqueAccounts,
collect(p.email) as compromisedEmails,
d.deviceType as deviceType,
d.userAgent as userAgent
WHERE uniqueAccounts > 1
RETURN d.id as DeviceID,
deviceType as DeviceType,
userAgent as UserAgent,
uniqueAccounts as NumberOfAccounts,
compromisedEmails as CompromisedAccounts
ORDER BY uniqueAccounts DESC
What It Does:
-
First query visualises the network of suspicious devices and their connections to multiple accounts
-
Second query provides detailed statistics about each suspicious device, including:
-
Number of unique accounts accessed
-
Device type and user agent information
-
List of potentially compromised email accounts
5.2. Suspicious Session Patterns
In these queries, we analyse session patterns to identify potential account takeover attempts through unusual session behaviours, failed login attempts, and suspicious location changes within sessions.
View Failed Login Attempts:
// Show clusters of failed login attempts within a time window
MATCH (p:Person)-[:PARTICIPATED_IN_EVENT]->(e:Event)
WHERE e.status = 'failed'
WITH p, e
ORDER BY e.created
WITH p,
collect({
eventId: e.id,
eventTime: e.created,
status: e.status
}) as attempts
WHERE size(attempts) >= 3
RETURN p.email as UserEmail,
attempts,
size(attempts) as FailedAttempts
ORDER BY FailedAttempts DESC
View Location Changes:
// Detect rapid location changes within sessions
MATCH (p:Person)-[:PARTICIPATED_IN_EVENT]->(e:Event)-[:HAS_LOCATION]->(l:Location)
WITH p, e, l
ORDER BY e.created
WITH p,
collect({
location: l.city + ', ' + l.country,
eventTime: e.created
}) as locations
WHERE size(locations) > 1
RETURN p.email as UserEmail,
locations,
size(locations) as LocationChanges
ORDER BY LocationChanges DESC
View Session Timeline:
// Analyse session patterns over time
MATCH (p:Person)-[:PARTICIPATED_IN_EVENT]->(e:Event), (d:Device)<-[:SESSION_USES_DEVICE]-(s:Session)-[:HAS_EVENT]->(e:Event)
WHERE e.sessionKey = s.id
WITH p, d, e, s
RETURN p.email as UserEmail,
d.id as DeviceID,
d.deviceType as DeviceType,
e.created as EventTime,
s.status as SessionStatus,
duration.between(
min(e.created),
max(e.created)
).minutes as SessionDurationMinutes
ORDER BY e.created
What It Does:
-
First query identifies clusters of failed login attempts:
-
Groups failed attempts by user
-
Shows the sequence and timing of failures
-
Helps identify brute force attacks
-
Second query detects suspicious location changes:
-
Tracks location changes within user sessions
-
Identifies physically impossible travel patterns
-
Helps spot location spoofing or compromised accounts
-
Third query analyses session patterns:
-
Shows the complete timeline of session events
-
Tracks device changes within sessions
-
Measures session duration and activity patterns
5.3. Multiple Failed Login Attempts from Different IPs
In these queries, we analyse patterns of failed login attempts from different IP addresses targeting the same account, which is a common indicator of brute force attacks.
View Failed Login Pattern:
// Show accounts with multiple failed login attempts from different IPs
MATCH (p:Person)-[:PARTICIPATED_IN_EVENT]->(e:Event)-[:USES_IP]->(ip:IP)
WHERE e.status = 'failed'
WITH p, count(DISTINCT ip) as uniqueIPs, collect(DISTINCT ip.ip) as ipAddresses,
count(e) as totalFailedAttempts
WHERE uniqueIPs >= 2
RETURN p.email as TargetAccount,
totalFailedAttempts as FailedAttempts,
uniqueIPs as NumberOfUniqueIPs,
ipAddresses as IPAddresses
ORDER BY totalFailedAttempts DESC
View Detailed Timeline:
// Show detailed timeline of failed attempts with location context
MATCH (p:Person)-[:PARTICIPATED_IN_EVENT]->(e:Event)-[:USES_IP]->(ip:IP),
(e)-[:HAS_LOCATION]->(l:Location),
(ip)-[:IS_ALLOCATED_TO]->(isp:ISP)
WHERE e.status = 'failed'
WITH p, count(DISTINCT ip) as uniqueIPs
WHERE uniqueIPs >= 2
MATCH (p)-[:PARTICIPATED_IN_EVENT]->(e:Event)-[:USES_IP]->(ip:IP),
(e)-[:HAS_LOCATION]->(l:Location),
(ip)-[:IS_ALLOCATED_TO]->(isp:ISP)
WHERE e.status = 'failed'
RETURN p.email as TargetAccount,
e.created as AttemptTime,
ip.ip as IPAddress,
l.city + ', ' + l.country as Location,
isp.isp as ISP
ORDER BY p.email, e.created
View Geographic Distribution:
// Show geographic distribution of failed attempts
MATCH (p:Person)-[:PARTICIPATED_IN_EVENT]->(e:Event)-[:HAS_LOCATION]->(l:Location)
WHERE e.status = 'failed'
WITH p, l, count(e) as attemptsFromLocation
WITH p,
count(DISTINCT l) as uniqueLocations,
collect(DISTINCT {
location: l.city + ', ' + l.country,
attempts: attemptsFromLocation
}) as locationBreakdown
WHERE uniqueLocations >= 2
RETURN p.email as TargetAccount,
uniqueLocations as NumberOfLocations,
locationBreakdown as LocationBreakdown
ORDER BY uniqueLocations DESC
What It Does:
-
First query provides an overview of accounts under attack:
-
Counts total failed attempts per account
-
Shows number of unique IPs used
-
Lists all IP addresses involved
-
Second query shows the detailed timeline:
-
Chronological sequence of failed attempts
-
Geographic location of each attempt
-
ISP information for each IP
-
Helps identify attack patterns and timing
-
Third query analyses geographic distribution:
-
Shows number of unique locations
-
Provides breakdown of attempts per location
-
Helps identify geographically dispersed attacks
Risk Indicators:
-
Multiple failed attempts from different IPs within a short timeframe
-
Geographically impossible location changes between attempts
-
Failed attempts from known high-risk ISPs or locations
-
Systematic pattern in timing of attempts suggesting automation
-
Large number of unique IPs targeting single account