2

I'm experiencing a weird behavior regarding setuid() and the setuid bit.

It seems like the suid bit and setuid() do not work as expected. I am expecting for a binary with +s and owned by uid 1001 that calls setuid(1001) to be called from any uid and assume uid 1001 after the call. Yet, that only seems to work if either:

  1. +s is not set and the calling user is root
  2. +s is set and the binary belongs to root

I am expecting that I overlooked a detail, however I cannot find my error.

The end goal would be to have a binary that can be called from any user and assume a fixed uid. I do not want it to be owned by root, but by the user whose identity should be assumed (mainly because this is an exercise on stack smashing and that would allow a priv esc).

I created a minimal example to pin down my problems, and here it is:

Consider test.c:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
int main() {
        int t = setuid(1001);
        if (t < 0) {
                perror("Error with setuid() - errno " + errno);
        }
        else {
                printf("did work fine, look who I am:.\n");
                system("/bin/bash -c whoami");
        }
}

Also, passwd looks like this in the relevant parts:

test1:x:1000:1000::/home/test1:/bin/sh
test2:x:1001:1001::/home/test2:/bin/sh

Now, consider this output:

root@kali:/tmp/test# ls -la
total 12
drwxr-xr-x  2 root root 4096 Oct 24 09:53 .
drwxrwxrwt 18 root root 4096 Oct 24 09:52 ..
-rw-r--r--  1 root root  304 Oct 24 09:51 test.c
root@kali:/tmp/test# gcc test.c -o test
root@kali:/tmp/test# ./test
did work fine, look who I am:.
test2
root@kali:/tmp/test# chown test2:test2 test
root@kali:/tmp/test# ./test
did work fine, look who I am:.
test2
root@kali:/tmp/test# chmod +s test
root@kali:/tmp/test# ./test
did work fine, look who I am:.
root
root@kali:/tmp/test# su test1
$ ./test
did work fine, look who I am:.
test1
$ 

As you can see, there's no error showing, yet the desired uid is not assumed correctly. To add insult to injury, consider this:

root@kali:/tmp/test# chown root:root test
root@kali:/tmp/test# chmod +s test
root@kali:/tmp/test# ./test
did work fine, look who I am:.
test2
root@kali:/tmp/test# su test1
$ ./test
did work fine, look who I am:.
test2

So I guess my question is: what am I doing wrong? Why does setreuid() work and setuid() doesn't?

Other things I tried: Using execve(), reproducing under ubuntu 18.04, using /bin/sh instead of /bin/bash.

  • There are a lot of shells in there, some of which try and be helpful and drop the effective uid if they are not invoked with '-p. Replace the system("/bin/bash -c whoami";) with 'execl("/usr/bin/whoami","whoami",NULL);` and see if it helps. Typically you want to use setreuid rather than setuid. – icarus Oct 24 '19 at 9:01
  • The documentation says the real user id cannot be set for unprivileged users. How comes that works? – Tobi Nary Oct 24 '19 at 9:26
1

The definition for setuid() is a little strange, as it does different things depending on whether or not the application is setuid root. (For good reasons, but not necessarily obvious reasons at the first glance.)

Fundamentally, what you need is to give up your original user identift before calling system(), since some shells do their best to refuse to honour setuid. Here is a modified version of your code; run it with and without the commented line to see the difference.

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>

int main() {
    int t;

    printf("before, geteuid() returned %d\n", geteuid());
    printf("before, getuid() returned %d\n", getuid());

    t = setuid(geteuid());
    if (t < 0) {
        perror("Error with setuid() - errno " + errno);
        exit(1);
    }

    printf("after, geteuid() returned %d\n", geteuid());
    printf("after, getuid() returned %d\n", getuid());

    // setreuid(geteuid(), geteuid());

    printf("finally, geteuid() returned %d\n", geteuid());
    printf("finally, getuid() returned %d\n", getuid());

    printf("did work fine, look who I am:\n");
    system("/bin/bash -c whoami");
}
  • Thank you. If I understand that correctly, the bash is not honoring the effective uid, so the real uid has to be set accordingly. Can you elaborate on why that even works? According to the documentation, unprivileged users cannot set the real uid. – Tobi Nary Oct 24 '19 at 10:14
  • Documentation for setreuid() doesn't say that. It does say "Unprivileged processes may only set the effective user ID to the real user ID, the effective user ID, or the saved set-user-ID." and "Unprivileged users may only set the real user ID to the real user ID or the effective user ID." – roaima Oct 24 '19 at 10:17
  • Ah, i See. Thank you. – Tobi Nary Oct 24 '19 at 10:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.