Summary
Security Researcher Julio Ángel Ferrari (aka T0X1CX) discovered that the Pi-hole web interface contains a stored HTML injection vulnerability (Stored HTML Injection) in the local DNS records configuration page (/admin/settings/dns). This vulnerability allows an authenticated administrator to inject arbitrary HTML code that is stored in the Pi-hole configuration and rendered every time the DNS records table is viewed.
Details
The file scripts/js/settings-dns-records.js contains the populateDataTable() function, which configures the DataTables used to display DNS records. Within this function, the rowCallback callback dynamically generates a delete button for each row. The vulnerable code is located at lines 100–110:
rowCallback(row, data) {
$(row).attr("data-id", data);
const button = `<button type="button"
class="btn btn-danger btn-xs"
id="delete${endpoint}${utils.hexEncode(data)}"
data-tag="${data}"
data-type="${endpoint}"
${setByEnv ? "disabled" : ""}>
<span class="far fa-trash-alt"></span>
</button>`;
$(`td:eq(${endpoint === "hosts" ? 2 : 3})`, row).html(button);
},
The issue lies on line 105:
The data variable contains the full DNS record value exactly as entered by the user and returned by the API. This value is inserted directly into the data-tag HTML attribute without any escaping or sanitization of special characters.
When an attacker supplies a value containing double quotes ("), they can prematurely “close” the data-tag attribute and inject additional HTML attributes into the element.
The JavaScript template string generates the button’s HTML by directly concatenating the value of data. Let’s analyze what happens with a malicious payload.
Value entered by the attacker:
test" style="background:green" x="
Concatenation in the template string:
`data-tag="${data}"`
// Becomes:
`data-tag="test" style="background:green" x=""`
Resulting HTML of the full button:
<button type="button"
class="btn btn-danger btn-xs"
id="deletecnameRecords..."
data-tag="test" style="background:green" x=",target.com"
data-type="cnameRecords">
<span class="far fa-trash-alt"></span>
</button>
As can be seen, the attribute style="background:green" has been successfully injected into the
element, modifying its visual appearance.
It is important to note that the code shows an inconsistency in how the data variable is handled:
Line 104: utils.hexEncode(data) is used for the button’s id attribute, which prevents injection in that context.
Line 105: No escaping is applied to the data-tag attribute, leaving it vulnerable.
This inconsistency suggests that the developer was aware of the need to sanitize the data for the id attribute, but failed to apply the same protection to data-tag.
Why existing protections do not work?
The DataTables configuration includes a general protection in columnDefs (lines 88–93):
columnDefs: [
{
targets: "_all",
render: $.fn.dataTable.render.text(),
},
],
The setting render: $.fn.dataTable.render.text() applied to targets: "_all" automatically escapes the content of data cells. However, this protection does not apply to the button generated in rowCallback because:
The button is manually generated using a template string.
It is inserted into the DOM using $().html(), which interprets the content as HTML.
DataTables’ render function only affects column data, not content generated programmatically in callbacks.
PoC
Log in to Pi-hole via the web interface, and navigate to Settings > Local DNS Records.
On the right-hand side, where it says List of local CNAME records, enter the following payload in the Domain field:
test" style="background:yellow" title="VULNERABLE" x="
Then, in the Target field, enter any value, for example: test.com
When you click the Add button, you will see that the delete button for the record appears in yellow the style we injected.
Impact
Exploitation of this vulnerability allows an authenticated attacker to inject arbitrary HTML attributes into the buttons of the DNS records table. Since Pi-hole implements a Content Security Policy (CSP) that blocks inline JavaScript, the impact is limited to pure HTML injection: UI spoofing by modifying the visual appearance of buttons (changing colors, sizes, or making dangerous buttons appear safe), manipulation of tooltips via the title attribute to display misleading messages on hover, alteration of accessibility by modifying aria-* attributes that affect users of screen readers, and partial defacement of the administration interface.
In environments where CSP is disabled or less restrictive, this vulnerability could escalate to stored Cross-Site Scripting (XSS), allowing arbitrary JavaScript execution, session cookie theft, and full control over the administrator’s account.
Summary
Security Researcher Julio Ángel Ferrari (aka T0X1CX) discovered that the Pi-hole web interface contains a stored HTML injection vulnerability (Stored HTML Injection) in the local DNS records configuration page (/admin/settings/dns). This vulnerability allows an authenticated administrator to inject arbitrary HTML code that is stored in the Pi-hole configuration and rendered every time the DNS records table is viewed.
Details
The file scripts/js/settings-dns-records.js contains the populateDataTable() function, which configures the DataTables used to display DNS records. Within this function, the rowCallback callback dynamically generates a delete button for each row. The vulnerable code is located at lines 100–110:
The issue lies on line 105:
The data variable contains the full DNS record value exactly as entered by the user and returned by the API. This value is inserted directly into the data-tag HTML attribute without any escaping or sanitization of special characters.
When an attacker supplies a value containing double quotes ("), they can prematurely “close” the data-tag attribute and inject additional HTML attributes into the element.
The JavaScript template string generates the button’s HTML by directly concatenating the value of data. Let’s analyze what happens with a malicious payload.
Value entered by the attacker:
Concatenation in the template string:
Resulting HTML of the full button:
As can be seen, the attribute style="background:green" has been successfully injected into the
element, modifying its visual appearance.It is important to note that the code shows an inconsistency in how the data variable is handled:
Line 104: utils.hexEncode(data) is used for the button’s id attribute, which prevents injection in that context.
Line 105: No escaping is applied to the data-tag attribute, leaving it vulnerable.
This inconsistency suggests that the developer was aware of the need to sanitize the data for the id attribute, but failed to apply the same protection to data-tag.
Why existing protections do not work?
The DataTables configuration includes a general protection in columnDefs (lines 88–93):
The setting render: $.fn.dataTable.render.text() applied to targets: "_all" automatically escapes the content of data cells. However, this protection does not apply to the button generated in rowCallback because:
The button is manually generated using a template string.
It is inserted into the DOM using $().html(), which interprets the content as HTML.
DataTables’ render function only affects column data, not content generated programmatically in callbacks.
PoC
Log in to Pi-hole via the web interface, and navigate to Settings > Local DNS Records.
On the right-hand side, where it says List of local CNAME records, enter the following payload in the Domain field:
Then, in the Target field, enter any value, for example: test.com
When you click the Add button, you will see that the delete button for the record appears in yellow the style we injected.
Impact
Exploitation of this vulnerability allows an authenticated attacker to inject arbitrary HTML attributes into the buttons of the DNS records table. Since Pi-hole implements a Content Security Policy (CSP) that blocks inline JavaScript, the impact is limited to pure HTML injection: UI spoofing by modifying the visual appearance of buttons (changing colors, sizes, or making dangerous buttons appear safe), manipulation of tooltips via the title attribute to display misleading messages on hover, alteration of accessibility by modifying aria-* attributes that affect users of screen readers, and partial defacement of the administration interface.
In environments where CSP is disabled or less restrictive, this vulnerability could escalate to stored Cross-Site Scripting (XSS), allowing arbitrary JavaScript execution, session cookie theft, and full control over the administrator’s account.