hunting-server-side-and-edge-side-includes · git:20260903.6dac719 · 2026-09-03 · sha256 802c1d988ca12f63
hunting-server-side-and-edge-side-includes git:20260903.6dac719A
Immutable. This exact content is served forever at /api/v1/blob/802c1d988ca12f63.
--- name: hunting-server-side-and-edge-side-includes description: >- Hunt server-side include and edge-side include injection, where untrusted input is reflected into a document that a processor later interprets for include and exec directives. When an origin server has server-side includes enabled, a reflected directive can read a server file or run a command. When a cache, content-delivery network, or proxy in front of the origin parses edge-side include tags, a reflected tag can fetch an internal URL for server-side request forgery, poison a shared cache, or copy a victim's cookie into a page. The edge case is easy to miss because the origin developer never sees the processor that evaluates the tag. Use when reflected input reaches a document that an include-capable server or edge tier processes. The reflected input is the source, the include or exec directive the processor evaluates is the sink, and the resulting file read, command, request forgery, or cache poisoning is the bug. license: MIT --- # Hunting server-side and edge-side includes: when a directive is reflected Two different tiers can turn reflected text into an executed directive. On the origin, a server with server-side includes enabled scans documents for directives and will read a file or run a command when it finds one, so untrusted input reflected into such a document and interpreted as a directive becomes file disclosure or command execution. At the edge, a cache, content-delivery network, or reverse proxy may parse edge-side include tags in responses and act on them, fetching a URL, splicing a fragment, or reading a request variable, so a reflected tag becomes server-side request forgery into the internal network, a poisoned shared cache, or a page that copies the victim's cookie back to the attacker. The edge variant is insidious: the application developer never wrote include handling and cannot see it in the origin code, because a separate tier performs it. You find both by locating reflection into processed documents and determining which processor sits in the path. ## When to use - Untrusted input is reflected into a page, template, or fragment served by an include-capable server. - A cache, content-delivery network, or reverse proxy in front of the origin may parse edge-side includes. - Response surfaces (headers, error pages, cached fragments) echo input into documents processed downstream. ## Scope check Test include injection only against systems you own or are authorized to assess, on non-production infrastructure, because a confirming directive can read server files, reach internal services, or poison a cache shared by other users. Prefer benign, observable probes over destructive commands, and coordinate before touching a shared cache. If you can't name the authorization, stop. ## The loop 1. **Establish that an include processor is actually in the path first.** Determine whether the origin has server-side includes enabled for the served document type, or whether a downstream cache, delivery network, or proxy parses edge-side include tags. This is the false-positive killer: if no processor interprets includes, reflected directive text is inert markup and there is no bug. Name the processor and the tier that runs it before treating any reflection as a lead. 2. **Find the reflection point.** Locate where untrusted input is echoed into a document the processor will scan: page body, a template fragment, an error page, a header value copied into a cached response. Confirm the input reaches the document before the processor runs, not after. 3. **Check for neutralization before the processor.** Read whether the reflected value is entity-encoded, whether the include syntax's delimiters are stripped or escaped, and whether the document type is one the processor ignores. Encoding that turns the directive's brackets into inert entities before the processor sees them closes the class; determine whether it is applied on this path. 4. **Test the server-side directive impact.** Where the origin processes includes, probe with a benign directive that reflects an observable value (a document variable, a size) and, only within scope, assess whether file-include and command-exec directives are permitted. The reachable impact is exactly the directive set the server allows. 5. **Test the edge-side directive impact.** Where the edge parses tags, probe whether an include tag makes the edge fetch a URL you control or an internal address (request forgery), whether the result is stored in a shared cache (poisoning that affects other users), and whether request variables such as cookies can be read into the page (token theft). Confirm which behaviors the specific edge honors. 6. **Confirm and record.** Confirm server-side by reflecting a benign directive that returns an observable value, and edge-side by causing an interaction with a host you control or an observable cache change, on an isolated instance or an isolated cache key. Kill the lead if no include processor is in the path, if the reflected value is encoded so directive delimiters are inert before processing, or if the processor ignores the served document type. Record the tier, the processor, the reflection point, the directive, and the effect. Set `kill_reason` when killing. ## Where include injection leaks - **The edge processor is invisible to the origin.** The application code has no include handling, so a source-only review of the origin misses an edge-side vulnerability performed by a separate tier. - **Reflection into cached responses is shared.** A directive spliced into a response stored under a shared cache key affects every user who receives that cached entry. - **Headers and error pages are reflection points too.** Input echoed into a header the edge copies into the body, or into a verbose error page, reaches the processor just as a normal page does. - **Edge includes reach the internal network.** An include tag that fetches a URL runs from the edge or origin's vantage, making it a request-forgery primitive against internal services. - **Encoding must precede the processor.** Output encoding applied after the include tier has already run does not help; the neutralization has to happen before the directive is scanned. ## Worked example (a confirm and a kill) > **Confirm.** A delivery tier in front of the application parses edge-side include tags, and a search > page reflects the query into the response body without encoding the tag delimiters. A query containing an > include tag pointed at a host the tester controls causes the edge to fetch that host, and pointing it at > an internal metadata address returns internal data into the page; the response is stored under a shared > cache key. **Confirmed** edge-side include injection to server-side request forgery and cache poisoning, > `high`, remediation = disable edge-side include parsing on responses built from user input, or > entity-encode tag delimiters at the origin before the response reaches the edge. > > **Kill.** An error page reflects the requested path, but the origin has server-side includes disabled for > the served type and no cache or proxy in the path parses edge-side includes; the value is additionally > entity-encoded. A reflected directive renders as visible inert text. **Killed**, `kill_reason` = "no > include processor runs on this path at either tier and the delimiters are entity-encoded, so the reflected > directive is never interpreted." ## Rationalizations to reject - *"We do not use server-side includes."* -> The origin may not, but a cache or delivery network in front of it can parse edge-side includes; confirm the whole path, not just the application server. - *"The tag just shows up as text."* -> That means this document type or tier does not process it; verify the processor, because a different surface or content type may be parsed. - *"Output is encoded."* -> Only if the encoding runs before the include processor; encoding applied after the edge has already interpreted the tag is too late. - *"It is only reflected, not stored."* -> Reflection into a response cached under a shared key becomes stored for every user who reads that cache entry. - *"Internal URLs are not reachable."* -> An edge-side fetch runs from inside the perimeter; that is exactly the request-forgery vantage that reaches internal services. ## Executing this in practice You need the served document types and whether the origin has includes enabled, the presence and behavior of any cache, delivery network, or proxy that parses edge-side includes, every reflection point into processed documents, and the encoding applied before each processor. For each reflection, decide which tier interprets includes, whether delimiters survive to the processor, and what a directive could read, fetch, or poison. Reading the config and code shows the processor and encoding; a benign directive probe shows whether it is interpreted. ## Related - `testing-web-cache-attacks` - the shared-cache poisoning that an edge-side include can cause is that skill's core concern; the include tag is one way input reaches a cached response. - `hunting-reflected-and-stored-xss` - the same reflection points feed a browser sink there and an include processor here; check both interpreters for a given reflection. - `hunting-host-header-and-url-parsing-trust` - an edge-side fetch is a request-forgery primitive; that skill covers where such internal requests are trusted. - `adjudicating-taint-paths` - use it to connect the reflection to the processor across the origin and edge tiers. - [FINDING-SCHEMA.md](../../FINDING-SCHEMA.md) - source = the reflected untrusted input, sink = the include or exec directive the processor evaluates, evidence = a benign directive returning an observable value or an out-of-band interaction on an isolated instance.