I am making an upload script for a music-related thing i am building.
I check whether the file is an MP3 or an OGG, but even if it IS, php will return true on this and say that it isnt on either of these checks.
Here's some example code:
$ft = "mp3";
if($ft != "mp3" || $ft != "ogg") { echo "not an ogg / mp3"; }
What it returns:
not an ogg / mp3
If i am doing something wrong, i am more than glad to be crapped on by Stack Overflow this time, cuz at least i know i did something stupidly wrong.
|| operator means or so if one of the following conditions is true it will do the action of the condition , you need to change it to && which means the both condition should be true to do the action
Related
A coworker today made a bet with me that he knows of a way to supply a specially formatted string that could pass the following regex check and still supply a file name with extension .php or .jsp or .asp:
if (preg_match('/\.(jpeg|jpg|gif|png|bmp|jpe)$/i', $var) && preg_match('/\.(asp|jsp|php)$/i', $var) == false)
{
echo "No way you have extension .php or .jsp or .asp after this check.";
}
As hard as I tried myself and searched the net, I was unable to find a flaw that would make such thing possible. Could I be overlooking something? Given that "null byte" vulnerability is dealt with, what else might be the issue here?
Note: In no way am I implying that this code is a full-proof method of checking the file extension, there might be a flaw in preg_match() function or the file contents could be of different format, I just ask the question in terms of regex syntax itself.
EDIT - actual code:
if (isset($_FILES["image"]) && $_FILES["image"]["name"] && preg_match('/\.(jpeg|jpg|gif|png|bmp|jpe)$/i', $_FILES["image"]["name"]) && preg_match('/\.(asp|jsp|php)$/i', $_FILES["image"]["name"]) == false) {
$time = time();
$imgname = $time . "_" . $_FILES["image"]["name"];
$dest = "../uploads/images/";
if (file_exists($dest) == false) {
mkdir($dest);
}
copy($_FILES['image']['tmp_name'], $dest . $imgname);
}else{
echo "Invalid image file";
}
PHP version: 5.3.29
EDIT: epilogue
Turned out the 'vulnerability' only presents itself on Windows. Nevertheless, it did exactly what my coworker told me it would - passed the regex check and saved the file with executable extension. Following was tested on WampServer 2.2 with PHP 5.3.13:
Passing the following string to the regex check above test.php:.jpg (note the ":" colon symbol at the end of desired extension) will validate it and the function copy() seems to omit everything after the colon symbol including the symbol itself.
Again, this is only true for windows. On linux the file will be written exactly with the same name as passed to the function.
There is not a single step or a full direct way to exploit your code but here are some thoughts.
You are passing it to copy() in this example but you have mentioned that you have been using this method to validate file ext awhile now so I assume you had other cases that may have used this procedure with other functions too on different PHP versions.
Consider this as a test procedure (Exploiting include, require):
$name = "test.php#.txt";
if (preg_match('/\.(xml|csv|txt)$/i', $name) && preg_match('/\.(asp|jsp|php)$/i', $name) == false) {
echo "in!!!!";
include $name;
} else {
echo "Invalid data file";
}
This will end up by printing "in!!!!" and executing 'test.php' even if it is uploaded it will include it from the tmp folder - of course that in this case you are already owned by the attacker but let's consider this options too.
It's not a common scenario for an uploading procedure but it's a concept that can be exploited by combining several methods:
Let's move on - If you execute:
//$_FILES['image']['name'] === "test.php#.jpg";
$name = $_FILES['image']['name'];
if (preg_match('/\.(jpeg|jpg|gif|png|bmp|jpe)$/i', $name) && preg_match('/\.(asp|jsp|php)$/i', $name) == false) {
echo "in!!!!";
copy($_FILES['image']['tmp_name'], "../uploads/".$name);
} else {
echo "Invalid image file";
}
Again perfectly fine. The file is copied into "uploads" folder - you can't access it directly (since the web server will strip down the right side of the #) but you injected the file and the attacker might find a way or another weak point to call it later.
An example for such execution scenario is common among sharing and hosting sites where the files are served by a PHP script which (in some unsafe cases) may load the file by including it with the wrong type of functions such as require, include, file_get_contents that are all vulnerable and can execute the file.
NULL byte
The null byte attacks were a big weakness in php < 5.3 but was reintroduced by a regression in versions 5.4+ in some functions including all the file related functions and many more in extensions. It was patched several times but it's still out there and alot of older versions are still in use. In case you are handling with an older php version you are definitely Exposed:
//$_FILES['image']['name'] === "test.php\0.jpg";
$name = $_FILES['image']['name'];
if (preg_match('/\.(jpeg|jpg|gif|png|bmp|jpe)$/i', $name) && preg_match('/\.(asp|jsp|php)$/i', $name) == false) {
echo "in!!!!";
copy($_FILES['image']['tmp_name'], "../uploads/".$name);
} else {
echo "Invalid image file";
}
Will print "in!!!!" and copy your file named "test.php".
The way php fixed that is by checking the string length before and after passing it to more deeper C procedure that creates the actual char array and by that if the string is truncated by the null byte (which indicates end of string in C) the length will not match. read more
Strangely enough even in patched and modern PHP releases it's still out there:
$input = "foo.php\0.gif";
include ($input); // Will load foo.php :)
My Conclusion:
Your method of validating file extensions can be improved significantly - Your code allows a PHP file called test.php#.jpg to pass through while it shouldn't. Successful attacks are mostly executed by combining several vulnerabilities even minor ones - you should consider any unexpected outcome and behavior as one.
Note: there are many more concerns about file names and pictures cause they are many time included in pages later on and if they are not filtered correctly and included safely you expose yourself to many more XSS stuff but that's out of topic.
Try this code.
$allowedExtension = array('jpeg','png','bmp'); // make list of all allowed extension
if(isset($_FILES["image"]["name"])){
$filenameArray = explode('.',$_FILES["image"]["name"]);
$extension = end($filenameArray);
if(in_array($extension,$allowedExtension)){
echo "allowed extension";
}else{
echo "not allowed extension";
}
}
preg_match() returns 1 if the pattern matches given subject, 0 if it does not, or FALSE if an error occurred.
$var = "test.php";
if (preg_match('/\.(jpeg|jpg|gif|png|bmp|jpe)$/i', $var) === 1
&& preg_match('/\.(asp|jsp|php)$/i', $var) !== 1)
{
echo "No way you have extension .php or .jsp or .asp after this check.";
} else{
echo "Invalid file";
}
So when you are going to check with your code, use === 1.
Ideally you should use.
function isImageFile($file) {
$info = pathinfo($file);
return in_array(strtolower($info['extension']),
array("jpg", "jpeg", "gif", "png", "bmp"));
}
I remember that in certains version in PHP < 5.3.X, PHP allows strings to contain 0x00, this char is considered as the end of string
So, for exemple, if your string contains : myfile.exe\0.jpg, so preg_match() will match jpg, but other PHP functions will stop in myfile.exe, like include() or copy() functions
I've seen many references in many forums to this statement in regards to uploading files
if ($_FILES["file"]["size"] < 20000)
Yet when I use this, it seems that no matter what the size of the file being uploaded, this always returns true.
I have placed echo statements like these to try to determine what is going on
if ($_FILES["file"]["size"] < 20000) {
echo "The file size (".$_FILES["file"]["size"].") is smaller than 20000" ;}
else {
echo "The file size (".$_FILES["file"]["size"].") is larger than 20000" ;}
and what I will get echoed is something like "The file size (2000000) is smaller than 20000" and never will I see "The file size (2000000) is larger than 20000" regardless of the size of the uploaded file
Any help would be much appreciated....
Thanks to all who responded, my problem turned out to me a simple typo. Sorry to waste your time with such a rookie mistake
check value of $_FILES["file"]["size"] first by echoing echo
$_FILES["file"]["size"];
check values of $_FILES by print_r($_FILES);
Depending on the values u can check for conditions
I'm trying to modify a line in PHP file that deals with data that comes from XML(about 600 stations). The php make this data usable by a streamer media player.
The original line is this
if (($title <> "")&& (strpos($link,"<") === false)&& preg_match("/Cha/i",$lang))
this gives me about 50 stations, I want to add few stations that do not have "Cha" in $lang but "Soprts" and has "China" in their $title.
So I wrote the line like this
if (($title <> "")&& (strpos($link,"<") === false)&& preg_match("/Cha/i",$lang)&& (preg_match("/China/i",$title)||preg_match("/Sports/i",$lang)))
But now I'm getting less results than before, now I'm getting only the stations that have CHA in $lang and "China" in $title. All the relevant stations that have CHA in $lang but not "China" in $title are ruled out.
As I have very limited knowledge in PHP I don't know how to make the proper sorting, someone offered me to use stripos but I don't know how to use it and his example didn't worked OK
You flipped the and and or in your new code. You want:
if (($title <> "")&& (strpos($link,"<") === false)&& (preg_match("/Cha/i",$lang)|| (preg_match("/China/i",$title)&&preg_match("/Sports/i",$lang))))
You dont put bracets corretly. You can add multiple options in regex by using | (means OR) ,try this:
if (($title <> "") && (strpos($link,"<") === false) && preg_match("/CHA|China|Sports/i",$lang))
I'm trying to learn PHP programming through trial, error and lots and lots of practice.
I've been working on a directory bases image gallery for the last few days and it seems to work fine. I can upload pictures and create new albums. Plus whenever I see a full size picture I have links to the previous and next pictures in the directory.
Now this is where I ran into problems. Once you got to the end of the directory the code would start an infinite loop while it searches for the next file, which isn't there.
So I altered my code and it now looks something like this:
$x = 1;
$dir = opendir($base.'/'.$get_album);
while ((($file = readdir($dir)) !== FALSE) && ($x == 1)) {
while ($img < $file) {
$img++;
$image_exists = file_exists($base.'/'.$get_album.'/'.$img);
if ($image_exists) {
echo "<a href='member.php?album=$get_album&imgID=$img'>
<img src='$base/$get_album/$img' width='70' align='right'>
</a>";
break;
}
$x++;
}
}
This works when I get to the last picture in the directory. So I thought I'd do the same for when I get to the first picture and just invert the operators like so:
$x = 1;
$dir = opendir($base.'/'.$get_album);
while ((($file = readdir($dir)) !== FALSE) && ($x == 1)) {
while ($img > $file) {
$img--;
$image_exists = file_exists($base.'/'.$get_album.'/'.$img);
if ($image_exists) {
echo "<a href='member.php?album=$get_album&imgID=$img'>
<img src='$base/$get_album/$img' width='70' align='right'>
</a>";
break;
}
$x++;
}
}
This however DOES NOT work. I can't understand why that would be the case and I've tried to change things around a few dozen times but I cant seem to make it stop looping.
What am I doing wrong?
Thanks in advance for your help (and for reading through all this).
The issue could likely be related to the fact that readdir returns a string, and you are treating it as if you know its a number. You might find that it is returning you something at the start or end, such as the parent directory '..' as a string or the current directory as a string '.'.
If you want to iterate over the directories, you should check that the return value from readdir is not '.' or '..' specifically.
Please use a better format to your code (it's really painful read it), for instance, in netbeans you can use shiftalt F to format automatically.
I want address your attention to this line:
$image_exists= file_exists($base.'/'.$get_album.'/'.$img);
That is a possible source of errors. If only $base.'/'.$get_album.'/' exists then:
$image_exists= file_exists($base.'/'.$get_album.'/'.$img); // is true even if $img is empty!!!
But moreover:
What is $img? You treat it as a number when do $img++ and then as a string. That is not good for your health.
A good start it should be initialize your vars at the beginning of your code. And then use them in consonance.
I think there is something fundamentally wrong with the design, you are comparing $img, an uninitialized integer, to $file, a string.
It also seems needlessly complicated, as you are reading files from a directory, then check if they exist and break out of your loop if they do.
Perhaps with a clear view of the directory structure and what you are trying to do, I could provide a more detailed answer, but I think you need to go back to the drawing board.
So I have an upload script, and I want to check the file type that is being uploaded. I only want pdf, doc, docx and text files
So I have:
$goodExtensions = array('.doc','.docx','.txt','.pdf', '.PDF');
$name = $_FILES['uploadFile']['name'];
$extension = substr($name, strpos($name,'.'), strlen($name)-1);
if(!in_array($extension,$goodExtensions) || (($_FILES['uploadFile']['type'] != "applicatioin/msword") || ($_FILES['uploadFile']['type'] != "application/pdf"))){
$error['uploadFile'] = "File not allowed. Only .doc, .docx, .txt and pdf";
}
Why I'm getting the error when testing and including correct documents?
Since you are using OR instead of AND in your expression:
if (!in_array($extension,$goodExtensions)
|| (($_FILES['uploadFile']['type'] != "applicatioin/msword")
|| ($_FILES['uploadFile']['type'] != "application/pdf"))) {
$error['uploadFile'] = "File not allowed. Only .doc, .docx, .txt and pdf";
}
this always evaluates to true: if the file extension is listed in the array goodExtensions, the first expression is false. However, since the file type can not be both Word and PDF at the same time, the second bracketed expression is always true.
So if you want to ensure that either the file extension or the MIME type is good, the correct expression would be (including the fix for the typo in "applicatioin/msword"):
if (!in_array($extension,$goodExtensions)
|| (($_FILES['uploadFile']['type'] != "application/msword")
&& ($_FILES['uploadFile']['type'] != "application/pdf"))) {
$error['uploadFile'] = "File not allowed. Only .doc, .docx, .txt and pdf";
}
The third parameter for substr is the length, not the end position. If you want everything up until the end of the string just omit the third parameter entirely:
$extension = substr($name, strpos($name,'.'));
You've also spelt application wrong in applicatioin/msword.
Finally, you might want to use strrpos instead of strpos, in case the filename contains other dots before the one separating the extension.
Edit: the logic in the if statement is wrong as well. You error if either the extension isn't known, or the type is not MS Word, or the type is not PDF. The type can't be both of those at once, so it'll always fail. You want the last || to be a &&, I think.
Probably because one (or more) of those 3 conditions in the if statement returns true.
Why I'm getting the error when testing and including correct documents?
I don't know, but you would do well to take the big "if" apart into singular blocks to find the error.
Make test outputs of the MIME type and file extension.
echo "Extension = ".$extension."<br>";
echo "MIME Type = ".$_FILES['uploadFile']['type'];
Also, one thing that jumps the eye is a typo in applicatioin/msword.