ADVANCED TECHNIQUE BRIEF
PHP filter chains as byte transducers
Reason about composable encoding transforms and prove suffix preservation before assembling a chain.
Also known as: PHP filter chains · iconv LFI to RCE
A PHP stream filter transforms bytes before a consumer sees them. Prefix-generating encodings and permissive normalization can therefore compose into byte generation, but only when each step preserves the suffix already assembled.
Research anchor and scope
Rémi Matasse’s filter-chain research explains prefix introduction, base64 cleanup, padding differences, and unstable conversions. Its separate Laravel inclusion gadget uses v9.34.0. The byte-transform mechanism needs PHP wrappers and iconv; framework presence alone does not expose unsafe deserialization or inclusion.
A checked harmless byte transformation
This workspace’s PHP 8.5.10 has an installed iconv extension that is disabled by default; it was enabled only for the individual verification process. Converting the ASCII string QkM from UTF-8 to CSISO2022KR produced:
input: 51 6b 4d QkM
encoded: 1b 24 29 43 51 6b 4d ESC $ ) C Q k M
base64 decode: 09 09 0c
base64 encode: 43 51 6b 4d CQkM
The ISO-2022-KR designation introduces a fixed four-byte prefix. The first three prefix bytes are outside the base64 alphabet and the stream decoder ignores them; C is inside that alphabet and survives. Re-encoding the decoded bytes gives C || QkM, preserving the original suffix. This example generates harmless data, not executable source, and is not a complete generated chain.
Choosing a three-symbol suffix makes the normalized result a complete four-symbol quantum. Arbitrary suffix lengths can leave incomplete quanta, so the example does not prove a universal prepend gadget. Count alphabet symbols and verify end-of-stream output for every chain stage.
Preserve an invariant, not just the first byte
Model a candidate transform as T and the normalization as clean. The desired property is clean(T(s)) = c || s over the permitted suffix alphabet. A conversion that produces c but alters any earlier symbol breaks subsequent composition. Encoding names alone do not imply preservation: codecs may reinterpret the suffix rather than merely add a designation or byte-order mark.
Use a small alphabet-covering suffix corpus, including mixed case, digits, +, and /. Compare exact bytes and lengths after every stage. A successful one-character probe does not establish behavior over arbitrary base64 strings or across stream-buffer boundaries. Construct longer results only from individually verified transformations.
Padding and runtime constraints
The stream convert.base64-decode filter and the language function base64_decode() differ on malformed = placement. Do not substitute the function for the actual stream consumer during verification. UTF-8 to UTF-7 converted a literal = to +AD0- in the local test. This removes the literal padding character but introduces new alphabet symbols; it does not simply delete padding. Any cleanup strategy must account for those symbols and preserve the intended suffix through another normalization round.
Record PHP version, iconv implementation, available conversion aliases, warning handling, input-length limits, and empty-stream behavior. The illustrative designation was verified locally; another platform can have different codec availability. Base64 decode/encode acts on bytes and alphabet quanta, so floating assumptions about textual equivalence are unsafe.
Connect the primitive to its consumer
A read sink returns transformed data; an include or require sink may interpret it as source; a hash or image parser consumes it differently. Wrapper-string control and a reachable sink are separate prerequisites. Report byte generation separately from inclusion, object gadgets, and execution. No full chain or historical framework gadget should be treated as universal solely because this bounded prefix example works.