There’s a certain kind of vulnerability that doesn’t look impressive at first glance.
It’s not flashy. It’s not remote. It doesn’t scream “critical RCE.”
In fact, if you skim it quickly, you might even shrug and move on.
This was one of those.
While looking at Pi-hole, I came across a behaviour that didn’t quite sit right. Nothing immediately explosive, just a small design decision that, when you follow it through properly, ends up giving you something far more interesting: a clean path from a low-privileged service account to root.
“It’s fine, it’s nologin”
Pi-hole runs with a dedicated pihole user. That’s good practice. It’s also configured with nologin, which at a glance sounds reassuring.
But here’s the thing people sometimes forget: nologin only stops interactive logins. It doesn’t stop code execution as that user.
If a process running as pihole gets compromised (and let’s be honest, that’s not a wild scenario), you now have an attacker executing code in that context.
So, the real question becomes: What can the pihole user influence that root later trusts?
That’s where things get interesting.
The core issue
At the centre of this is a file:
/etc/pihole/versions
On affected systems, this file is writable by the pihole user.
At the same time, multiple Pi-hole scripts running as root will source that file. Not parse it safely. Not read structured data. Just straight up:
. /etc/pihole/versions
Which means whatever ends up in that file gets executed as root.
That’s not theoretical. That’s exactly what happens.
Turning that into root
Once you spot that pattern, the path forward is straightforward.
If you can execute code as pihole, you append something malicious into /etc/pihole/versions.
Something as simple as: /usr/bin/id > /tmp/pihole-root-proof
Then you wait, or trigger one of the code paths that runs as root.
Pi-hole obliges, sources the file, and executes your payload as root.
You now have confirmation: uid=0(root)
That’s your escalation. Clean, reliable, and built entirely on expected behaviour.

Why this matters (even if it’s “Post-Compromise”)
This is the part where people tend to disagree.
You’ll often hear something like: “Well, you already need code execution as pihole, so how bad is it really?”
Here’s the reality: this is exactly the kind of step attackers rely on.
Initial access is messy. Privilege escalation is where things become stable, persistent, and dangerous.
Going from a constrained service account to root means:
-
- Full system control
- Ability to tamper with logs
- Persistence mechanisms
- Pivoting deeper into a network
Even though the vulnerability is local and requires prior access, it still results in high impact across confidentiality, integrity, and availability.
That’s not hypothetical risk. That’s complete compromise.
The fix (and what it tells you)
This issue was addressed in version 6.4.1, which removes the unsafe behaviour and hardens how that file is handled.
And this is the key takeaway:
If a fix removes “source-ing” of a writable file, that wasn’t just a minor issue. It was a design flaw.
The real lesson here isn’t about Pi-hole specifically. It’s broader:
-
- Never source files that a lower-privileged user can modify
- Treat configuration as untrusted input
- “It’s just a helper file” is how these bugs slip through
The bigger pattern
You see this class of issue more often than people think.
It usually looks like:
-
- A low-privileged service account
- A writable config or state file
- A root process that trusts it too much
Individually, each part seems reasonable. Combined, they give you privilege escalation.
And that’s the dangerous bit: these aren’t obvious bugs. They’re emergent behaviour from design decisions.

Final thoughts
This wasn’t a loud vulnerability. It didn’t jump out immediately.
But those are often the ones worth paying attention to.
Because in real environments, attackers don’t need perfect conditions. They need chains. And this is a solid link in that chain.
If you’re running Pi-hole, update.
If you’re building software, take a hard look at where privilege boundaries blur, especially where root ends up trusting something it shouldn’t.
References