Can an appended file suffix to a parameter for fil

2019-07-19 18:34发布

问题:

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.)

回答1:

Sure, the nullbyte, \0 (or %00 if you want to test in a query-string), will have file_get_contents() stop processing the string when it hits it.

So, if $_GET['filename'] is ../../../../../etc/passwd%00 (such as your_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 a preg_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 to realpath() 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).