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.
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.
// ✅ 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.
What makes SSTI different from XSS?