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.
Related
This is a PHP app running in a Linux Docker container.
A file gets uploaded from the FE that is called "A & T.pdf".
The filename is saved in the database as "A & T.pdf".
The file is saved in Azure File Storage as "A & T.pdf".
When we go to download the file, it says ERROR: File 'A' doesn't exist. It is apparently cutting the filename off before the ampersand.
$filename = get_get('file', '0', 'string', 255);
$file=$CFG->questdir.$filename;
if (file_exists($file)) {
...
} else {
echo "ERROR: File '$filename' doesn't exist";
}
I've tried a number of different things: str_replace($file, '&', '\&'), addeslashes(), urlencode(), and a few others that aren't coming to mind.
Things like this should be sanitized going on, which is being fixed.
At this point, I'm just curious how to to resolve this error as it exists?
Database has the correct name. Storage has the correct name. PHP doesn't like the ampersand. How do you properly escape it in the variable being passed to file_exists()?
EDIT:
Tracing the steps, it looks like the filename is getting chopped off in here:
function get_get($name,$default='',$type='string',$maxlenght=0){
if(!isset($_GET[$name])) {
$var=$default; //Default
} else {
$var=trim($_GET[$name]);
if(strlen($var)>$maxlenght) $var=substr($var,0,$maxlenght);
settype($var,$type);
if($type=="string" && !get_magic_quotes_gpc()) {
$var=pg_escape_string(my_connect(), $var);
}
}
return $var;
}
It looks like it is getting truncated at the $var=trim($_GET[$name]);.
My bet is that it's not actually PHP with this issue, as & is not a special character for PHP, and given the error it actually appears to be the space at issue. While space and & are not special characters in PHP, they are in a URL. So, I suspect what is happening is your URL is something like
http://www.example.org/script.php?name=A & T.pdf
This would need to be URL encoded
http://www.example.org/script.php?name=A%20%26%20T.pdf
PHP has a command you can use if you're setting up the URL with it, otherwise do some googling for online URL encoders: https://www.php.net/manual/en/function.urlencode.php
First of all, my code is working...but the resultant file is causing problems on my server. Only files with strange characters are causing errors on the server, such as file does not exist or error connecting to file when trying to open the file through FTP. All files without strange characters are working fine on the server, and can be opened and edited.
Here's my workflow:
Get text from a TextView on user's screen, run it through this code to remove unwanted characters:
replaceAll("[^a-z ,()A-Z0-9]+", "-");
Create a text file using this text as the file name;
Upload this text file to server with this PHP script:
<?php
$file_path = "uploads/";
$file_path = $file_path . basename( $_FILES['uploaded_file']['name']);
if(move_uploaded_file($_FILES['uploaded_file']['tmp_name'], $file_path)) {
echo "success";
} else{
echo "fail";
}
?>
The filenames are containing these strange characters, I assume due to non English characters on the user's screen.
I need to be careful because the path to upload the file to my server is based on this file name and I don't know how to test it with non English characters. Any help is much appreciated. I need to remove/replace any non English characters without messing up the file path.
Technically you can solve this by converting string on server to UTF-8 using mb_convert_encoding, but really your code is very not safe, as you are using a passed user variable as a file path, and hackers can send /../../../ and so forth.
The solution I use for both, is to convert on server the passed file name to a hex string, using bin2hex. That way you have a very safe file name, with no encoding issues.
Use this line its help you.
String styledText = Your File Name;
textView.setText(Html.fromHtml(styledText));
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.
Currently I am trying to check with PHP if a file exists. The current file I am trying to check if it exists has an apostrophe in it, the file is called:13067-AP-03 A - Situation projetée.pdf.
The code I use to check if the file exist is:
$filename = 'C:/13067-AP-03 A - Situation projetée.pdf';
if (file_exists($filename))
{
echo "The file exists";
} else
{
echo "The file does not exist";
}
The problem that I am facing right now is that whenever I try to check if the file exists I get the message it doesn't exist. If I continue to remove the é I get the message that the file does exist.
It looks that PHP somehow doesn't recognize the file if it has a apostrophe in it. I tried the following:
urlencode($filename);
addslashes($filename);
utf8_encode($filename);
None of which worked. I also tried:
setlocale(LC_ALL, "en_US.utf8");
Maybe worth noticing is that when I get the filename straight from PHP I get the following:
13067-AP-03 A - Situation projet�e.pdf
I have to do the following to have the filename displayed correctly:
$filename = iconv( "CP437", 'UTF-8', $filename);
I was wondering if someone had the same problem before and could help me out with this one. All help is greatly appreciated.
For those who are interested, the script runs on a windows machine.
Strangely this worked: I copied all the source code from Sublime Text 3 to notepad. I proceeded to save the source code in notepad by overwriting the PHP file.
Now when I check to see if the file exists it shows the following filename that exists:
13067-AP-03 A - Situation projet�e.pdf
The only problem that I am facing right now is that I want to download the file using file_get_contents. But file_get_contents doesnt interpet the � as an apostrophe.
I think it's a problem of the PHP under Windows. I downloaded a Windows binary copy to my Windows who's in Japanese and successfully reproduced your problem.
According to https://bugs.php.net/bug.php?id=47096
So, if you have a generic name of a file (along with its path) as a Unicode string $u (for example UTF-8 encoded) and you want to try to save it with that name under Windows, you must first check the current locale calling setlocale(LC_CTYPE, 0) to retrieve the current code page, then you must convert $u to an array of bytes according to the code page; if one or more code points have no counterpart in the current code page, the file cannot be saved with that name from PHP. Dot.
My code page is CP932, which you can see yours by running chcp in cmd.
So the code is expected to be:
$filename='C:\Users\Frederick\Desktop\13067-AP-03 A - Situation projetée.pdf';
$filename=mb_convert_encoding($filename, 'CP932', 'UTF-8');
var_dump($filename);
var_dump(file_exists($filename));
But this won't work! Why? Because CP932 doesn't contain the character of é!
According to https://msdn.microsoft.com/en-us/library/windows/desktop/dd317748%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396
NTFS stores file names in Unicode. In contrast, the older FAT12, FAT16, and FAT32 file systems use the OEM character set.
Windows itself uses UTF-16LE, which is called Unicode by Microsoft, to save its file names. But PHP doesn't support a UTF-16LE encoded file name.
In conclusion, it's a pity that I cannot find a way to solve the problem rather than escaping all those characters when naming the files if you work on Windows. And I also do not think that the team of PHP will solve the problem in the future.
Make sure that your text editor is saving the file as "UTF-8 without BOM"
BOM is the Byte Order Mark, two bytes placed at the start of the file which allow software reading the file to determine if it has been saved as little-endian or big-endian, however the PHP interpreter cannot interpret these characters and so you must save the file without the byte order mark.
Try this on start of your php file:
<?php
header('Content-Type: text/html; charset=utf-8');
?>
This question already has answers here:
PHP - Upload utf-8 filename
(9 answers)
UTF-8 all the way through
(13 answers)
Closed 4 months ago.
I have this home made app that allows multiple file uploads, I pass the files to php with AJAX, create new dir with php, move there uploaded files and save the dir location to database. Then to see the files I run listing of the directory location saved in the db.
The problem is that files come from all around the world so very often they have some non latin characters like for example ü. When I echo the filename in php names appear correctly even when they have names written in Arabic, yet they are being saved on the server with encoded names as for example ü in place of ü. When I list the files from directory I can see the name ü.txt insted of ü.txt but when I click on it server returns error object not found (since on the server it is saved as ü.txt and it reads the link as ü.txt).
I tried some of the suggested solutions as for example using iconv, but the filenames are still being saved the same way.
I could swear the problem wasn't present when the web app was hosted on linux, but at the moment I am not so sure about it anymore. Right now I temporarily run it on xampp (on Windows) and it seems like filenames are saved using windows-1252 encoding (default Windows' encoding on the server). Is it default Windows encoding related problem?
To be honest I do not know how to approach that problem and I would appreciate any help. Should I keep on trying to save the files in different character encoding or would it be better to approach it different way and change the manner of listing the already saved and encoded files?
EDIT. According to the (finally) closed bug report it was fixed in php 7.1.
In the end I solved it with the following approach:
When uploading the files I urlencode the names with rawurlencode()
When fetching the files from server they are obviously URL encoded so I use urldecode($filename) to print correct names
Links in a href are automatically translated, so for example "%20" becomes a " " and URL ends up being incorrect since it links to incorrect filename. I decided to encode them back and print them ending up with something like this: print $dirReceived.rawurlencode($file); ($dirReceived is the directory where received files are stored, defined earlier in the code)
I also added download attribute with urldecode($filename) to save the file with UTF-8 name when needed.
Thanks to this I have files saved on the server with url encoded names. Can open them in browser (very important as most of them are *.pdf) and can download them with correct name which lets me upload and download even files with names written in Arabic, Cyrillic, etc.
So far I tested it and looks good. I am thinking of implementing it in production code. Any concerns/thoughts on it?
EDIT.
Since there are no objections I select my answer as the one that solved my problem. After doing some testing everything looks good on client and server side. When saving the files on server they are URL encoded, when downloading them they are decoded and saved with correct names.
At the beginning I was using the code:
for($i=0;$i<count($_FILES['file']['name']);$i++)
{
move_uploaded_file($_FILES['file']['tmp_name'][$i],
"../filepath/" . $_FILES['file']['name'][$i]);
}
This method caused the problem upon saving file and replaced every UTF-8 special character with cp1252 encoded one (ü saved as ü etc.), so I added one line and replaced that code with the following:
for($i=0;$i<count($_FILES['file']['name']);$i++)
{
$fname= rawurlencode($_FILES['file']['name'][$i]);
move_uploaded_file($_FILES['file']['tmp_name'][$i],
"../filepath/" . $fname);
}
This allows me to save any filename on server using URL encoding (% and two hexadecimals) which is compatible with both cp1252 and UTF-8.
To list the saved files I use filepaths I have saved in DB and list them for files. I was using the following code:
if (is_dir($dir)){
if ($dh = opendir($dir)){
while (($file = readdir($dh)) !== false){
if(is_file($dir . $file)){
echo "<li><a href='".$dir.$file."' download='".$file ."'>".$file."</a></li><br />";
}
}
closedir($dh);
}
}
Since URL encoded filenames were decoded automatically I changed it to:
if (is_dir($dir)){
if ($dh = opendir($dir)){
while (($file = readdir($dh)) !== false){
if(is_file($dir . $file)){
echo "<li><a href='";
print $dir.rawurlencode($file);
echo "' download='" . urldecode($file) ."'>".urldecode($file)."</a></li><br />";
}
}
closedir($dh);
}
}
I don't know if this is the best way to solve it but works perfectly, also I am aware that it is generally a good practice not to use php to generate html tags but at the moment I have some critical bugs that need addressing so first that and then I'll have to work on the appearance of the code itself.
EDIT2
Also the great thing is I do not have to change names of the already uploaded files which in my case is a big advantage.
Are you using $_FILES['upfile']['name'] to name the file? That could create your problem.
How about using GNU Recode?
$fileName = recode_string('latin1',$_FILES['upfile']['name']);
Syntax:
recode_string(string recode type,string $string)
Valid Character sets: http://www.faqs.org/rfcs/rfc1345.html
Somehow you must validate the characters in the uploaded file name.
You could also try sprintf. The formatted string characters can be unpredictable, but will probably work.
$fileName = pathinfo($_FILES['upfile']['name'], PATHINFO_FILENAME);
$fileName = sprintf('./uploads/%s',$fileName);
When you save the file name use
$fileName = mysqli_real_escape_string($fileName)