Task 1 of 5

How a £10,000 Bug Bounty Was Won with a Template

In 2021, a security researcher found a critical vulnerability in GitLab — one of the world's largest code hosting platforms. The vulnerability was CVE-2021-22205, and it allowed unauthenticated attackers to execute arbitrary commands on the server by uploading a specially crafted image file. The root cause? A template engine processing untrusted data.

THE GITLAB ATTACK — WHAT HAPPENED
Researcher submits image
A malicious image file is uploaded to a GitLab instance via the API — no authentication required
Image metadata processed
GitLab uses ExifTool to process image metadata — ExifTool has a code execution bug triggered by template-like strings in certain DjVu files
Server executes payload
The template string in the image metadata is evaluated on the server — the attacker's code runs with the GitLab process permissions
Full server compromise
With RCE on the GitLab server, the attacker has access to all hosted repos, CI/CD pipelines, and potentially connected infrastructure

Thousands of self-hosted GitLab instances were left exposed on the internet. Mass exploitation began within days of the CVE being published. Researchers estimated over 50,000 vulnerable servers were reachable publicly.

What is Server-Side Template Injection?

Web applications use template engines to build dynamic HTML pages. A template engine combines a fixed template with variable data:

// Template (defined by developer):
<h1>Hello, {{ username }}!</h1>

// Data (from the database):
{ username: "Alice" }

// Output:
<h1>Hello, Alice!</h1>

This is safe — the template is controlled by the developer, and only the data comes from the user.

SSTI occurs when user input becomes part of the template itself — not just the data fed into it. When the template engine processes the input, it executes any template expressions inside it — giving the attacker a scripting environment running on the server.

THE VULNERABLE PATTERN
// ✅ SAFE — user data goes INTO a fixed template
const html = pug.render('h1 Hello #{name}', { name: req.body.name });

// ❌ VULNERABLE — user input IS the template
const html = pug.render(req.body.template);

One line of difference. Massive security consequence.

1

What makes SSTI different from XSS?

Answer all 1 question to continue