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

Reply via email to