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.
Related
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
My code is as follows:
<?php
session_start();
$img=imagecreatetruecolor(150,50);
$white=imagecolorallocate($img,255,255,255);
$black=imagecolorallocate($img,0,0,0);
$red=imagecolorallocate($img,255,0,0);
$pink=imagecolorallocate($img,200,0,150);
$grey=imagecolorallocate($img,150,150,150);
$blue=imagecolorallocate($img,0,204,255);
$redd=imagecolorallocate($img, 153, 0,0);
function randomString($length){
$chars="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ023456789";
srand((double)microtime()*1000000);
$str="";
while($i<=$length){
$num=rand() % 33;
$tmp=substr($chars,$num,1);
$str.=$tmp;
$i++;
}
return $str;
}
for($i=0;$i<=rand(1,5);$i++)
{
$color=(rand(1,2)==1)? $grey:$white;
imageline($img, rand(5,50),rand(5,50),rand(50,150) , rand(5,50), $color);
}
$ran=randomString(rand(3,6));
$_SESSION['captcha']=$ran;
imagefill($img,0,0,$redd);
imagettftext($img,14,7,23,27,$black,"fonts/times_new_yorker.ttf",$ran);
imagettftext($img,16,10,18,30,$white,"fonts/times_new_yorker.ttf",$ran);
header("Content-type:image/png");
imagepng($img);
imagedestroy($img);
?>
Yesterday this worked as expected. But now Firefox is showing a message:
This image cannot be displayed, because this contains error.
When I searched for any solutions, it seems everyone is saying something about enabling GD. But in my code GD is enabled, and this very code worked perfectly up until this morning.
Can anyone help me to get a solution for this?
The image cannot be displayed, because PHP reports an error, and the header('Content-Type: image/png') tells it to show the page as an image.
To see the error, you should remove the following part:
header("Content-type:image/png");
imagepng($img);
imagedestroy($img);
or better yet, surround it with if (!isset($_GET['debug'])) statement. That way you can append ?debug=1 to your URL and see all possible PHP errors, while the image stills display normally.
There are a few possible solutions why your code might have stopped working without changing it. My guess is that you tampered with environment somehow.
session_start() needs to store session data in a directory on your local drive. Does your PHP have access to that directory?
The font fonts/times_new_yorker.ttf could disappear.
You could have moved the script to Linux machine, where letter casing matters. Are you sure the path to the font shouldn't have uppercase characters anywhere in it?
Also, just a couple of tips:
You don't need to call srand(), it's initialized automatically. (I assume you come from C/C++ background).
Instead of using rand(), you should use mt_rand() as it's faster and provides better randomness.
Instead of using magic numbers, you should use meaningful expressions (for example, replace % 33 with % strlen($chars)).
Since you seem to display a captcha, consider matching 0 and O, 1 and l as the same "character", so that reasonable user mistakes are forgiven. (Pardon if you do it already.)
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)) {}
how safe the following code is, in case everybody could make use of it:
$file = $_FILES["file"]['tmp_name'];
$contents = file_get_contents($file);
$base64 = base64_encode($contents);
<img src="data:image/png;base64,'.$base64.'">
in case this is not safe agains hackers, etc. how should i secure it?
Char set of a base64 encoded string is ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwx yz0123456789+/ and an = sign as a last byte filter.
So no matter what the file contents is, you are putting its base64 encoded value and even without escaping, browser will show the given string in the img tag.
You will however need to make sure that the string actually encodes to an image.
And by the way, you are wasting a lot of your server memory for this task by assigning a new variable in every step.
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.