Detection rules › Panther
OpenAI Credential Stuffing
Detects credential stuffing attacks against OpenAI accounts by tracking the number of distinct source IP addresses submitting failed login attempts against the same email address within a short timeframe. Unlike brute force from a single IP, credential stuffing distributes attempts across many IPs to evade rate limiting. This rule complements OpenAI.BruteForce.Login.Success, which confirms account compromise once a successful login follows the failures.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Credential Access | T1110.004 Brute Force: Credential Stuffing |
Rule body yaml
AnalysisType: rule
RuleID: "OpenAI.CredentialStuffing"
DisplayName: "OpenAI Credential Stuffing"
Filename: openai_credential_stuffing.py
Enabled: true
LogTypes:
- OpenAI.Audit
Severity: Medium
Threshold: 5
DedupPeriodMinutes: 30
Description: >
Detects credential stuffing attacks against OpenAI accounts by tracking the number of
distinct source IP addresses submitting failed login attempts against the same email
address within a short timeframe. Unlike brute force from a single IP, credential
stuffing distributes attempts across many IPs to evade rate limiting. This rule
complements OpenAI.BruteForce.Login.Success, which confirms account compromise
once a successful login follows the failures.
Runbook: |
1. Review the distinct source IPs from the failed login attempts and check if they belong to known anonymization infrastructure (VPNs, Tor exit nodes, residential proxies) using threat intelligence
2. Query OpenAI audit logs for all login.succeeded and login.failed events against the targeted email address within the past 30 minutes to identify all contributing source IPs and determine if any attempt succeeded — the alert context shows only the IP of the final triggering event, not all contributing IPs
3. Check for other alerts involving the targeted email address in the past 7 days and look for login.succeeded events from unfamiliar locations or devices to assess whether account compromise has already occurred
Reference: https://platform.openai.com/docs/api-reference/audit-logs
Tags:
- OpenAI
- Credential Access
Reports:
MITRE ATT&CK:
- TA0006:T1110.004 # Credential Access: Credential Stuffing
Tests:
- Name: Match - Failed Login
ExpectedResult: true
Log:
id: "audit_log-failed001"
type: "login.failed"
effective_at: 1702857600
object: "organization.audit_log"
actor:
type: "session"
session:
user:
id: "user-123"
email: "user@company.com"
ip_address: "203.0.113.100"
user_agent: "Mozilla/5.0"
login_failed:
error_code: "invalid_credentials"
error_message: "Invalid username or password"
- Name: Match - Failed Login Different IP
ExpectedResult: true
Log:
id: "audit_log-failed002"
type: "login.failed"
effective_at: 1702857610
object: "organization.audit_log"
actor:
type: "session"
session:
user:
id: "user-123"
email: "user@company.com"
ip_address: "198.51.100.25"
user_agent: "python-requests/2.28.0"
login_failed:
error_code: "invalid_credentials"
error_message: "Invalid username or password"
- Name: No Match - Successful Login
ExpectedResult: false
Log:
id: "audit_log-success001"
type: "login.succeeded"
effective_at: 1702857620
object: "organization.audit_log"
actor:
type: "session"
session:
user:
id: "user-123"
email: "user@company.com"
ip_address: "203.0.113.100"
user_agent: "Mozilla/5.0"
- Name: No Match - Non-Login Event
ExpectedResult: false
Log:
id: "audit_log-api001"
type: "api_key.created"
effective_at: 1702857700
object: "organization.audit_log"
actor:
type: "session"
session:
user:
id: "user-123"
email: "user@company.com"
ip_address: "203.0.113.100"
user_agent: "Mozilla/5.0"
Detection logic
Condition
type eq "login.failed"
Indicators
Each row is a field, operator, and value that the rule matches. The corpus column counts how many other rules in the catalog look for the same combination: high numbers point to widely-used, community-vetted indicators. Blank or 1 shows that the indicator is specific to this rule.
| Field | Kind | Values |
|---|---|---|
type | eq |
|
Output fields
Fields the rule emits when it matches. Chronicle authors list these in the outcome block; they appear on the detection and $risk_score drives alerting. Sentinel / Defender XDR rules build them up through project / summarize / extend stages. Sentinel maps these into alert fields via entityMappings and customDetails; Defender XDR custom detections surface them as alert fields directly.
| Field | Source |
|---|---|
email | actor.session.user.email |
ip_address | actor.session.ip_address |
error_code | login_failed.error_code |