Skip to content
This allows arbitrary code

This allows arbitrary code

github.com • September 21, 2026

While fixing GHSA-2794-hrj8-5jg9 we reviewed the surrounding code and found that Pi-hole's configuration validators can be bypassed in four independent ways. Each one lets an authenticated administrator put a value into the running configuration that every other way of configuring FTL refuses.

The most serious of them defeats the fixes for a series of already published advisories: the checks that reject embedded newlines in dnsmasq-bound options are simply not run when the value arrives inside a Teleporter archive.

These are our own findings, not part of either externally reported advisory.

1. A Teleporter import runs no validators at all

POST /api/teleporter accepts an archive containing a complete pihole.toml . The import parses it with readFTLtoml() and installs the result with replace_config() . readFTLtoml() only parses - the validator each config item declares ( conf_item->c ) is never called. The API, the CLI and environment variables all run them, so the one input reachable over the network without any access to the host is the one that was never checked.

Several published advisories were fixed by adding exactly such a validator. dns.hostRecord , dns.cnameRecords , dns.upstreams , dhcp.hosts , dhcp.leaseTime and misc.dnsmasq_lines reject embedded newlines, because a newline lets one value carry a second, arbitrary directive into the generated dnsmasq configuration. Wrapping the value in an archive skips that check:

/etc/pihole/dnsmasq.conf then contains log-queries as a directive of its own. Substituting dhcp-script= for it yields code execution as the pihole user, which is the outcome those advisories describe. The generated configuration is tested before it is applied, so a payload only has to be a valid dnsmasq directive - which these are.

This means the fixes for GHSA-23w8-7333-p9fj , GHSA-fqv2-qhfh-ghcj , GHSA-28g5-gg88-wh5m , GHSA-wxhv-w77q-6qwp , GHSA-vfmq-jrx3-wv3c and GHSA-chj2-h854-5rwx are all bypassable in released versions.

Introduced in FTL 6.0 and present in every release up to the fix.

2. Per-item validation cannot see a change to another item

Validators receive one value and compare it against the configuration currently in effect. A single PATCH may change several items at once, and each is checked against the old value of the others, so a combination that no individual check rejects is installed:

Both values pass their own check and the result places a file Pi-hole writes inside the web server's document root.

Introduced in FTL 6.7, the release that added the first rule spanning two config items - before that there was no such rule to bypass - and present in every release up to the fix.

3. The document root can be moved on top of a file Pi-hole writes

GHSA-g7v8-8q8f-hprp was fixed by keeping files.log.webserver out of the document root. That check compares against the document root configured at the time, and nothing prevents moving the document root afterwards:

The fix for GHSA-g7v8-8q8f-hprp is therefore incomplete in released versions. Its reporter should be informed.

Introduced in FTL 6.7, the release that added the one-directional check, and present in every release up to the fix.

4. Only one written path was separated from the document root

files.log.webserver was the only path checked against the document root. files.log.ftl , files.log.dnsmasq , files.database , files.tmp_db , files.gravity and files.pcap had no such check, and files.gravity_tmp - the directory gravity writes downloaded list content into - had no path validation whatsoever ( validate_stub ). Any of them can be pointed into the document root directly, without needing 2 or 3 above.

Introduced in FTL 6.0 and present in every release up to the fix.

An authenticated administrator, or anyone at all when no admin password is set, can:

execute code as the pihole user by carrying a second dnsmasq directive into the generated configuration, which is what the newline checks exist to prevent,

read any file the pihole user can open, including the API password hash and the TLS private key in /etc/pihole , by making the web server serve them, and

place a file whose content follows from what clients send below the document root, where a name matching the Lua server page pattern makes the web server evaluate it - the chain described in GHSA-g7v8-8q8f-hprp .

Files outside the web are served without authentication once webserver.serve_all is enabled, so the resulting read does not require a session at all.

Every release from 6.0 onwards, with the two later starting points noted per finding above. No released version carries the fix. It is intended for 6.7.2, and the version fields on this advisory are set once that is actually released rather than now - naming a version that does not exist yet would be wrong in the one field tooling reads.

Every value in an imported configuration is held to the validator its config item declares, before the configuration is installed. This is what closes 1, and it is what the published advisories above depend on.

The resulting configuration is validated as a whole at both API write paths and in the import, which closes 2. Checking items individually against the state they are replacing cannot express a rule two of them.

The document root and the files Pi-hole writes are kept apart in both directions: a written path may not move into the document root, and the document root may not move onto a written path.

webserver.paths.webroot may not be / nor overlap /etc/pihole .

Imported dhcp.leases content has to parse as a lease database, in both the ZIP and the TAR.GZ importer. This is not one of the findings above: it implements the remediation recommended in GHSA-8j7w-m3cr-6q6x , which named the function and was never carried out. It ships here because the same import path is being fixed.

The fix is on branch fix/webserver-option-hardening in the private fork of this advisory.

Extracted Entities