I have coded a little upload script that works just fine in every case when I use it, but I always get messages from my client that he gets an error that I die() when the upload goes wrong/doesn't work.
I believe that this was caused by the special characters they use. Because they are Austrians they have special chars like äÄöÖüÜß in their filenames. Plus they use whitespaces in there filenames.
Therefore I used a regex to replace special characters into underlines(_).
$moveFile = function($tmpname,$name,$time) {
// the regex to resolve the special chars problem
$name = preg_replace('/[^a-zA-Z0-9\.]/s', '_', $name);
if (!move_uploaded_file($tmpname,'assets/siteContents/bewerbungen/'.$time.'_'.$name)) {
die('something went wrong while uploading');
}
};
// move application_files__image
$moveFile($_FILES['application_files__image']['tmp_name'],
$_FILES['application_files__image']['name'],
$time);
// move application_files__image
$moveFile($_FILES['application_files__cv']['tmp_name'],
$_FILES['application_files__cv']['name'],
$time);
// move application_files__certificates
if (count($_FILES['application_files__certificates']['name'])) {
foreach ($_FILES['application_files__certificates']['name'] as $i => $name) {
$moveFile($_FILES['application_files__certificates']['tmp_name'][$i],
$_FILES['application_files__certificates']['name'][$i],
$time);
$a_list[] = 'assets/siteContents/bewerbungen/'.$time.'_'.preg_replace('/[^a-zA-Z0-9\.]/s', '_',$_FILES['application_files__certificates']['name'][$i]);
}
}
If the error isn't caused by the special chars in the filename I am not sure what the problem might be.
The function returns FALSE on this conditions:
If filename is not a valid upload file, then no action will occur, and
move_uploaded_file() will return FALSE.
If filename is a valid upload file, but cannot be moved for some
reason, no action will occur, and move_uploaded_file() will return
FALSE. Additionally, a warning will be issued.
(Source: PHP.NET)
You are filtering your filename with this regex /[^a-zA-Z0-9\.]/s but this will only replace the first occurence of a special char not all.
Try adding the "g" modifier: /[^a-zA-Z0-9\.]/sg
Are you sure your client has the right permissions to move the file
to the desired location? I would double check them.
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
// What's my mime?
$_mime = 'text/plain';
if ($_file[strlen($_file)-1] == 'j') { $_mime = 'text/javascript'; }
else { $_mime = 'text/css'; }
I really don't understand why the above does not work, my server will response with two input types either .min.js or .min.css
It should take the last character, step back one, which should either be j or s.
The response is always text/css regardless. Of course strict mime restriction then breaks my entire website.
You're off by one, a common error.
Let's say your filename is script.js.
The length is 9, but since the count in an array starts from 0, the j is the 7th letter, and the s the 8th.
string.js
^ ^
0 8
So just do if ($_file[strlen($_file)-2] == 'j')
$file = 'script.js';
console.log('File name length:', $file.length);
console.log('First letter:', $file[0]);
console.log('Last letter:', $file[$file.length - 1]);
console.log('The letter you want:', $file[$file.length - 2]);
Also, I'm assuming $_file is a string with the name of your file.
Anyway, I hope you're aware you aren't doing a mime-type check, but just checking the file extension - this doesn't provide any security to you, and you cannot be sure about the mime-type of the file. You need to trust the source of the file. So do not use this way to determine the mime-type if the file is uploaded to your server by a third party.
Looks like you are just checking the file extension, in that case you could try a simple function like this to get the extension.
function filename($file){
return substr($file,0,strrpos($file,'.'));
}
function extension($file){
return strtolower(substr(strrchr($file,'.'),1));
}
Note that this function is not bullet proof but in a simple scenario such as your it would work.
I have a value that may be either an image URL or an image Base64 string. What is the best method to determine which is which? If it's an image URL, the image will already reside on my server.
I've tried doing a preg_match but I think running a preg_match on a potentially huge base64 string will be server intense.
EDIT: The two best methods thus far.
// if not base64 URL
if (substr($str, 0, 5) !== 'data:') {}
// if file exists
if (file_exists($str)) {}
You mean you want to differentiate between
<img src="http://example.com/kittens.jpg" />
and
<img src="data:image/png;base64,...." />
You'd only need to look at the first 5 chars of the src attribute to figure out if it's a data uri, e.g.
if (substr($src, 0, 5) == 'data:')) {
... got a data uri ...
}
If it doesn't look like a data uri, then it's safe to assume it's a URL and treat it as such.
If those are only two possibilities, you can do something like:
$string = 'xxx';
$part = substr($string, 0, 6); //if an image, it will extract upto http(s):
if(strstr($part, ':')) {
//image
} else {
//not an image
}
Explanation: The above code assumes that the input is either a base64 string or an image. If it's an image, it will and should contain the protocol information (including the :). That's not allowed in a base64 encoded string.
You can do this with preg_match(). When preg_match doesn't see the d, the code will stop. if it finds a d not followed by an a it will stop and so on. Also this way you're not doing superfluous math and string parsing:
if(!preg_match('!^data\:!',$str) {
//image
} else {
//stream
}
You can also use is_file() which will not return true on directories.
// if file exists and is a file and not a directory
if (is_file($str)) {}
I am using Jquery uploader to upload images to my website. It uses a file called uploadhandler.php to manipulate the files. Inside the uploadhandler.php is the following function which appears to make changes to how the filename is formatted etc.
The problem I am having is if I upload a file with spaces in the file name it doesn't appear to be removing the spaces in the file name. Does anyone know how I can edit it to add an extra command to remove any spaces in the file name, or point me in the right direction on how to do it ?.
protected function trim_file_name($name, $type, $index, $content_range) {
// Remove path information and dots around the filename, to prevent uploading
// into different directories or replacing hidden system files.
// Also remove control characters and spaces (\x00..\x20) around the filename:
$file_name = trim(basename(stripslashes($name)), ".\x00..\x20");
// Add missing file extension for known image types:
if (strpos($file_name, '.') === false &&
preg_match('/^image\/(gif|jpe?g|png)/', $type, $matches)) {
$file_name .= '.'.$matches[1];
}
while(is_dir($this->get_upload_path($file_name))) {
$file_name = $this->upcount_name($file_name);
}
$uploaded_bytes = $this->fix_integer_overflow(intval($content_range[1]));
while(is_file($this->get_upload_path($file_name))) {
if ($uploaded_bytes === $this->get_file_size(
$this->get_upload_path($file_name))) {
break;
}
$file_name = $this->upcount_name($file_name);
}
return $file_name;
}
The line:
$file_name = trim(basename(stripslashes($name)), ".\x00..\x20");
Will remove spaces "around" the filename, such as "foo " because of the x20 bit which is a space. You could simply add directly after this line:
$file_name = str_replace(" ", "", $file_name);
Easy as that! Also remember, when you use the 2nd parameter in trim() you remove the "default" list of characters listed in the manual ( http://php.net/trim ) and replace it with your own ...
I would also point out, I would never use the filename as given by the browser in $_FILES[$x]['name'] ... It simply opens too many questions and possibilities up. One technique to avoid the issue altogether might be to simply use the md5() or sha1() or similar of the file's contents, example:
$file_name = md5_file($_FILES['your_file_tag_name']['tmp_name']);
That way in theory, you never ever have spaces in the files, or "dirty" filenames ... And in addition, if a user uploads two files with the precise same contents, you simply need to check for the file's existance, and if it already exists, you already have that exact same file. Hope this helps, this all assumes you're doing file uploads, which I'm not 100% certain you are.
I have a built a script around class.upload from http://www.verot.net/php_class_upload.htm
Basically what it is that all my images are stored on the server in a directory called /images/
The script I built basically takes some parameters from my website such as /xyzDir/tomnjerry.jpg?w=100&h=100&fill=1&color=fff
Then I have mod_rewrite which reads the file from /xyzDir/ into a php script which then translates the width and height and returns the image.
Lately I have noticed some idiots from Turkey trying to input weird characters into the parameters w= and h=
On my script I do check to make sure only integer is allowed in width and heigh and fill can be either 1 or 2 and color can only be certain values which i check via array.
I just want to see if there is anything else I should be doing in order to avoid getting hacked.
Thanks
Always remember, Filter In, Escape Out for all user supplied (or untrusted) input.
When reading user supplied data, filter it to known values. DO NOT BLACKLIST! Always always always always whitelist what you are expecting to get. If you're expecting a hex number, validate it with a regex like: ^[a-f0-9]+$. Figure out what you expect, and filter towards that. Do none of your filenames have anything but alpha, numeric and .? Then filter to ^[a-z0-9.]+$. But don't start thinking blacklisting against things. It won't work.
When using user-data, escape it properly for the use at hand. If it's going in a database, either bind it as a parameterized query, or escape it with the database's escape function. If you're calling a shell command, escape it with escapeshellarg(). If you're using it in a regex pattern, escape it with preg_quote(). There are more than that, but you get the idea.
When outputting user data, escape it properly for the format you're outputting it as. If you're outputting it to HTML or XML, use htmlspecialchars(). If you're outputting to raw headers for some reason, escape any linebreaks (str_replace(array("\r", "\n"), array('\r', '\n'), $string)). Etc, etc, etc.
But always filter using a white-list, and always escape using the correct method for the context. Otherwise there's a significant chance you'll miss something...
create a validation class to validate your post params like so.
class MyValidation
{
public function is_interger($val)
{
return is_int($val);
}
public function within_range($val,$min,$max)
{
if($this->is_interger($val))
{
return ($val < $max && $val > $min);
}
return false;
}
public function is_hex($val)
{
return preg_match("/^([a-f0-9]{3}){1,2}$/",$val);
}
}
And use to validate your values.
Example:
$Validator = new MyValidation();
if($Validator->is_hex($_POST['color']))
{
//Sweet.
}
Make sure the image name does not contain string like "../". Depending on your script, that could be a way to step out images directory and make the script deliver other files.
You should use intval() for ensuring that the width and height are integers
$width = intval($_GET['w']);
$height = intval($_GET['h']);
You can do
$fill = $fill == 1 ? 1 : 2;
Which is a ternary operator, so if it's anything apart from 1 it's going to be set to 2.
As for validation of hex, the rules of hex dictate that it must be in range of 0-9/A-F.
$color = preg_replace('/[^0-9a-f]/i', "", $_GET['color']);
Hope that helps.
(It should be noted that my suggested code will perform the manipulation required to make it suitable for your page, rather than confirming that is is valid before hand)
No one's mentioned the filter extension here which provides great filtering natively implemented in the PHP engine. IMHO this is a great extension and should always be used before rolling your own filtering code. For example, checking for an integer is as simple as:
<?php
if (filter_var($value, FILTER_VALIDATE_INT)) {
//Valid Integer.
}
?>
Validating a hex number can be done with:
<?php
if (filter_var($value, FILTER_VALIDATE_INT, FILTER_FLAG_ALLOW_HEX)) {
//Valid Hex number
}
?>
Then I have mod_rewrite which reads the file from /xyzDir/ into a php script which then translates the width and height and returns the image.
If you include the file, image or other type, it will execute any PHP code buried within it. So if you didn't shake off any possible code appended to a user uploaded image by reformatting it through imagemagick or gd into a completely new file, that is one way your server can be compromised.
So for example if you serve the user uploaded image like this...
<?php
header('Content-type: image/jpeg');
header('Content-Disposition: attachment; filename="tomnjerry.jpg"');
include('xyzDir/tomnjerry.jpg');
?>
...and if the user opened the jpg in a raw text editor and appended <?php phpinfo(); ?> to the very end before uploading it to your server, then they can browse to and download it and extract all phpinfo details of your PHP installation from it.
But since you mentioned resizing the image first, you're probably not even serving the image this way. So you should be safe from this attack.