According to what I read online, to prevent null byte attacks I should use the following on all user input:
$data = str_replace(chr(0), '', $data);
Makes sense to me. However, how do you do this on images the user has uploaded via form? I don't have much experience dealing with images like this.
I'm assuming you can't just do it like:
$_FILES['pic']['tmp_name'] = str_replace(chr(0), '', $_FILES['pic']['tmp_name']);
As mentioned in comments, PHP is no longer generally vulnerable to this attack. Attempts to open files with names containing null bytes will now fail, instead of opening an unexpected file.
Even in versions of PHP that were vulnerable to this attack, no filtering was necessary for uploaded files. The temporary file name used for uploaded files is generated internally by PHP, and will not contain null bytes or any other "surprising" special characters such as spaces.
Related
When I take content of a picture I try to dump it like that:
$filename = '(900).jpg';
$im = file_get_contents($filename);
var_dump(serialize($im));
When the picture is under 1mb everything works, but if it is more than 1mb browser crash can you tell me why is that a browser issue or some limitation of file_get_contents() function?
The only limitation of file_get_contents might be the memory which is allowed for PHP to use. And the default is about 128 MB.
It is a browser "issue" if you want to call it that. Outputting so much debug information to the browser is not a good idea as you can see. Additionally there is no benefit in viewing a binary file as text.
If you want to find out if the variable is set, you can use functions to check the size of the (binary) string e.g. mb_strlen().
A better way would be this
$filename = '(900).jpg';
$im = file_get_contents($filename);
// check if the file could be loaded
if ($im !== false) {
// start your processing
}
But this does not check what kind of file you have loaded into the string. If you must store the file into the database - which is considered very evil - you can either store the binary string into a BLOB type row or encode the binary string with base64_encode() and store it into a text type. Both of this solutions are also not recommended!
If you need to store image information into the database, you should think about using references to the files - e.g. the file path. Your primary objective is to secure that the database information and the filesystem information is always synchronized.
I am trying to open an image that has Latin characters in its name (113_Atlético Madrid).
I saved it by encoding its name with the PHP function rawurlencode(), so now its new name is 113_Atl%C3%A9tico%20Madrid. But when I am trying to open it by this URL for example mysite.com/images/113_Atl%C3%A9tico%20Madrid.png I got 404 error.
How I can fix this issue?
PHP code:
if(isset($_FILES['Team'])){
$avatar = $_FILES['Team'];
$model->avatar = "{$id}_".rawurlencode($model->name).".png";
if(!is_file(getcwd()."/images/avatars/competitions/{$model->avatar}")){
move_uploaded_file($avatar['tmp_name']['avatar'], getcwd()."/images/avatars/teams/{$model->avatar}");
}
}
%-encoding is for URLs. Filenames are not URLs. You use the form:
http://example.org/images/113_Atl%C3%A9tico%20Madrid.png
in the URL, and the web server will decode that to a filename something like:
/var/www/example-site/data/images/113_Atlético Madrid.png
You should use rawurlencode() when you're preparing the filename to go in a URL, but you shouldn't use it to prepare the filename for disc storage.
There is an additional problem here in that storing non-ASCII filenames on disc is something that is unreliable across platforms. Especially if you run on a Windows server, the PHP file APIs like move_uploaded_file() can very likely use an encoding that you didn't want, and you might end up with a filename like 113_Atlético Madrid.png.
There isn't necessarily an easy fix to this, but you could use any form of encoding, even %-encoding. So if you stuck with your current rawurlencode() for making filenames:
/var/www/example-site/data/images/113_Atl%C3%A9tico%20Madrid.png
that would be OK but you would then have to use double-rawurlencode to generate the matching URL:
http://example.org/images/113_Atl%25C3%25A9tico%2520Madrid.png
But in any case, it's very risky to include potentially-user-supplied arbitrary strings as part of a filename. You may be open to directory traversal attacks, where the name contains a string like /../../ to access the filesystem outside of the target directory. (And these attacks commonly escalate for execute-arbitrary-code attacks for PHP apps which are typically deployed with weak permissioning.) You would be much better off using an entirely synthetic name, as suggested (+1) by #MatthewBrown.
(Note this still isn't the end of security problems with allowing user file uploads, which it turns out is a very difficult feature to get right. There are still issues with content-sniffing and plugins that can allow image files to be re-interpreted as other types of file, resulting in cross-site scripting issues. To prevent all possibility of this it is best to only serve user-supplied files from a separate hostname, so that XSS against that host doesn't get you XSS against the main site.)
If you do not need to preserve the name of the file (and often there are good reasons not to) then it might be best to simply rename the entirely. The current timestamp is a reasonable choice.
if(isset($_FILES['Team'])){
$avatar = $_FILES['Team'];
$date = new DateTime();
$model->avatar = "{$id}_".$date->format('Y-m-d-H-i-sP').".png";
if(!is_file(getcwd()."/images/avatars/competitions/{$model->avatar}")){
move_uploaded_file($avatar['tmp_name']['avatar'], getcwd()."/images/avatars/teams/{$model->avatar}");
}
}
After all, what the file was called before it was uploaded shouldn't be that important and much more importantly if two users have a picture called "me.png" there is much less chance of a conflict.
If you are married to the idea of encoding the file name then I can only point you to other answers:
How do I use filesystem functions in PHP, using UTF-8 strings?
PHP - FTP filename encoding issue
PHP - Upload utf-8 filename
Hey guys I've seen a lot of options on fread (which requires a fiole, or writing to memory),
but I am trying to invalidate an input based on a string that has already been accepted (unknown format). I have something like this
if (FALSE !== str_getcsv($this->_contents, "\n"))
{
foreach (preg_split("/\n/", $this->_contents) AS $line)
{
$data[] = explode(',', $line);
}
print_r($data); die;
$this->_format = 'csv';
$this->_contents = $this->trimContents($data);
return true;
}
Which works fine on a real csv or csv filled variable, but when I try to pass it garbage to invalidate, something like:
https://www.gravatar.com/avatar/625a713bbbbdac8bea64bb8c2a9be0a4 which is garbage (since its a png), it believes its csv
anyway and keeps on chugging along until the program chokes. How can I fix this? I have not seen and CSV validators that
are not at least several classes deep, is there a simple three or four line to (in)validate?
is there a simple three or four line to (in)validate?
Nope. CSV is so loosely defined - it has no telltale signs like header bytes, and there isn't even a standard for what character is used for separating columns! - that there technically is no way to tell whether a file is CSV or not - even your PNG could technically be a gigantic one-column CSV with some esoteric field and line separator.
For validation, look at what purpose you are using the CSV files for and what input you are expecting. Are the files going to contain address data, separated into, say, 10 columns? Then look at the first line of the file, and see whether enough columns exist, and whether they contain alphanumeric data. Are you looking for a CSV file full of numbers? Then parse the first line, and look for the kinds of values you need. And so on...
If you have an idea of the kinds of CSVs likely to make it to your system, you could apply some heuristics -- at the risk of not accepting valid CSVs. For instance, you could look at line length, consistency of line length, special characters, etc...
If all you are doing is checking for the presence of commas and newlines, then any sufficiently large, random file will likely have those and thus pass such a CSV test.
I'm trying to reproduce the Null Byte Injection attack on an upload form. I have this code:
<?php
if(substr($_FILES['file']['name'], -3) != "php") {
if(move_uploaded_file($_FILES['file']['tmp_name'], $_FILES['file']['name']))
echo '<b>File uploaded</b>';
else
echo '<b>Can not upload</b>';
}
else
echo '<b>This is not a valid file/b>';
?>
I'm trying to upload a file named like this : file.php%00jpg so it will bypass the substr() and will be uploaded as file.php since move_uploaded_file() should stop at the null byte (%00).
The problem is that the uploaded file is not named file.php on the server but file.php%00jpg (which can be accessed by typing /file.php%2500jpg in the url bar).
It seems that move_uploaded_file() does not care about the null byte, so how does this works? Is it possible to upload a file with .php extension with my piece of code?
Thanks :).
The HTML form urlencodes the file name to %2500 and PHP decodes it again to %00 (percent sign, zero, zero). There is no null byte in your test anywhere, you'd have to name the file with an actual null byte (not possible) or fiddle with the HTTP request manually instead of using the HTML form. Still, current PHP versions are not vulnerable to this attack, PHP internally boxes variables in a so called ZVal container and allows null bytes in the middle of a string without any effect.
I've just read the PHP section on
http://projects.webappsec.org/Null-Byte-Injection.
The example it provides is pretty dumb - I mean, why would you ever want to include a file based on an outside param without checking it first (for directory traversal attacks, for one)?
So, if following standard PHP security practices, such as
encoding user entered data on display
validating user entered stuff that works with files
preventing CRSF
not running uploads via something that executes PHP
etc
Can anyone provide a real life example or a common mistake of PHP developers where this problem can occur?
Thanks
Upate
I'm trying to make something break, and this what I have tried.
// $filename is from public
$filename = "some_file\0_that_is_bad.jpg";
$ext = pathinfo($filename, PATHINFO_EXTENSION);
var_dump($filename, $ext);
Which outputs
string(26) "some_file�_that_is_bad.jpg"
string(3) "jpg"
I believe that part of the fun with Null byte injection is that simple validation may not be good enough to catch them
e.g. the string "password.txt\0blah.jpg" actually ends with ".jpg" as far as the scripting language is concerned .. but when passed to a C based function ( such as many system functions) it gets truncated to "password.txt"
This means that a simple check like this may not be safe. (this is just pseudocode, not PHP)
if ( filename.endswith(".jpg") ) { some_c_function(filename); }
Instead you may have to do
filename = break_at_null(filename);
if ( filename.endswith(".jpg") ) { some_c_function(filename); }
Now it doesn't really matter what that c function is .. the examples in the cited article may have need file reading functions, but it could just as well be database accesses, system calls, etc.