Consider the following classic problem case:
<?php
$filename = "/tmp/".$_GET['f'];
readfile($filename);
This code is vulnerable to a directory traversal attack, for example if the value of $_GET['f']
is ../etc/shadow
the contents of that file will be disclosed to the attacker.
There are well-known approaches to prevent this type of attack; I am not asking how to do that. The question is: is the following use of dirname
a bulletproof way to prevent the attack?
<?php
if (dirname($_GET['f']) != '.') die ('Attack prevented');
It sounds like it should be since dirname
:
- returns
.
if and only if there are no slashes in the input path (the online documentation makes a less rigorous guarantee but the source is explicit) - is binary-safe (so cannot be tricked by embedded nulls)
So as far as I can tell, the only possible avenue of attack would be to pass data to $_GET['f']
in an encoding such that either the character /
or \
(let's not forget Windows) encodes to something that does not contain the ASCII value of the corresponding character and at the same time this encoding has to be transparently supported by the underlying C runtime library's fopen
function.
The no-ASCII-value restriction rules out all single-byte encodings, UTF-8, and both flavors of UTF-16; furthermore, since the spec for the C runtime is platform-agnostic the attack could only be applicable to some filesystem that used a "vulnerable" encoding to represent names. Such a filesystem does not, to my knowledge, exist; it would hardly make sense for anyone to create it; and finally PHP would not be hosted on such a hypothetical exotic system even if it existed.
In conclusion, it seems to me that this check is 100% safe. Is there something I missed?