forked from claw/flaskpaste
293 lines
8.0 KiB
Markdown
293 lines
8.0 KiB
Markdown
# Security
|
|
|
|
FlaskPaste is designed with security as a primary concern. This document describes security features, best practices, and responsible disclosure procedures.
|
|
|
|
## Security Features
|
|
|
|
### Authentication
|
|
|
|
**Client Certificate Authentication (mTLS)**
|
|
|
|
FlaskPaste supports mutual TLS authentication via reverse proxy:
|
|
|
|
1. Reverse proxy terminates TLS and validates client certificates
|
|
2. Proxy extracts SHA1 fingerprint and passes via `X-SSL-Client-SHA1` header
|
|
3. FlaskPaste uses fingerprint for ownership tracking and authorization
|
|
|
|
```nginx
|
|
# nginx example
|
|
location / {
|
|
proxy_pass http://127.0.0.1:5000;
|
|
proxy_set_header X-SSL-Client-SHA1 $ssl_client_fingerprint;
|
|
}
|
|
```
|
|
|
|
**Proxy Trust Validation**
|
|
|
|
Defense-in-depth against header spoofing:
|
|
|
|
```bash
|
|
export FLASKPASTE_PROXY_SECRET="shared-secret"
|
|
```
|
|
|
|
The proxy must send `X-Proxy-Secret` header; requests without valid secret are treated as anonymous.
|
|
|
|
### Password Protection
|
|
|
|
Pastes can be password-protected using PBKDF2-HMAC-SHA256:
|
|
|
|
- 600,000 iterations (OWASP 2023 recommendation)
|
|
- 32-byte random salt per password
|
|
- Constant-time comparison prevents timing attacks
|
|
- Passwords never logged or stored in plaintext
|
|
|
|
```bash
|
|
# Create protected paste
|
|
curl -H "X-Paste-Password: secret" --data-binary @file.txt https://paste.example.com/
|
|
|
|
# Access protected paste
|
|
curl -H "X-Paste-Password: secret" https://paste.example.com/abc123/raw
|
|
```
|
|
|
|
### End-to-End Encryption
|
|
|
|
CLI supports client-side AES-256-GCM encryption:
|
|
|
|
- Key generated locally, never sent to server
|
|
- Key appended to URL as fragment (`#key`) which browsers never transmit
|
|
- Server stores only opaque ciphertext
|
|
- Zero-knowledge: server cannot read content
|
|
|
|
```bash
|
|
./fpaste create -e secret.txt
|
|
# Returns: https://paste.example.com/abc123#<base64-key>
|
|
```
|
|
|
|
### Abuse Prevention
|
|
|
|
**Content-Hash Deduplication**
|
|
|
|
Prevents spam flooding by throttling repeated identical submissions:
|
|
|
|
```bash
|
|
FLASKPASTE_DEDUP_WINDOW=3600 # 1 hour window
|
|
FLASKPASTE_DEDUP_MAX=3 # Max 3 identical submissions
|
|
```
|
|
|
|
**Proof-of-Work**
|
|
|
|
Computational puzzle prevents automated spam:
|
|
|
|
```bash
|
|
FLASKPASTE_POW_DIFFICULTY=20 # Leading zero bits required
|
|
FLASKPASTE_POW_TTL=300 # Challenge validity (seconds)
|
|
```
|
|
|
|
**Entropy Enforcement**
|
|
|
|
Optional minimum entropy requirement to enforce encrypted uploads:
|
|
|
|
```bash
|
|
FLASKPASTE_MIN_ENTROPY=6.0 # Bits per byte (encrypted ~7.5-8.0)
|
|
FLASKPASTE_MIN_ENTROPY_SIZE=256 # Only check content >= this size
|
|
```
|
|
|
|
### URL Shortener Security
|
|
|
|
**Open Redirect Prevention**
|
|
|
|
Short URL creation validates target URLs:
|
|
|
|
- Only `http` and `https` schemes allowed (rejects `javascript:`, `data:`, `ftp:`, `file:`)
|
|
- Network location (hostname) required — rejects scheme-only URLs
|
|
- Maximum URL length: 2048 bytes
|
|
- Short IDs: 8-char base62 (`[a-zA-Z0-9]`) with `secrets.choice()` for unpredictability
|
|
- Redirect responses include `Cache-Control: no-store, no-cache` to prevent caching
|
|
|
|
**Access Controls**
|
|
|
|
- Creation: rate-limited + proof-of-work (same as paste creation)
|
|
- Redirect: lookup rate limiting prevents enumeration
|
|
- Deletion: owner authentication required
|
|
- Listing: authentication required, shows only own URLs
|
|
|
|
### Security Headers
|
|
|
|
All responses include:
|
|
|
|
| Header | Value |
|
|
|--------|-------|
|
|
| `Strict-Transport-Security` | `max-age=31536000; includeSubDomains` |
|
|
| `X-Content-Type-Options` | `nosniff` |
|
|
| `X-Frame-Options` | `DENY` |
|
|
| `X-XSS-Protection` | `1; mode=block` |
|
|
| `Content-Security-Policy` | `default-src 'none'` |
|
|
| `Referrer-Policy` | `no-referrer` |
|
|
| `Permissions-Policy` | `geolocation=(), microphone=(), camera=()` |
|
|
| `Cache-Control` | `no-store, no-cache, must-revalidate` |
|
|
| `Pragma` | `no-cache` |
|
|
|
|
### Request Tracing
|
|
|
|
All requests receive `X-Request-ID` header for log correlation and debugging. Pass your own ID to trace requests through reverse proxies.
|
|
|
|
## Input Validation
|
|
|
|
### Paste IDs
|
|
|
|
- Hexadecimal only (`[a-f0-9]+`)
|
|
- Configurable length (default 12 characters)
|
|
- Validated on all endpoints
|
|
|
|
### Short URL IDs
|
|
|
|
- Base62 only (`[a-zA-Z0-9]+`)
|
|
- 8 characters (configurable via `FLASKPASTE_SHORT_ID_LENGTH`)
|
|
- Validated on all `/s/` endpoints
|
|
|
|
### MIME Types
|
|
|
|
- Magic byte detection for binary formats
|
|
- Sanitized against injection
|
|
- Only safe characters allowed: `[a-z0-9!#$&\-^_.+]`
|
|
|
|
### Size Limits
|
|
|
|
```bash
|
|
FLASKPASTE_MAX_ANON=3145728 # 3 MiB for anonymous
|
|
FLASKPASTE_MAX_AUTH=52428800 # 50 MiB for authenticated
|
|
```
|
|
|
|
### Password Limits
|
|
|
|
- Maximum 1024 characters (prevents DoS via hashing)
|
|
- Unicode supported
|
|
- Special characters allowed
|
|
|
|
## SQL Injection Prevention
|
|
|
|
All database queries use parameterized statements:
|
|
|
|
```python
|
|
# Correct - parameterized
|
|
db.execute("SELECT * FROM pastes WHERE id = ?", (paste_id,))
|
|
|
|
# Never - string concatenation
|
|
db.execute(f"SELECT * FROM pastes WHERE id = '{paste_id}'")
|
|
```
|
|
|
|
## Database Security
|
|
|
|
- SQLite with WAL mode for better concurrency
|
|
- Foreign keys enforced
|
|
- Automatic cleanup of expired content
|
|
- No sensitive data in plaintext (passwords are hashed)
|
|
|
|
## Deployment Recommendations
|
|
|
|
### Reverse Proxy
|
|
|
|
Always deploy behind a TLS-terminating reverse proxy:
|
|
|
|
```
|
|
┌─────────┐ TLS ┌─────────┐ HTTP ┌───────────┐
|
|
│ Client │ ──────────── │ nginx │ ──────────── │ FlaskPaste│
|
|
│ │ mTLS opt │ HAProxy │ local │ │
|
|
└─────────┘ └─────────┘ └───────────┘
|
|
```
|
|
|
|
### Container Security
|
|
|
|
```dockerfile
|
|
# Run as non-root
|
|
USER app
|
|
|
|
# Read-only filesystem where possible
|
|
# Mount /app/data as writable volume
|
|
```
|
|
|
|
### Network Isolation
|
|
|
|
- FlaskPaste should only listen on localhost or internal network
|
|
- Reverse proxy handles external traffic
|
|
- Use firewall rules to restrict direct access
|
|
|
|
### Secrets Management
|
|
|
|
```bash
|
|
# Use strong, unique secrets
|
|
FLASKPASTE_PROXY_SECRET="$(openssl rand -hex 32)"
|
|
FLASKPASTE_POW_SECRET="$(openssl rand -hex 32)"
|
|
```
|
|
|
|
## Security Checklist
|
|
|
|
### Before Deployment
|
|
|
|
- [ ] Deploy behind TLS-terminating reverse proxy
|
|
- [ ] Configure `FLASKPASTE_PROXY_SECRET` if using auth headers
|
|
- [ ] Enable proof-of-work if public-facing
|
|
- [ ] Set appropriate size limits
|
|
- [ ] Configure paste expiry
|
|
|
|
### Ongoing
|
|
|
|
- [ ] Monitor logs for suspicious activity
|
|
- [ ] Keep dependencies updated
|
|
- [ ] Review access patterns
|
|
- [ ] Rotate secrets periodically
|
|
- [ ] Test backup/restore procedures
|
|
|
|
## Known Limitations
|
|
|
|
1. **No user accounts** - PKI handles identity
|
|
2. **Single-node only** - SQLite limits horizontal scaling
|
|
|
|
## Reporting Vulnerabilities
|
|
|
|
If you discover a security vulnerability:
|
|
|
|
1. **Do not** open a public issue
|
|
2. Email security concerns to the repository maintainer
|
|
3. Include:
|
|
- Description of the vulnerability
|
|
- Steps to reproduce
|
|
- Potential impact
|
|
- Suggested fix (if any)
|
|
|
|
We aim to acknowledge reports within 48 hours and provide a fix timeline within 7 days.
|
|
|
|
## Security Updates
|
|
|
|
Security fixes are released as soon as possible. Subscribe to repository releases for notifications.
|
|
|
|
## Threat Model
|
|
|
|
### In Scope
|
|
|
|
- Unauthorized access to pastes
|
|
- Content injection attacks
|
|
- Authentication bypass
|
|
- Information disclosure
|
|
- Denial of service (application-level)
|
|
- Open redirect via URL shortener
|
|
|
|
### Out of Scope
|
|
|
|
- Physical access to server
|
|
- Compromise of reverse proxy
|
|
- Client-side vulnerabilities
|
|
- Network-level DoS
|
|
- Social engineering
|
|
|
|
## Version History
|
|
|
|
| Version | Security Changes |
|
|
|---------|------------------|
|
|
| 1.6.0 | URL shortener with open redirect prevention, scheme allowlist, target URL validation |
|
|
| 1.5.0 | Pentest remediation (15 items): timing attack prevention, serial collision detection, lookup rate limiting, content hash locking, anti-flood memory limits, CLI path validation, SSL hostname verification, config permission checks |
|
|
| 1.4.0 | Anti-flood dynamic PoW, IP-based rate limiting, audit logging |
|
|
| 1.2.0 | Password protection with PBKDF2, code modernization |
|
|
| 1.1.0 | E2E encryption, entropy enforcement, burn-after-read |
|
|
| 1.0.0 | Initial release with core security features |
|