Consider the following code
<?php
// warning: this code is unsafe and for demonstrational purposes only,
// do not use in a production environment
$filename = $_GET['filename'];
$extension = 'txt';
$path = '/absolute/path';
$fullFilename = sprintf('%s/%s.%s', $path, $filename, $extension);
echo file_get_contents($fullFilename);
We all know (at least I hope so) that prepending an absolute path is by no way an adequate mean to prevent leaving the given path - one can simply insert one or more "../
"s to the query string to reach any path on the file system.
My question is: Analogously to the given example, can $_GET['filename']
also be manipulated in such a way that the given extension suffix can be bypassed as well, i.e. a file other than .txt is echoed? I'm especially thinking of certain control characters that bypass the appended file extension in the same fashion as ../
does with the prefix.
I tried adding some control characters (e.g. ASCII code 127 for delete) to the query string or concatenating two filenames by using &&
, |
or >
, but all to no avail and I wondered if there exists such a possibility at all.
(Btw, I'd like to add the note that this questions is not asked for the purpose to exploit a system. It's purely a hypothetical question that came across my mind recently.)
Sure, the
nullbyte
,\0
(or%00
if you want to test in a query-string), will havefile_get_contents()
stop processing the string when it hits it.So, if
$_GET['filename']
is../../../../../etc/passwd%00
(such asyour_page.php?filename=../../../../etc/passwd%00
),file_get_contents()
will never see the ending.txt
(it will see it, but won't process it).Sadly,
sprintf
does not strip the nullbyte when building your string. I'm not sure about other control characters, but if anything - I would always recommend to strip the nullbyte. However, a more intuitive approach would be to build a whitelist of allowed characters and perform apreg_match()
against the input.Going one-up on that one, you can use PHP's
realpath()
on the built string and it will determine the full path that is really being accessed. So,/absolute/path/../../../../etc/passwd\0.txt
, passed torealpath()
will return/etc/passwd
. You can then do further validation on the returned value (such as, is the extension still.txt
or is it within a specified/required directory).