retitle -1 CVE-2025-53391: Local privilege escalation via zuluPolkit, caused by Debian patchBug #1108288 [src:zulucrypt] Local privilege escalation via zuluPolkit, caused by Debian patch
Source: zulucrypt
X-Debbugs-Cc: marciosouza@debian.org, adrelanos@whonix.org, arraybolt3@ubuntu.com
Version: 6.2.0-1
Severity: critical
Tags: security
In the current zuluCrypt packaging, the zuluPolkit/CMakeLists.txt file
is patched to allow any user to use zuluPolkit, a shim provided as part
of zuluCrypt for running privileged operations. (Upstream's default
allows only users considered as "admins" by polkit to use this
utility.) However, due to the kinds of operations zuluPolkit is capable
of running, the ability to run zuluPolkit as root is equivalent to root access, and thus this patch opens an LPE vulnerability that allows any
user on the system to elevate to root with only their own password.
zuluPolkit works using a slightly convoluted combination of a UNIX
socket and standard input. The executable is intended to be launched as `/usr/bin/zuluPolkit /path/to/unix-socket`. zuluPolkit will prompt for
a "token" (this turns out to be a simple authentication cookie which is
later used when connecting to the socket), then it will delete the
specified file and create a UNIX socket there. (Coincidentally this
allows us to delete anything we want on the system and replace it with
a UNIX socket.) zuluPolkit then listens on the socket, waiting for
something to connect to it and send it a JSON payload.
The format of the JSON payload supported by zuluPolkit is as follows:
{
"path": "/path/to/file",
"data": "data for the command, if applicable",
"password": "disk decryption passphrase, if applicable",
"cookie": "authentication cookie",
"command": "one of (exit|Read|Write) or a whitelisted shell command"
}
I'm primarily interested in the "Read" and "Write" commands. These
commands allow dumping the contents of an arbitrary file, and replacing
the contents of an arbitrary file, respectively. This provides an easy
way to elevate to root - dump /etc/shadow, set the root account's
password to be empty, then overwrite /etc/shadow with the insecure
version. At that point you can `su` to root and have full control of
the system. (The ability to run certain shell commands may also be of interest, but zuluCrypt seems to limit this to only a few specific
commands, so this *may* not be a worry. There might be some way to
trick zuluCrypt into running non-whitelisted things, but I have not investigated this.)
To demonstrate the vuln, I installed a fresh copy of Debian 12 with the
LXDE desktop [1] into a virtual machine, setting both a user and a root password. I verified that my user account (`user`) was not able to
elevate to root with its password, by running `sudo -i` (this told me
that `user` was not in the sudoers file). Then I `su`'d to root,
providing the root password in the process, and installed zuluCrypt
with `apt install zulucrypt-gui`. I only really needed zuluPolkit, but
wanted to ensure that installing zuluCrypt itself would pull in the vulnerable code. This did indeed install the zuluPolkit executable, so
I then exited out of the root shell.
In order to connect to zuluPolkit and pass malicious JSON to it, I
created a Python script as follows:
dump-shadow.py:
#!/bin/python3 -su
import socket
import sys
mysock = socket.socket(family=socket.AF_UNIX)
mysock.connect("/zulu-polkit")
payload = b"""{
"cookie": "qwe",
"path": "/etc/shadow",
"data": "",
"password": "",
"command": "Read"
}"""
mysock.sendall(payload)
while True:
rslt = mysock.recv(4096)
if len(rslt) != 0:
rslt_str = rslt.decode(encoding="latin_1")
sys.stdout.write(rslt_str)
(The use of "latin_1" encoding is intentional - this is the encoding zuluPolkit uses to encode data it writes to a file. It's probably not strictly necessary here but is used for consistency. Yes, you do have
to terminate this using Ctrl+C, it's not pretty, but I wanted reliable
and fast more than pretty.)
Next, in one terminal, I ran `pkexec /usr/bin/zuluPolkit /zulu-polkit`. pkexec prompted me for my *user* password, which as illustrated earlier
is NOT accepted by sudo for escalating to root. It is accepted for
running zuluPolkit though. Once it was started, I typed in a "token" of `qwe`, and pressed Enter. Then in another terminal, I ran
`python3 ./dump-shadow.py`. This proceeded to spit out the following
(heavily truncated, and of course I was shown actual password hashes
where I have the word "REDACTED"):
{
"exitCode": 0,
"exitStatus": 0,
"finished": true,
"stdError": "root:REDACTED:20259:0:99999:7:::\n..."
"stdOut": "root:REDACTED:20259:0:99999:7:::\n..."
}
So this worked, I was indeed able to dump the contents of /etc/shadow
doing this. The next step then was to try to overwrite /etc/shadow with malicious contents. I took the shadow file contents provided by the dump-shadow.py script, and deleted the root password hash entirely to
allow passwordless root login. Then I copied and tweaked the
dump-shadow.py script slightly, changing the payload by putting the maliciously modified shadow file contents in the "data" key and
changing the "command" key to "Write". zuluPolkit was still running its server, so I just reused the socket it still had open. The new script
was named `write-shadow.py`.
With that done, I ran `python3 ./write-shadow.py`. This returned a
rather boring JSON blob:
{
"exitCode": 0,
"exitStatus": 0,
"finished": true,
"stdError": ""
"stdOut": ""
}
Then I ran dump-shadow.py again, and got:
{
"exitCode": 0,
"exitStatus": 0,
"finished": true,
"stdError": "root::20259:0:99999:7:::\n..."
"stdOut": "root::20259:0:99999:7:::\n..."
}
The attack appeared to have worked, so finally I ran `su`. As expected,
I was instantly given a root shell with no password prompt. I also
tried logging into a TTY as `root` - this also let me in instantly with
no password prompt.
Once in a root shell, I ran `cat /etc/shadow` to make sure the file
wasn't horribly mangled. The file looked normal to me, so it appears zuluPolkit does not mangle things when writing them.
This vulnerability is fully exploitable on a fresh install of Debian
12.11. I have not yet attempted to exploit it on Trixie or Sid, but
given that the patch introducing the vuln is still present in the Git
tree [2] and I used zuluPolkit's upstream code as a reference when
developing this PoC, I assume they are vulnerable as well. Since the
patch was introduced for zuluCrypt 5.5.0 (judging by the
`zulucrypt-5.5.0` directory name present in the patch), I would assume
Debian 11 (Bullseye) is also vulnerable, and that Debian 10 (Buster) is probably not vulnerable.
It would likely require extensive upstream reworking to make zuluPolkit
safe for arbitrary unprivileged users to run, if it's possible at all
(I don't know what zuluPolkit itself is actually used for in
zuluCrypt, but it looks like it's intended to help with opening and decrypting device files, which would make it impossible to "make
safe"). Due to this, I believe the attempt to "fix" the polkit
configuration in zuluCrypt was well-meant but misguided, and that the problematic patch should be removed without replacement. The fact that
only admin users can use zulucrypt-gui by default is a feature, not a
bug.
[1] I meant to select LXQt, but hit LXDE on accident instead :P
[2] https://salsa.debian.org/debian/zulucrypt/-/blob/debian/master/debian/patches/fix_zulupolkit_policy.patch?ref_type=heads
Sysop: | Keyop |
---|---|
Location: | Huddersfield, West Yorkshire, UK |
Users: | 546 |
Nodes: | 16 (2 / 14) |
Uptime: | 145:40:43 |
Calls: | 10,383 |
Calls today: | 8 |
Files: | 14,054 |
D/L today: |
2 files (1,861K bytes) |
Messages: | 6,417,685 |