You need a way to spot NFC relay attacks before they drain accounts. Here's a detection script you can deploy in your transaction monitoring pipeline today.
Purpose of the Script
This script identifies transaction patterns consistent with NFC relay fraud, particularly the type enabled by malware like WindRelay. This malware captures contactless Cardholder Data and forwards it in real time to attackers at payment terminals or ATMs.
The script flags transactions based on behavioral anomalies that occur when an attacker uses relayed NFC data while the victim's phone remains in a different location. You'll integrate this into your existing fraud detection layer, not replace it.
Key indicators this script monitors:
- Simultaneous device activity in conflicting locations
- Contactless transactions during active mobile banking sessions
- ATM withdrawals immediately following contactless authorizations
- Multiple rapid-fire contactless attempts (relay connection testing)
- Contactless transactions outside the cardholder's established geographic pattern
Prerequisites
Before you implement this script, ensure you have:
- Access to real-time transaction data streams (authorization requests, not just settlements)
- Device fingerprinting or mobile app session logs that timestamp user activity
- Geographic data for both card transactions and mobile banking sessions
- A rule engine or fraud scoring system where you can inject custom logic
- The ability to flag transactions for manual review without auto-declining (you'll tune thresholds before going live)
You don't need machine learning infrastructure for this. The script uses rule-based logic that you can implement in SQL, Python, or your fraud platform's native scripting language.
The Detection Script
# NFC Relay Fraud Detection Rules
# Deploy in your real-time authorization pipeline
def evaluate_nfc_relay_risk(transaction, recent_activity):
risk_score = 0
flags = []
# Rule 1: Contactless transaction during active mobile session
if transaction.entry_mode == "CONTACTLESS":
active_sessions = get_mobile_sessions(
customer_id=transaction.customer_id,
time_window_minutes=15
)
if active_sessions:
last_session = active_sessions[0]
distance_km = calculate_distance(
transaction.location,
last_session.location
)
# Flag if mobile session and transaction are >10km apart
if distance_km > 10:
risk_score += 40
flags.append("MOBILE_SESSION_LOCATION_MISMATCH")
# Rule 2: ATM withdrawal after contactless purchase
if transaction.terminal_type == "ATM" and transaction.entry_mode == "CONTACTLESS":
recent_purchases = get_transactions(
customer_id=transaction.customer_id,
time_window_minutes=30,
terminal_type="POS"
)
if recent_purchases:
risk_score += 35
flags.append("ATM_AFTER_CONTACTLESS_POS")
# Rule 3: Multiple rapid contactless attempts
recent_contactless = get_transactions(
customer_id=transaction.customer_id,
time_window_minutes=5,
entry_mode="CONTACTLESS"
)
if len(recent_contactless) >= 3:
risk_score += 30
flags.append("RAPID_CONTACTLESS_ATTEMPTS")
# Rule 4: Contactless outside established pattern
customer_profile = get_customer_profile(transaction.customer_id)
if not is_within_normal_geography(
transaction.location,
customer_profile.typical_locations
):
if transaction.entry_mode == "CONTACTLESS":
risk_score += 25
flags.append("GEOGRAPHIC_ANOMALY_CONTACTLESS")
# Rule 5: First contactless transaction ever
if not customer_profile.has_contactless_history:
if transaction.entry_mode == "CONTACTLESS":
risk_score += 20
flags.append("FIRST_CONTACTLESS_USE")
return {
"risk_score": risk_score,
"flags": flags,
"action": determine_action(risk_score)
}
def determine_action(score):
if score >= 70:
return "DECLINE_AND_ALERT"
elif score >= 50:
return "STEP_UP_AUTH" # Trigger MFA
elif score >= 30:
return "MANUAL_REVIEW"
else:
return "APPROVE"
Customization Options
Adjust geographic thresholds based on your market:
If you serve dense urban areas, reduce the 10km threshold in Rule 1 to 5km. In rural markets where customers travel farther, increase it to 25km. Check your false positive rate after two weeks and tune accordingly.
Weight the rules for your risk appetite:
The risk scores (40, 35, 30, etc.) reflect a moderate risk posture. If you're seeing actual NFC relay fraud, increase the Rule 1 score to 60. If you're declining too many legitimate transactions, lower Rule 5 to 10.
Add merchant category filtering:
Insert this check before Rule 1:
# Skip scoring for low-risk merchant categories
if transaction.mcc in ["5411", "5912", "5541"]: # Grocery, drug stores, gas
return {"risk_score": 0, "flags": [], "action": "APPROVE"}
Integrate with your existing velocity checks:
If you already monitor transaction velocity, combine this script's output with those scores rather than running parallel systems.
Configure the time windows:
The 15-minute window in Rule 1 assumes the victim's mobile session is still active during the attack. If your session timeout is 10 minutes, adjust the window to 10. The 30-minute window in Rule 2 reflects the typical gap between a contactless purchase and an ATM withdrawal in observed attacks.
Validation Steps
Week 1: Shadow Mode
Run the script against live transactions but don't take automated action. Log every flag and score. You're looking for:
- How many transactions score above 50 (would trigger step-up auth)
- How many score above 70 (would decline)
- Whether any known fraud cases from the past 90 days would have been caught
Week 2: Tune Thresholds
Review flagged transactions with your fraud analysts. Calculate your precision: what percentage of high-scoring transactions were actually suspicious? If you're flagging 1,000 transactions per day and only 10 are worth reviewing, your thresholds are too sensitive.
Week 3: Enable Step-Up Authentication
For transactions scoring 50-69, trigger Multi-Factor Authentication before approving. Monitor completion rates. If legitimate customers abandon 30% of these challenges, your scoring is too aggressive.
Week 4: Enable Declines
Activate the decline action for scores above 70. Track your decline rate and dispute rate. A sudden spike in disputes means you're declining legitimate contactless transactions.
Ongoing: Monitor for Relay-Specific Patterns
Watch for clusters of Rule 1 flags (mobile session location mismatch). A single customer triggering this repeatedly within an hour is likely under active attack. That's when you call them immediately, not just decline the transaction.
The 13-minute call that victims receive from attackers impersonating their bank isn't just social engineering. It's the coordination mechanism that makes the relay work. Your detection window is that same 13 minutes. If you can spot the anomaly and challenge the transaction during the call, you break the attack before the victim taps their card.



