436 lines
10 KiB
Markdown
436 lines
10 KiB
Markdown
# Security Misconfiguration Reference
|
|
|
|
## Overview
|
|
|
|
Security misconfiguration is one of the most common vulnerabilities. It occurs when security settings are not defined, implemented incorrectly, or left at insecure defaults. This includes missing security headers, overly permissive CORS, debug mode in production, and exposed sensitive endpoints.
|
|
|
|
---
|
|
|
|
## Security Headers
|
|
|
|
### Missing Headers
|
|
|
|
```python
|
|
# VULNERABLE: No security headers
|
|
@app.route('/')
|
|
def index():
|
|
return render_template('index.html')
|
|
|
|
# SAFE: Security headers configured
|
|
@app.after_request
|
|
def add_security_headers(response):
|
|
response.headers['X-Content-Type-Options'] = 'nosniff'
|
|
response.headers['X-Frame-Options'] = 'DENY'
|
|
response.headers['X-XSS-Protection'] = '1; mode=block'
|
|
response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
|
|
response.headers['Content-Security-Policy'] = "default-src 'self'"
|
|
response.headers['Referrer-Policy'] = 'strict-origin-when-cross-origin'
|
|
response.headers['Permissions-Policy'] = 'geolocation=(), microphone=()'
|
|
return response
|
|
```
|
|
|
|
### Header Checklist
|
|
|
|
| Header | Purpose | Secure Value |
|
|
|--------|---------|--------------|
|
|
| `X-Content-Type-Options` | Prevent MIME sniffing | `nosniff` |
|
|
| `X-Frame-Options` | Prevent clickjacking | `DENY` or `SAMEORIGIN` |
|
|
| `Strict-Transport-Security` | Force HTTPS | `max-age=31536000; includeSubDomains` |
|
|
| `Content-Security-Policy` | Prevent XSS, injection | Restrictive policy |
|
|
| `Referrer-Policy` | Control referrer leakage | `strict-origin-when-cross-origin` |
|
|
| `Permissions-Policy` | Disable browser features | Disable unused features |
|
|
|
|
### Content Security Policy
|
|
|
|
```python
|
|
# VULNERABLE: Overly permissive CSP
|
|
"Content-Security-Policy: default-src *"
|
|
"Content-Security-Policy: script-src 'unsafe-inline' 'unsafe-eval'"
|
|
|
|
# SAFE: Restrictive CSP
|
|
"Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-{random}'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'"
|
|
```
|
|
|
|
---
|
|
|
|
## CORS Misconfiguration
|
|
|
|
### Dangerous Patterns
|
|
|
|
```python
|
|
# VULNERABLE: Allow all origins
|
|
CORS(app, origins='*')
|
|
Access-Control-Allow-Origin: *
|
|
|
|
# VULNERABLE: Reflect origin without validation
|
|
@app.after_request
|
|
def add_cors(response):
|
|
response.headers['Access-Control-Allow-Origin'] = request.headers.get('Origin')
|
|
response.headers['Access-Control-Allow-Credentials'] = 'true'
|
|
return response
|
|
|
|
# VULNERABLE: Wildcard with credentials (browsers block, but shows misconfiguration)
|
|
Access-Control-Allow-Origin: *
|
|
Access-Control-Allow-Credentials: true
|
|
|
|
# VULNERABLE: Null origin allowed
|
|
Access-Control-Allow-Origin: null
|
|
```
|
|
|
|
### Safe CORS Configuration
|
|
|
|
```python
|
|
# SAFE: Explicit allowlist
|
|
ALLOWED_ORIGINS = {
|
|
'https://app.example.com',
|
|
'https://admin.example.com'
|
|
}
|
|
|
|
@app.after_request
|
|
def add_cors(response):
|
|
origin = request.headers.get('Origin')
|
|
if origin in ALLOWED_ORIGINS:
|
|
response.headers['Access-Control-Allow-Origin'] = origin
|
|
response.headers['Access-Control-Allow-Credentials'] = 'true'
|
|
response.headers['Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS'
|
|
response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization'
|
|
return response
|
|
```
|
|
|
|
---
|
|
|
|
## Debug Mode in Production
|
|
|
|
### Dangerous Patterns
|
|
|
|
```python
|
|
# VULNERABLE: Debug mode enabled
|
|
# Flask
|
|
app.run(debug=True)
|
|
DEBUG = True
|
|
|
|
# Django
|
|
DEBUG = True # in settings.py
|
|
|
|
# Express
|
|
app.set('env', 'development')
|
|
|
|
# Spring Boot
|
|
spring.devtools.restart.enabled=true
|
|
management.endpoints.web.exposure.include=*
|
|
```
|
|
|
|
### Detection
|
|
|
|
```python
|
|
# Check for debug indicators
|
|
if app.debug:
|
|
# Exposes stack traces, allows code execution in some frameworks
|
|
pass
|
|
|
|
# Check environment variables
|
|
if os.environ.get('DEBUG') == 'true':
|
|
pass
|
|
if os.environ.get('FLASK_ENV') == 'development':
|
|
pass
|
|
```
|
|
|
|
---
|
|
|
|
## Default Credentials
|
|
|
|
### Patterns to Flag
|
|
|
|
```python
|
|
# VULNERABLE: Default/weak credentials
|
|
username = 'admin'
|
|
password = 'admin'
|
|
password = 'password'
|
|
password = '123456'
|
|
password = 'changeme'
|
|
password = 'default'
|
|
|
|
# VULNERABLE: Well-known default credentials
|
|
# Database defaults
|
|
DB_PASSWORD = 'root'
|
|
DB_PASSWORD = 'postgres'
|
|
DB_PASSWORD = 'mysql'
|
|
|
|
# Admin panel defaults
|
|
ADMIN_PASSWORD = 'admin123'
|
|
SECRET_KEY = 'development-secret-key'
|
|
```
|
|
|
|
### Configuration Files to Check
|
|
|
|
```yaml
|
|
# Docker Compose
|
|
services:
|
|
db:
|
|
environment:
|
|
MYSQL_ROOT_PASSWORD: root # VULNERABLE
|
|
POSTGRES_PASSWORD: postgres # VULNERABLE
|
|
|
|
# Kubernetes Secrets (base64 encoded defaults)
|
|
apiVersion: v1
|
|
kind: Secret
|
|
data:
|
|
password: YWRtaW4= # 'admin' base64 encoded - VULNERABLE
|
|
```
|
|
|
|
---
|
|
|
|
## Exposed Endpoints
|
|
|
|
### Admin/Debug Endpoints
|
|
|
|
```python
|
|
# VULNERABLE: Exposed debug endpoints
|
|
@app.route('/debug')
|
|
@app.route('/admin') # without authentication
|
|
@app.route('/metrics') # without authentication
|
|
@app.route('/health') # may expose sensitive info
|
|
@app.route('/env')
|
|
@app.route('/config')
|
|
@app.route('/phpinfo.php')
|
|
@app.route('/.git')
|
|
@app.route('/.env')
|
|
|
|
# Spring Boot Actuator endpoints
|
|
/actuator/env
|
|
/actuator/heapdump
|
|
/actuator/configprops
|
|
/actuator/mappings
|
|
```
|
|
|
|
### Protection
|
|
|
|
```python
|
|
# SAFE: Protect sensitive endpoints
|
|
@app.route('/admin')
|
|
@require_admin
|
|
def admin_panel():
|
|
pass
|
|
|
|
@app.route('/metrics')
|
|
@require_internal_network
|
|
def metrics():
|
|
pass
|
|
|
|
# Spring Boot: Restrict actuator
|
|
management.endpoints.web.exposure.include=health,info
|
|
management.endpoint.health.show-details=never
|
|
```
|
|
|
|
---
|
|
|
|
## TLS/SSL Misconfiguration
|
|
|
|
### Insecure Patterns
|
|
|
|
```python
|
|
# VULNERABLE: SSL verification disabled
|
|
requests.get(url, verify=False)
|
|
urllib3.disable_warnings()
|
|
|
|
# VULNERABLE: Weak TLS versions
|
|
ssl_context.minimum_version = ssl.TLSVersion.TLSv1 # Use TLS 1.2+
|
|
|
|
# VULNERABLE: Weak cipher suites
|
|
ssl_context.set_ciphers('ALL')
|
|
ssl_context.set_ciphers('DEFAULT')
|
|
```
|
|
|
|
### Secure Configuration
|
|
|
|
```python
|
|
# SAFE: Proper TLS configuration
|
|
import ssl
|
|
|
|
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
|
|
context.minimum_version = ssl.TLSVersion.TLSv1_2
|
|
context.set_ciphers('ECDHE+AESGCM:DHE+AESGCM:ECDHE+CHACHA20')
|
|
context.verify_mode = ssl.CERT_REQUIRED
|
|
context.check_hostname = True
|
|
```
|
|
|
|
---
|
|
|
|
## Directory Listing
|
|
|
|
### Dangerous Patterns
|
|
|
|
```nginx
|
|
# VULNERABLE: Directory listing enabled
|
|
# Nginx
|
|
autoindex on;
|
|
|
|
# Apache
|
|
Options +Indexes
|
|
|
|
# Python
|
|
python -m http.server # Lists directories by default
|
|
```
|
|
|
|
### Secure Configuration
|
|
|
|
```nginx
|
|
# SAFE: Directory listing disabled
|
|
# Nginx
|
|
autoindex off;
|
|
|
|
# Apache
|
|
Options -Indexes
|
|
```
|
|
|
|
---
|
|
|
|
## Verbose Error Messages
|
|
|
|
### Dangerous Patterns
|
|
|
|
```python
|
|
# VULNERABLE: Detailed errors in response
|
|
@app.errorhandler(Exception)
|
|
def handle_error(e):
|
|
return jsonify({
|
|
'error': str(e),
|
|
'traceback': traceback.format_exc(),
|
|
'query': last_executed_query,
|
|
'config': app.config
|
|
}), 500
|
|
|
|
# VULNERABLE: Stack traces exposed
|
|
app.config['PROPAGATE_EXCEPTIONS'] = True
|
|
```
|
|
|
|
### Secure Error Handling
|
|
|
|
```python
|
|
# SAFE: Generic error messages
|
|
@app.errorhandler(Exception)
|
|
def handle_error(e):
|
|
app.logger.error(f"Error: {e}", exc_info=True) # Log details server-side
|
|
return jsonify({'error': 'An unexpected error occurred'}), 500
|
|
```
|
|
|
|
---
|
|
|
|
## Cookie Security
|
|
|
|
### Insecure Patterns
|
|
|
|
```python
|
|
# VULNERABLE: Insecure cookie settings
|
|
response.set_cookie('session', value) # Missing flags
|
|
|
|
# VULNERABLE: Explicit insecure flags
|
|
response.set_cookie('session', value, secure=False, httponly=False, samesite='None')
|
|
```
|
|
|
|
### Secure Cookie Configuration
|
|
|
|
```python
|
|
# SAFE: Secure cookie settings
|
|
response.set_cookie(
|
|
'session',
|
|
value,
|
|
secure=True, # HTTPS only
|
|
httponly=True, # No JavaScript access
|
|
samesite='Lax', # CSRF protection
|
|
max_age=3600, # Reasonable expiration
|
|
path='/',
|
|
domain='.example.com'
|
|
)
|
|
|
|
# Flask session configuration
|
|
app.config['SESSION_COOKIE_SECURE'] = True
|
|
app.config['SESSION_COOKIE_HTTPONLY'] = True
|
|
app.config['SESSION_COOKIE_SAMESITE'] = 'Lax'
|
|
```
|
|
|
|
---
|
|
|
|
## Permissive File Permissions
|
|
|
|
### Dangerous Patterns
|
|
|
|
```python
|
|
# VULNERABLE: World-readable sensitive files
|
|
os.chmod(config_file, 0o777)
|
|
os.chmod(private_key, 0o644)
|
|
|
|
# VULNERABLE: Overly permissive umask
|
|
os.umask(0o000)
|
|
```
|
|
|
|
### Secure Permissions
|
|
|
|
```python
|
|
# SAFE: Restrictive permissions
|
|
os.chmod(config_file, 0o600) # Owner read/write only
|
|
os.chmod(private_key, 0o400) # Owner read only
|
|
os.chmod(script, 0o700) # Owner execute only
|
|
```
|
|
|
|
---
|
|
|
|
## HTTP Methods
|
|
|
|
### Dangerous Patterns
|
|
|
|
```python
|
|
# VULNERABLE: All methods allowed
|
|
@app.route('/api/data', methods=['GET', 'POST', 'PUT', 'DELETE', 'TRACE', 'OPTIONS'])
|
|
|
|
# VULNERABLE: TRACE method enabled (XST attacks)
|
|
# VULNERABLE: Unnecessary methods on sensitive endpoints
|
|
```
|
|
|
|
### Secure Configuration
|
|
|
|
```python
|
|
# SAFE: Explicit method restrictions
|
|
@app.route('/api/data', methods=['GET'])
|
|
def get_data():
|
|
pass
|
|
|
|
@app.route('/api/data', methods=['POST'])
|
|
@require_auth
|
|
def create_data():
|
|
pass
|
|
```
|
|
|
|
---
|
|
|
|
## Grep Patterns for Detection
|
|
|
|
```bash
|
|
# Debug mode
|
|
grep -rn "debug.*=.*[Tt]rue\|DEBUG.*=.*[Tt]rue" --include="*.py" --include="*.js" --include="*.json"
|
|
|
|
# CORS wildcards
|
|
grep -rn "Access-Control-Allow-Origin.*\*\|origins.*\*\|origin.*\*" --include="*.py" --include="*.js"
|
|
|
|
# SSL verification disabled
|
|
grep -rn "verify.*=.*[Ff]alse\|rejectUnauthorized.*false\|NODE_TLS_REJECT_UNAUTHORIZED" --include="*.py" --include="*.js"
|
|
|
|
# Default credentials
|
|
grep -rn "password.*=.*['\"]admin\|password.*=.*['\"]root\|password.*=.*['\"]123456" --include="*.py" --include="*.yaml" --include="*.yml"
|
|
|
|
# Missing security headers (check for absence)
|
|
grep -rn "after_request\|middleware" --include="*.py" | grep -v "X-Content-Type-Options\|X-Frame-Options"
|
|
|
|
# Exposed endpoints
|
|
grep -rn "@app.route.*debug\|@app.route.*admin\|@app.route.*config\|/actuator" --include="*.py" --include="*.java"
|
|
```
|
|
|
|
---
|
|
|
|
## References
|
|
|
|
- [OWASP Security Misconfiguration](https://owasp.org/Top10/A05_2021-Security_Misconfiguration/)
|
|
- [OWASP HTTP Security Headers](https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html)
|
|
- [OWASP TLS Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Transport_Layer_Security_Cheat_Sheet.html)
|
|
- [CWE-16: Configuration](https://cwe.mitre.org/data/definitions/16.html)
|