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.
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.
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 \
run
The malicious server...
4.34.0Exploitability
AV:NAC:LPR:NUI:RScope
S:UImpact
C:HI:HA:H8.8/CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H