initial commit
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
# Self-Hosted Service Security Audit
|
||||
|
||||
A comprehensive methodology for assessing the security posture of any self-hosted web service (KasmVNC, Immich, Paperless, Mealie, etc.) behind a VPS reverse proxy.
|
||||
|
||||
## Audit Checklist (Run in Order)
|
||||
|
||||
### 1. Network Exposure
|
||||
```bash
|
||||
# DNS resolution — where does the domain point?
|
||||
dig +short <subdomain>.<domain> @1.1.1.1
|
||||
|
||||
# Compare against server's public IP
|
||||
curl -s ifconfig.me
|
||||
|
||||
# If they differ → VPS reverse proxy in front → good
|
||||
# If they match → service directly exposed → verify UFW
|
||||
```
|
||||
|
||||
### 2. TLS / Transport Security
|
||||
```bash
|
||||
# Full handshake info + headers
|
||||
curl -sI -v https://<domain>/ 2>&1 | grep -E '(SSL|TLS|certificate|HTTP/|Server|Strict-Transport|X-Content|X-Frame|CSP|Referrer)'
|
||||
|
||||
# Certificate details
|
||||
echo | openssl s_client -servername <domain> -connect <domain>:443 2>/dev/null | openssl x509 -noout -text | grep -E '(Subject:|Issuer:|Not Before|Not After|Subject Alternative)'
|
||||
```
|
||||
|
||||
**Check for:**
|
||||
- TLS 1.2+ only (no TLS 1.0/1.1, no SSLv3)
|
||||
- Strong ciphers (AES-GCM, ChaCha20) — not RC4, 3DES, or MD5
|
||||
- Let's Encrypt or similar trusted CA (not self-signed)
|
||||
- HSTS header present (`Strict-Transport-Security`)
|
||||
- Certificate covers the domain (Subject Alternative Name)
|
||||
|
||||
### 3. HTTP Security Headers
|
||||
```bash
|
||||
curl -sI https://<domain>/ | grep -i -E '(strict-transport-security|x-content-type-options|x-frame-options|content-security-policy|referrer-policy|permissions-policy)'
|
||||
```
|
||||
|
||||
**Check for:**
|
||||
- `Strict-Transport-Security` — HSTS, ideally `max-age>=31536000`
|
||||
- `X-Content-Type-Options: nosniff` — prevents MIME sniffing
|
||||
- `X-Frame-Options: DENY` or `SAMEORIGIN` — clickjacking protection
|
||||
- `Content-Security-Policy` — XSS mitigation (can be tricky with SPAs/WebSockets)
|
||||
- `Referrer-Policy` — controls referrer leakage
|
||||
- Missing headers are a hardening opportunity, not always critical for internal services
|
||||
|
||||
### 4. Nginx / Reverse Proxy Config
|
||||
```bash
|
||||
# Inspect the nginx site config
|
||||
cat /etc/nginx/sites-available/<service>
|
||||
|
||||
# Check for server_name — does it match the subdomain?
|
||||
# Check for auth_basic — any nginx-level auth?
|
||||
grep -rn "auth_basic" /etc/nginx/ 2>/dev/null
|
||||
|
||||
# Check for rate limiting
|
||||
grep -rn "limit_req" /etc/nginx/ 2>/dev/null
|
||||
|
||||
# Check for proxy_hide_header — which headers are stripped?
|
||||
grep "proxy_hide_header" /etc/nginx/sites-available/<service>
|
||||
```
|
||||
|
||||
**Check for:**
|
||||
- `server_name` includes ALL valid domains (DuckDNS + custom domain)
|
||||
- `auth_basic` for defense-in-depth (recommended for single-user services)
|
||||
- `limit_req` to prevent brute force
|
||||
- `proxy_hide_header` — strips unwanted backend headers (critical for KasmVNC COEP/COOP)
|
||||
- `proxy_ssl_verify off` — acceptable for localhost backends, note it's not verifying
|
||||
|
||||
### 5. Docker Container Security
|
||||
```bash
|
||||
# List containers with status
|
||||
docker ps --format "table {{.Names}}\t{{.Status}}"
|
||||
|
||||
# Deep inspect
|
||||
docker inspect <container> --format '{{.HostConfig.NetworkMode}}' # network mode
|
||||
docker inspect <container> --format '{{.HostConfig.CapDrop}}' # dropped capabilities
|
||||
docker inspect <container> --format '{{.HostConfig.CapAdd}}' # added capabilities
|
||||
docker inspect <container> --format '{{.HostConfig.SecurityOpt}}' # security opts
|
||||
docker inspect <container> --format '{{.Config.User}}' # running user
|
||||
```
|
||||
|
||||
**Check for:**
|
||||
- **Network mode**: `bridge` (isolated, preferred) vs `host` (full host network access — riskier)
|
||||
- **Capabilities**: ideally `--cap-drop=ALL` with explicit `--cap-add` for what's needed
|
||||
- **Running user**: non-root inside container (e.g., `kasm-user` is good)
|
||||
- **Security opts**: `no-new-privileges:true` is a hardening bonus
|
||||
- **Restart policy**: `unless-stopped` (good) vs `always` (also fine)
|
||||
|
||||
### 6. Container Internals
|
||||
```bash
|
||||
# Inside the container
|
||||
docker exec <container> whoami
|
||||
docker exec <container> cat /etc/os-release 2>/dev/null
|
||||
docker exec <container> cat /etc/debian_version 2>/dev/null
|
||||
docker exec <container> uname -r 2>/dev/null
|
||||
|
||||
# Application version
|
||||
docker exec <container> <app> --version 2>/dev/null
|
||||
```
|
||||
|
||||
**Check for:**
|
||||
- Base OS age (Ubuntu 20.04 is past standard EOL in April 2025)
|
||||
- Application version recency
|
||||
- Image build date (`docker inspect <image> --format '{{.Created}}'`)
|
||||
- Whether the image is regularly updated
|
||||
|
||||
### 7. Firewall and Host Defense
|
||||
```bash
|
||||
# UFW rules
|
||||
sudo ufw status verbose
|
||||
|
||||
# iptables rules for the service port
|
||||
sudo iptables -L INPUT -n --line-numbers | grep <port>
|
||||
|
||||
# Security monitoring
|
||||
dpkg -l fail2ban crowdsec rkhunter chkrootkit 2>/dev/null | grep '^ii'
|
||||
```
|
||||
|
||||
**Check for:**
|
||||
- Default inbound policy: DROP (not ACCEPT)
|
||||
- Service port allowed only from needed ranges (LAN, Tailscale VPN)
|
||||
- Public internet blocked at the firewall level
|
||||
- fail2ban or similar running for brute-force protection
|
||||
|
||||
### 8. VPS Reverse Proxy Architecture
|
||||
```bash
|
||||
# Trace the architecture
|
||||
dig +short <subdomain>.<domain> # → VPS IP
|
||||
# VPS proxies to home server via Tailscale
|
||||
ssh ubuntu@<vps-ip> 'grep proxy_pass /etc/nginx/sites-enabled/<service>'
|
||||
```
|
||||
|
||||
**Check for:**
|
||||
- DNS points to VPS, not home server IP
|
||||
- VPS nginx proxies via Tailscale IP (100.x.x.x), not public IP
|
||||
- UFW on home server allows port only from LAN + Tailscale ranges
|
||||
- Home router has NO port forward for this service (zero open ports)
|
||||
|
||||
### 9. Risk Summary Matrix
|
||||
|
||||
| Layer | Weakness | Severity | Mitigation |
|
||||
|-------|----------|----------|------------|
|
||||
| Network | Public exposure via DNS | Low if behind VPS | UFW, VPS proxy |
|
||||
| Transport | Missing HSTS | Low | `add_header` in nginx |
|
||||
| Auth | Single auth layer | Medium | Add nginx `auth_basic` |
|
||||
| Container | Host networking | High | Switch to bridge + iptables |
|
||||
| Container | Default capabilities | Medium | `--cap-drop=ALL` + explicit adds |
|
||||
| Container | Old base OS | Medium | Regular image pulls |
|
||||
| App | No rate limiting | Low | `limit_req` in nginx |
|
||||
|
||||
## Interpretation Guide
|
||||
|
||||
- **Low severity** = hardening opportunity, not urgent. Benefits of fixing may not justify risk of breaking the service.
|
||||
- **Medium severity** = address when convenient. Adds defense-in-depth.
|
||||
- **High severity** = actively exploitable if attacker gains any toehold. Prioritize.
|
||||
|
||||
The firewall + VPS architecture is almost always the strongest defense. Even a poorly-configured container behind a proper firewall is hard to reach from the internet. Focus hardening effort where it reduces blast radius once an attacker is inside the network.
|
||||
Reference in New Issue
Block a user