160 lines
6.3 KiB
Markdown
160 lines
6.3 KiB
Markdown
# 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.
|