ADVANCED TECHNIQUE BRIEF
Blind file reads through filter-induced error oracles
Turn a nonprinting file read into a measured predicate while accounting for memory limits and filter errors.
Also known as: dechunk error oracle · blind PHP file read
A nonprinting file read can disclose information if file bytes control a visible failure. The useful primitive is a reproducible predicate, not merely the ability to make a request return an error.
Research anchor and scope
Rémi Matasse’s research connects PHP dechunk behavior, iconv expansion, and byte repositioning to a blind read in a PHP 8.1 Apache environment. This is a disclosure primitive, separate from source inclusion. It requires attacker control over a filter-bearing resource and an observable error distinction.
Establish the actual dechunk predicate
For short input with no valid chunk-size line and no CRLF, a leading hexadecimal character makes dechunk attempt chunk parsing and discard the incomplete data. A leading nonhexadecimal character can pass the bytes through. This restricted behavior is useful; it is not a rule that every hex-leading input becomes empty.
Harmless PHP stream tests on this workspace’s PHP 8.5.10 yielded:
input hex input text dechunk output hex
42 7a Bz empty
47 7a Gz 47 7a
31 7a 1z empty
As a counterexample, the valid chunked input 1\r\nZ\r\n0\r\n\r\n returns Z. Keep newline structure controlled. Base64 normalization offers a bounded ASCII alphabet without CRLF, but the resulting predicate identifies membership in [0-9A-Fa-f], not a unique character.
Amplify empty versus nonempty safely
With iconv enabled, convert.iconv.UTF8.UCS-4LE represents ASCII characters and NUL bytes in four bytes each. Reinterpreting the resulting valid UTF-8 bytes through the same conversion repeatedly produces lengths approximately B*4^k. A bounded local check on Gz gave lengths 2, 8, 32, 128; no memory-exhaustion test was executed.
Put the content-sensitive filter before expansion. Empty output stays empty; surviving bytes can exceed the PHP request’s memory_limit. For a hypothetical 64 MiB budget and two surviving bytes, 13 idealized rounds produce 128 MiB, whereas 12 produce 32 MiB. Real intermediate allocations and ordinary request overhead can trigger failure earlier. Measure the deployment’s limit and error handling rather than copying a fixed round count.
Distinguish status, response length, a stable fatal-error marker, or connection closure. Suppressed warnings and proxy error rewriting can erase the channel. Generic server errors, unrelated memory pressure, caching, or worker restarts can mimic it. Calibrate both outcomes using known harmless files.
A bounded repositioning step
For ASCII text of even byte length, convert.iconv.UTF16LE.UTF16BE exchanges adjacent byte pairs. The independently checked bytes for G7AZ are 47 37 41 5a; conversion yields 37 47 5a 41, or 7GZA. Applying dechunk afterward now measures the former second byte, 7, rather than the original first byte, G. This reaches two positions, not arbitrary full-file extraction. Odd lengths or invalid surrogate sequences require separate treatment; base64 text avoids surrogate code units.
To recover a character, combine transformations whose predicate signatures separate every candidate and repeat uncertain results. To reach later positions, independently verify an additional rotation/removal gadget; a first-byte oracle or pair swap alone is insufficient. Track available file permissions, wrapper-string limits, and filter availability. Successful read disclosure supplies neither arbitrary write nor execution without another sink and primitive.