Summary
Stored XSS vulnerability exists in Pi-hole when a user downloads an attacker's Adlist containing invalid domains. These domains are later displayed on the page when the update gravity function is called and the XSS is triggered.
PoC
When you enter an Adlist url, the application stores it to call it later when the update gravity function is then called.

If the list contains valid domains, then they are added, otherwise after backend processing of the list by the gravity.sh file, they are discarded. If a XSS payload is inserted, like: <script>alert(5)</script>, as shown in the figure:

When the update gravity function is called, the file previously entered in Adlist is called by the application and after being reprocessed on the backend side if it does not contain valid domains, some of them (a reduced list), are shown on the screen and the XSS is triggered.

XSS triggered:

But why does it then become a stored XSS? Because if the attacker leaves the list, the XSS will always be triggered and so it remains permanently as a stored. Similarly, even if the attacker removes the list, the vulnerability will persist as the application will fetch a cached copy of the list present in /etc/pihole:

And when the function is called then the cached copy is used causing XSS again:

This happened in the gravity.sh file:

With XSS you can do a number of things but they often target cookies to attempt session attacks but the cookies are well defended with the various protection flags and there was no way they could be taken. Another limitation is that on the backend side the gravity.sh file processes what is in the list by deleting spaces, comments, carriage returns and more and this makes writing the payload even more difficult because it must not contain spaces otherwise only what is after the last space is taken into account. In fact, here we see how the gravity.sh file acts on the list:

and if i try to write the same payload with spaces, like: <script> alert(5) </script> this happens:

and in /etc/pihole this time I have only the </script> close tag!

``
Tips that go beyond vulnerability
Even if the payload does not accept spaces there can be loopholes such as using splits and join for text

or some payloads such as <img/src="http://127.0.0.1:8000/tests.js"/onerror=alert(4)> that can be written without spaces using the “/”. (I tried the bypass of this limitation and succeeded but as you can then see even the CSP comes into play if you try to call resources outside the application that could help with the problem of spaces in the payload.)

Impact
As you know, an XSS attack allows for several malicious actions, not only print an alert or load an image, such as session hijacking, stealing sensitive information, malicious interaction with the DOM to modify page content, keylogging, performing redirects to URLs controlled by the attacker, and much more. Therefore, it is important not to underestimate an XSS especially if it is stored.
Summary
Stored XSS vulnerability exists in Pi-hole when a user downloads an attacker's Adlist containing invalid domains. These domains are later displayed on the page when the update gravity function is called and the XSS is triggered.
PoC
When you enter an Adlist url, the application stores it to call it later when the update gravity function is then called.
If the list contains valid domains, then they are added, otherwise after backend processing of the list by the gravity.sh file, they are discarded. If a XSS payload is inserted, like:
<script>alert(5)</script>, as shown in the figure:When the update gravity function is called, the file previously entered in Adlist is called by the application and after being reprocessed on the backend side if it does not contain valid domains, some of them (a reduced list), are shown on the screen and the XSS is triggered.
XSS triggered:
But why does it then become a stored XSS? Because if the attacker leaves the list, the XSS will always be triggered and so it remains permanently as a stored. Similarly, even if the attacker removes the list, the vulnerability will persist as the application will fetch a cached copy of the list present in /etc/pihole:
And when the function is called then the cached copy is used causing XSS again:
This happened in the gravity.sh file:
With XSS you can do a number of things but they often target cookies to attempt session attacks but the cookies are well defended with the various protection flags and there was no way they could be taken. Another limitation is that on the backend side the gravity.sh file processes what is in the list by deleting spaces, comments, carriage returns and more and this makes writing the payload even more difficult because it must not contain spaces otherwise only what is after the last space is taken into account. In fact, here we see how the gravity.sh file acts on the list:
and if i try to write the same payload with spaces, like:
<script> alert(5) </script>this happens:and in /etc/pihole this time I have only the </script> close tag!
``
Tips that go beyond vulnerability
Even if the payload does not accept spaces there can be loopholes such as using splits and join for text
or some payloads such as
<img/src="http://127.0.0.1:8000/tests.js"/onerror=alert(4)>that can be written without spaces using the “/”. (I tried the bypass of this limitation and succeeded but as you can then see even the CSP comes into play if you try to call resources outside the application that could help with the problem of spaces in the payload.)Impact
As you know, an XSS attack allows for several malicious actions, not only print an alert or load an image, such as session hijacking, stealing sensitive information, malicious interaction with the DOM to modify page content, keylogging, performing redirects to URLs controlled by the attacker, and much more. Therefore, it is important not to underestimate an XSS especially if it is stored.