fix: auto-renew TLS certificates without restart#1337
fix: auto-renew TLS certificates without restart#1337dogancanbakir wants to merge 2 commits intodevfrom
Conversation
Neo Security AuditHigh: 1 · Medium: 2 Highlights
High (1)
Medium (2)
Security ImpactTOCTOU vulnerability in certificate reload mechanism ( Missing TLS MinVersion allows downgrade to TLS 1.2 ( Missing TLS MinVersion in ACME configuration ( Attack ExamplesTOCTOU vulnerability in certificate reload mechanism ( Suggested FixesTOCTOU vulnerability in certificate reload mechanism ( Missing TLS MinVersion allows downgrade to TLS 1.2 ( Missing TLS MinVersion in ACME configuration ( Hardening Notes
Comment |
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
cmd/interactsh-server/main.go
Outdated
| domainCerts, certFiles, cmCfg, acmeErr = acme.HandleWildcardCertificates(fmt.Sprintf("*.%s", trimmedDomain), hostmaster, acmeStore, cliOptions.Debug, cliOptions.Resolvers) | ||
| if acmeErr != nil { |
There was a problem hiding this comment.
Each loop iteration overwrites cmCfg, domainCerts, and certFiles. If multiple domains are configured, only the last domain's certificates and config survive.
There was a problem hiding this comment.
either we can handle the multi-domain case properly, or support only single domain
Split HandleWildcardCertificates into NewCertmagicConfig + per-domain cert obtaining so a single certmagic.Config is shared across all domains. The ACME loop now appends to domainCerts/certFiles instead of replacing them on each iteration, fixing multi-domain certificate handling.
| r.mu.Lock() | ||
| defer r.mu.Unlock() | ||
|
|
||
| mt, err := latestModTime(r.certPath, r.keyPath) |
There was a problem hiding this comment.
🟠 TOCTOU vulnerability in certificate reload mechanism (CWE-367) — The tryReload() function has a time-of-check-time-of-use (TOCTOU) race condition. It checks file modification time at line 81, then loads the certificate at line 90. An attacker with filesystem access could replace the certificate files with symlinks between these operations, causing the server to load certificates from an attacker-controlled location.
Attack Example
1. Wait for tryReload() to call os.Stat() at line 81
2. Replace cert.pem with symlink to /tmp/attacker-cert.pem between lines 81-90
3. Server loads attacker's certificate at line 90 via tls.LoadX509KeyPair()
Suggested Fix
Use file descriptors instead of paths: open files with O_NOFOLLOW flag, fstat() the fd, then read from the fd. This prevents symlink following and eliminates the TOCTOU window. Example: fd := unix.Open(path, unix.O_RDONLY|unix.O_NOFOLLOW, 0); defer unix.Close(fd); fstat(fd); read certificate from fd
| } else { | ||
| tlsConfig = acmeManagerTLS | ||
| go reloader.Start(context.Background()) | ||
| tlsConfig = &tls.Config{ |
There was a problem hiding this comment.
🟡 Missing TLS MinVersion allows downgrade to TLS 1.2 (CWE-327) — TLS configuration for custom certificates does not specify MinVersion, allowing clients to negotiate TLS 1.2. While Go 1.22+ defaults to TLS 1.2 minimum, explicitly setting TLS 1.3 provides defense-in-depth against future changes and protocol downgrade attacks.
Suggested Fix
Add MinVersion: tls.VersionTLS13 to the tls.Config at line 302:
tlsConfig = &tls.Config{
GetCertificate: reloader.GetCertificate,
NextProtos: []string{"h2", "http/1.1"},
MinVersion: tls.VersionTLS13,
}
| certFiles = append(certFiles, files...) | ||
| } | ||
| } | ||
| tlsConfig = &tls.Config{ |
There was a problem hiding this comment.
🟡 Missing TLS MinVersion in ACME configuration (CWE-327) — TLS configuration for ACME certificates does not specify MinVersion, allowing clients to negotiate TLS 1.2. While Go 1.22+ defaults to TLS 1.2 minimum, explicitly setting TLS 1.3 provides defense-in-depth.
Suggested Fix
Add MinVersion: tls.VersionTLS13 to the tls.Config at line 323:
tlsConfig = &tls.Config{
GetCertificate: cfg.GetCertificate,
NextProtos: []string{"h2", "http/1.1"},
MinVersion: tls.VersionTLS13,
}
Summary
GetCertificatecallback instead of statictls.Config.Certificatesso ACME-renewed certs are served dynamicallyCertReloaderfor custom cert files (--cert/--privkey) that polls for file changes and hot-swaps the certificateCERT_CHECK_INTERVALenv variable (default1h)Fixes #1332