Hi Team,
please see the vulnerability details below. Please let me know if there are any questions or remarks. Thank you for your work on the pi-hole project.
Summary
The formatInfo() function in queries.js renders data.upstream, data.client.ip, and data.ede.text into HTML without escaping when a user expands a query row in the Query Log, enabling stored HTML injection. JavaScript execution is blocked by the server's CSP (script-src 'self'). The same fields are properly escaped in the table view (rowCallback), confirming the omission was an oversight.
Details
In scripts/js/queries.js, the formatInfo() detail view interpolates API data into HTML strings without escaping:
1. data.upstream (lines 160–162, rendered at line 360):
// parseQueryStatus — builds fieldtext with raw upstream value
fieldtext =
(data.reply.type !== "UNKNOWN" ? "Forwarded, reply from " : "Forwarded to ") +
data.upstream; // <-- no escapeHtml
// formatInfo — inserts fieldtext into HTML
queryStatus.fieldtext + // <-- unescaped in HTML
2. data.client.ip (line 395):
const ipInfo =
data.client.name !== null && data.client.name.length > 0
? utils.escapeHtml(data.client.name) + " (" + data.client.ip + ")"
: data.client.ip; // name IS escaped, IP is NOT
3. data.ede.text (line 432):
edeInfo += ">" + data.ede.text + "</strong></div>"; // <-- no escapeHtml
Inconsistency proving oversight: The table view (rowCallback) properly escapes the same data — utils.escapeHtml(querystatus.fieldtext) at line 643, .text(data.client.ip) at line 685, and utils.escapeHtml(data.cname) at line 675.
Upstream constraints: In normal operation, upstream and client.ip are set internally by FTL from network data and cannot contain HTML. However, data.ede.text (Extended DNS Error) comes from upstream DNS server responses — a malicious upstream DNS server could return arbitrary EDE text. The teleporter does not import pihole-FTL.db on restore (confirmed by testing — the file is exported but silently skipped on import), so teleporter import is not a viable vector.
PoC
Prerequisites: Pi-hole Web v6.4.1; control of an upstream DNS server (for EDE text), or a browser proxy / Playwright to intercept API responses (to demonstrate the client-side vulnerability in isolation).
Note: FTL serves query data from memory, not directly from pihole-FTL.db. Injecting rows into the database does not surface them via the API without an FTL restart, and FTL rebuilds its own ID mappings on startup. The most practical PoC uses API response interception to simulate malicious data reaching the client, isolating the client-side escaping bug from the server-side data path.
Method: API response interception (Playwright)
await page.route('**/api/queries**', async (route) => {
const response = await route.fetch();
const json = await response.json();
if (json.queries && json.queries.length > 0) {
json.queries[0].upstream = '<b style="color:red;font-size:18px">INJECTED-UPSTREAM</b>';
json.queries[0].client = { ip: '<b style="color:red;font-size:18px">INJECTED-IP</b>', name: null };
json.queries[0].status = 'FORWARDED';
json.queries[0].reply = { type: 'IP', time: 25.0 };
}
await route.fulfill({ response, body: JSON.stringify(json) });
});
- Set up the route interception above, then navigate to Query Log (
/admin/queries).
- Click the first query row to expand the detail view.
Screenshot:

Impact
Defense-in-depth stored HTML injection. Authenticated Pi-hole administrators who expand query rows in the Query Log are affected. JavaScript execution is blocked by CSP (script-src 'self'), limiting impact to UI defacement and potential phishing via injected forms (the CSP is missing form-action). Exploitation requires local filesystem access (database manipulation) or control of an upstream DNS server (for EDE text — the most realistic remote vector). The inconsistency between the properly-escaped table view and the unescaped detail view confirms this was an oversight, not a design choice.
Hi Team,
please see the vulnerability details below. Please let me know if there are any questions or remarks. Thank you for your work on the pi-hole project.
Summary
The
formatInfo()function inqueries.jsrendersdata.upstream,data.client.ip, anddata.ede.textinto HTML without escaping when a user expands a query row in the Query Log, enabling stored HTML injection. JavaScript execution is blocked by the server's CSP (script-src 'self'). The same fields are properly escaped in the table view (rowCallback), confirming the omission was an oversight.Details
In
scripts/js/queries.js, theformatInfo()detail view interpolates API data into HTML strings without escaping:1.
data.upstream(lines 160–162, rendered at line 360):2.
data.client.ip(line 395):3.
data.ede.text(line 432):Inconsistency proving oversight: The table view (
rowCallback) properly escapes the same data —utils.escapeHtml(querystatus.fieldtext)at line 643,.text(data.client.ip)at line 685, andutils.escapeHtml(data.cname)at line 675.Upstream constraints: In normal operation,
upstreamandclient.ipare set internally by FTL from network data and cannot contain HTML. However,data.ede.text(Extended DNS Error) comes from upstream DNS server responses — a malicious upstream DNS server could return arbitrary EDE text. The teleporter does not importpihole-FTL.dbon restore (confirmed by testing — the file is exported but silently skipped on import), so teleporter import is not a viable vector.PoC
Prerequisites: Pi-hole Web v6.4.1; control of an upstream DNS server (for EDE text), or a browser proxy / Playwright to intercept API responses (to demonstrate the client-side vulnerability in isolation).
Note: FTL serves query data from memory, not directly from
pihole-FTL.db. Injecting rows into the database does not surface them via the API without an FTL restart, and FTL rebuilds its own ID mappings on startup. The most practical PoC uses API response interception to simulate malicious data reaching the client, isolating the client-side escaping bug from the server-side data path.Method: API response interception (Playwright)
/admin/queries).Screenshot:

Impact
Defense-in-depth stored HTML injection. Authenticated Pi-hole administrators who expand query rows in the Query Log are affected. JavaScript execution is blocked by CSP (
script-src 'self'), limiting impact to UI defacement and potential phishing via injected forms (the CSP is missingform-action). Exploitation requires local filesystem access (database manipulation) or control of an upstream DNS server (for EDE text — the most realistic remote vector). The inconsistency between the properly-escaped table view and the unescaped detail view confirms this was an oversight, not a design choice.