Stream Filter Injection via php:// wrapper
debt(d5/e5/b5/t9)
Closest to 'specialist tool catches' (d5), semgrep rules and phpstan taint analysis can flag user input flowing into include/require, but it requires running these specialist tools rather than default linting.
Closest to 'touches multiple files / significant refactor in one component' (e5), quick_fix requires whitelisting allowed values at every include site, plus php.ini configuration changes, plus auditing all path-accepting functions — multi-file remediation.
Closest to 'persistent productivity tax' (b5), the rule that user input must never reach include/require with proper whitelisting applies across all web-facing PHP code and shapes how dynamic page routing must be designed.
Closest to 'catastrophic trap' (t9), misconception explicitly states devs believe php://filter is read-only and harmless, when filter chains (e.g. base64 + iconv) can synthesize executable PHP from arbitrary files — the 'obvious' mental model is completely wrong.
TL;DR
Explanation
PHP's stream wrappers are accessed via URLs: php://filter/convert.base64-encode/resource=index.php reads and base64-encodes a file. When file functions (include, require, file_get_contents) accept user input, attackers can use php://filter to read source files, chain conversion filters (zlib.deflate|convert.base64-encode) to extract data, or use php://input with allow_url_include to execute POST body as PHP. The filter chain technique (CVE-2022-26134 and similar) uses chained filters to create arbitrary PHP code from non-PHP files. Defence: never pass user input to any file function.
Common Misconception
Why It Matters
Common Mistakes
- Allowing user-supplied paths in include/require.
- Not disabling allow_url_include and allow_url_fopen in production.
- Not blocking php://, file://, data:// in any path-accepting function.
Code Examples
// LFI — allows php://filter injection:
$page = $_GET['page'];
include $page; // php://filter/convert.base64-encode/resource=config.php
// Whitelist approach:
$allowed = ['home', 'about', 'contact'];
$page = $_GET['page'] ?? 'home';
if (!in_array($page, $allowed, true)) {
$page = 'home';
}
include __DIR__ . '/pages/' . $page . '.php';
// php.ini:
// allow_url_include = Off
// allow_url_fopen = Off