STRIDE Threat Classification
Learn the STRIDE methodology for systematically categorizing security threats: Spoofing, Tampering, Repudiation, Information Disclosure, Denial of Service, and Elevation of Privilege.
STRIDE is a threat classification framework developed at Microsoft in the late 1990s. It provides a systematic way to think about different types of security threats, ensuring you don't overlook common attack vectors during security analysis.
The acronym stands for six threat categories: Spoofing, Tampering, Repudiation, Information Disclosure, Denial of Service, and Elevation of Privilege. Each category maps to a security property you want to protect.
The Six Threat Categories
| Threat | Security Property | Description |
|---|---|---|
| Spoofing | Authentication | Pretending to be someone or something else |
| Tampering | Integrity | Modifying data or code without authorization |
| Repudiation | Non-repudiation | Denying an action occurred |
| Information Disclosure | Confidentiality | Exposing information to unauthorized parties |
| Denial of Service | Availability | Making a system unavailable |
| Elevation of Privilege | Authorization | Gaining capabilities beyond what's allowed |
Spoofing
Spoofing threats involve an attacker pretending to be someone or something they're not. This violates the authentication property of your system.
Common Spoofing Attacks
- Identity spoofing: Using stolen credentials to impersonate a legitimate user
- IP spoofing: Forging source IP addresses in network packets
- Email spoofing: Sending emails with forged sender addresses
- DNS spoofing: Redirecting domain lookups to malicious servers
- ARP spoofing: Poisoning ARP tables to intercept network traffic
Real-World Example: Session Hijacking
Consider a web application that stores session tokens in cookies:
# Vulnerable: Session token without proper validation
@app.route('/dashboard')
def dashboard():
session_token = request.cookies.get('session_id')
# No validation of token origin or integrity
user = get_user_from_session(session_token)
return render_template('dashboard.html', user=user)
An attacker who captures a session token (via XSS, network sniffing, or malware) can impersonate the victim by sending requests with the stolen token.
Mitigations for Spoofing
# Better: Bind sessions to additional factors
@app.route('/dashboard')
def dashboard():
session_token = request.cookies.get('session_id')
session_data = validate_session(session_token)
# Verify session is bound to this client
if session_data['ip'] != request.remote_addr:
log_security_event('Session IP mismatch', session_data)
abort(403)
if session_data['user_agent'] != request.headers.get('User-Agent'):
log_security_event('Session UA mismatch', session_data)
abort(403)
return render_template('dashboard.html', user=session_data['user'])
Key mitigations:
- Multi-factor authentication (MFA)
- Certificate-based authentication
- Session binding to IP/user-agent
- Short session lifetimes with refresh tokens
Tampering
Tampering threats involve unauthorized modification of data or code. This violates the integrity property.
Common Tampering Attacks
- Man-in-the-middle (MITM): Intercepting and modifying data in transit
- Parameter tampering: Modifying hidden form fields or URL parameters
- SQL injection: Altering database queries
- File tampering: Modifying configuration files or binaries
- Memory tampering: Altering program state at runtime
Real-World Example: Price Manipulation
E-commerce applications often pass price data through hidden form fields:
<!-- Vulnerable: Price in hidden field -->
<form action="/checkout" method="POST">
<input type="hidden" name="price" value="99.99">
<input type="hidden" name="product_id" value="12345">
<button type="submit">Buy Now</button>
</form>
An attacker can modify the hidden field using browser developer tools:
// Attacker modifies price before submission
document.querySelector('input[name="price"]').value = '0.01';
Mitigations for Tampering
# Better: Server-side price lookup
@app.route('/checkout', methods=['POST'])
def checkout():
product_id = request.form.get('product_id')
# Never trust client-provided prices
product = Product.query.get(product_id)
if not product:
abort(404)
# Use server-side price
price = product.current_price
# Optional: Verify with digital signature
expected_signature = hmac.new(
SECRET_KEY,
f"{product_id}:{price}".encode(),
hashlib.sha256
).hexdigest()
return process_order(product_id, price)
Key mitigations:
- Input validation on all user data
- Digital signatures/HMAC for sensitive data
- TLS for data in transit
- File integrity monitoring (AIDE, Tripwire)
- Code signing and verification
Repudiation
Repudiation threats involve users denying they performed an action when there's no way to prove otherwise. This violates the non-repudiation property.
Common Repudiation Scenarios
- User claims they didn't make a purchase
- Admin denies deleting critical files
- Attacker covers tracks by deleting logs
- Developer claims they didn't push malicious code
Real-World Example: Missing Audit Trail
# Vulnerable: No audit logging
@app.route('/admin/delete-user/<user_id>', methods=['POST'])
@admin_required
def delete_user(user_id):
user = User.query.get(user_id)
db.session.delete(user)
db.session.commit()
return redirect('/admin/users')
Without logging, there's no way to determine who deleted a user or when it happened.
Mitigations for Repudiation
# Better: Comprehensive audit logging
@app.route('/admin/delete-user/<user_id>', methods=['POST'])
@admin_required
def delete_user(user_id):
user = User.query.get(user_id)
# Log before the action
audit_log.info(
'User deletion initiated',
extra={
'action': 'DELETE_USER',
'target_user_id': user_id,
'target_username': user.username,
'admin_id': current_user.id,
'admin_ip': request.remote_addr,
'timestamp': datetime.utcnow().isoformat(),
'request_id': request.headers.get('X-Request-ID')
}
)
# Soft delete preserves evidence
user.deleted_at = datetime.utcnow()
user.deleted_by = current_user.id
db.session.commit()
return redirect('/admin/users')
Key mitigations:
- Comprehensive audit logging
- Tamper-evident log storage (append-only, signed)
- Digital signatures for critical transactions
- Centralized log aggregation
- Regular log review and alerting
Information Disclosure
Information disclosure threats involve exposing sensitive data to unauthorized parties. This violates the confidentiality property.
Common Information Disclosure Vectors
- Error messages: Stack traces revealing internal paths
- API responses: Over-fetching sensitive fields
- Logs: Accidentally logging passwords or tokens
- Metadata: EXIF data in images, Git history
- Timing attacks: Inferring data from response times
Real-World Example: Verbose Errors
# Vulnerable: Detailed error messages in production
@app.route('/login', methods=['POST'])
def login():
try:
user = User.query.filter_by(email=request.form['email']).first()
if not user:
raise Exception(f"User {request.form['email']} not found in database")
if not check_password(request.form['password'], user.password_hash):
raise Exception(f"Invalid password for user {user.id}")
return login_user(user)
except Exception as e:
# Leaks whether email exists and internal user IDs
return str(e), 401
Mitigations for Information Disclosure
# Better: Generic error messages
@app.route('/login', methods=['POST'])
def login():
user = User.query.filter_by(email=request.form['email']).first()
# Use constant-time comparison to prevent timing attacks
if not user or not user.check_password(request.form['password']):
# Log details server-side, return generic message to client
app.logger.warning(
'Failed login attempt',
extra={'email': request.form['email'], 'ip': request.remote_addr}
)
# Same message whether user exists or password is wrong
return {'error': 'Invalid credentials'}, 401
return login_user(user)
Key mitigations:
- Generic error messages in production
- Principle of least privilege for data access
- Encryption for sensitive data at rest and in transit
- Data masking in logs
- Regular secrets scanning in code/configs
Denial of Service
Denial of Service (DoS) threats make a system unavailable to legitimate users. This violates the availability property.
Common DoS Attack Vectors
- Volume-based: Flooding with traffic (UDP floods, amplification)
- Protocol-based: Exploiting protocol weaknesses (SYN floods)
- Application-layer: Expensive operations (regex DoS, zip bombs)
- Resource exhaustion: Memory, disk, file descriptors
- Algorithmic complexity: O(n²) operations with large inputs
Real-World Example: ReDoS (Regular Expression DoS)
# Vulnerable: Catastrophic backtracking regex
import re
# This regex has exponential time complexity with certain inputs
email_pattern = re.compile(r'^([a-zA-Z0-9]+)+@example.com$')
@app.route('/validate-email')
def validate_email():
email = request.args.get('email')
# Malicious input: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaa!'
# This can hang the server for minutes
if email_pattern.match(email):
return 'Valid'
return 'Invalid'
Mitigations for Denial of Service
# Better: Safe regex with timeout
import re
import signal
from functools import wraps
# Use non-backtracking pattern
email_pattern = re.compile(r'^[a-zA-Z0-9]+@example\.com$')
def timeout(seconds):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
signal.alarm(seconds)
try:
return func(*args, **kwargs)
finally:
signal.alarm(0)
return wrapper
return decorator
@app.route('/validate-email')
@timeout(1) # Kill if takes more than 1 second
def validate_email():
email = request.args.get('email', '')[:255] # Limit input length
if email_pattern.match(email):
return 'Valid'
return 'Invalid'
Key mitigations:
- Rate limiting and throttling
- Input size limits
- Timeout for expensive operations
- Auto-scaling infrastructure
- CDN and DDoS protection services
- Circuit breakers for downstream services
Elevation of Privilege
Elevation of Privilege (EoP) threats involve gaining capabilities beyond what's authorized. This violates the authorization property.
Common EoP Attacks
- Privilege escalation: Normal user gains admin rights
- IDOR: Accessing other users' resources by changing IDs
- Path traversal: Escaping intended directories
- Container escape: Breaking out of container isolation
- JWT manipulation: Forging tokens with elevated claims
Real-World Example: Insecure Direct Object Reference (IDOR)
# Vulnerable: No ownership verification
@app.route('/api/documents/<doc_id>')
@login_required
def get_document(doc_id):
# User can access ANY document by changing the ID
document = Document.query.get(doc_id)
if not document:
abort(404)
return document.to_dict()
An attacker can enumerate document IDs to access other users' files:
# Attacker iterates through document IDs
for i in {1..10000}; do
curl "https://api.example.com/documents/$i" -H "Cookie: session=attacker_session"
done
Mitigations for Elevation of Privilege
# Better: Verify ownership
@app.route('/api/documents/<doc_id>')
@login_required
def get_document(doc_id):
document = Document.query.get(doc_id)
if not document:
abort(404)
# Verify the current user owns this document
if document.owner_id != current_user.id:
# Log potential attack
app.logger.warning(
'IDOR attempt detected',
extra={
'user_id': current_user.id,
'requested_doc_id': doc_id,
'doc_owner_id': document.owner_id
}
)
abort(403)
return document.to_dict()
# Even better: Use UUIDs instead of sequential IDs
# and scope queries to the current user
@app.route('/api/documents/<uuid:doc_id>')
@login_required
def get_document_v2(doc_id):
document = Document.query.filter_by(
id=doc_id,
owner_id=current_user.id
).first_or_404()
return document.to_dict()
Key mitigations:
- Always verify authorization, not just authentication
- Scope database queries to current user
- Use UUIDs instead of sequential IDs
- Implement role-based access control (RBAC)
- Regular privilege audits
- Container and VM hardening
Applying STRIDE in Practice
When analyzing a system, walk through each component and ask:
- Spoofing: Can an attacker pretend to be this component or user?
- Tampering: Can data flowing through here be modified?
- Repudiation: Can actions here be denied without evidence?
- Information Disclosure: Can sensitive data leak from here?
- Denial of Service: Can this component be overwhelmed?
- Elevation of Privilege: Can access controls be bypassed here?
STRIDE per Element
Different system elements are vulnerable to different STRIDE categories:
| Element | S | T | R | I | D | E |
|---|---|---|---|---|---|---|
| External Entity | ✓ | ✓ | ||||
| Process | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Data Store | ✓ | ? | ✓ | ✓ | ||
| Data Flow | ✓ | ✓ | ✓ |
This matrix helps you focus your analysis—processes are vulnerable to all six categories, while data flows are primarily concerned with tampering, disclosure, and availability.
Summary
STRIDE provides a comprehensive framework for identifying threats:
- Spoofing → Strengthen authentication
- Tampering → Protect integrity
- Repudiation → Implement audit logging
- Information Disclosure → Ensure confidentiality
- Denial of Service → Maintain availability
- Elevation of Privilege → Enforce authorization
In the next section, we'll learn how to prioritize these threats using the DREAD scoring system.
Found an issue?