I have an API that accepts base64 encoded image data, and will need to decode the data, save the image file, then create a thumbnail from that image.
I am concerned that malicious code could get executed if I do not properly validate the contents of the POST payload before attempting to create a thumbnail.
The basic workflow I have thus far is below. Is there enough validation that I do not need to be concerned about security? I guess I am worried about someone encoding something bad, then when one of the image functions below is called, the internet explodes.
<?php
$decodedImage = base64_decode($_POST["canvas"]);
if ($decodedImage === false) {
// Error out
}
$imageSizeValidation = getimagesizefromstring($decodedImage);
if ($imageSizeValidation[0] < 1 || $imageSizeValidation[1] < 1 || !$imageSizeValidation['mime']) {
// Error out
}
$tempFilePath = "/tmp/" . microtime(true) . "-canvas-web.jpg";
file_put_contents($tempFilePath, $decodedImage);
$originalWidth = $imageSizeValidation[0];
$originalHeight = $imageSizeValidation[1];
$newWidth = 49;
$newHeight = 49;
$scaleWidth = $newWidth / $originalWidth;
$scaleHeight = $newHeight / $originalHeight;
$scale = min($scaleWidth, $scaleHeight);
$width = (int)($originalWidth * $scale);
$height = (int)($originalHeight * $scale);
$xpos = (int)(($newWidth - $width) / 2);
$ypos = (int)(($newHeight - $height) / 2);
$oldImage = imagecreatefromjpeg($tempFilePath);
$newImage = imagecreatetruecolor($width, $height);
$background = imagecolorallocate($oldImage, 255, 255, 255);
imagefilledrectangle($newImage, 0, 0, $width, $height, $background);
imagecopyresampled($newImage, $oldImage, $xpos, $ypos, 0, 0, $width, $height, $originalWidth, $originalHeight);
imagedestroy($oldImage);
imagejpeg($newImage, "/path/to/new.jpg", 90);
imagedestroy($newImage);
Didn't end up getting any answers, so for those that are interested in what I ultimately did:
After investigating this a bit more, I found that one of my biggest concerns was valid image files encoded with inline PHP, Ruby, etc. EG: An image with the following at the end:
<?php phpinfo();
I ended up taking the decoded image data, and giving it to imagecreatefromstring(), then saving the image to a temp directory via imagejpeg().
This seemed to remove any encoded PHP from the original image data. At that point I validated the image size data of the saved image using getimagesize(). Assuming that everything was valid at that point, I moved the image to a permanent location.
Another thing I changed, was instead of using a filename based on a static string and microtime(), I used a hash.
Regarding the concern of images with code injected, I found this link to be helpful:
https://www.owasp.org/index.php/Unrestricted_File_Upload
I also found this other SO post useful, as far as overall ideas go:
Validating base64 encoded images
Finally, the following book brought the concern of images with code to my attention in the first place:
http://www.apress.com/9781430233183
Related
I have a folder named images, where I have images like: hero.jpg, hero_medium.jpg, hero_small.jpg. My question is, if this is the correct solution, or maybe it would be better to have one big picture and change its size via URL. At the moment I have managed to do something like this in img.php file in images folder:
<?php
header('Content-Type: image/jpeg');
$filename = 'hero.jpg';
list($width_orig, $height_orig) = getimagesize($filename);
if(empty($_GET['w'])){
$width = $width_orig;
} else {
$width = $_GET['w'];
}
$height=$width;
$ratio_orig = $width_orig/$height_orig;
if ($width/$height > $ratio_orig) {
$width = $height*$ratio_orig;
} else {
$height = $width/$ratio_orig;
}
$image_p = imagecreatetruecolor($width, $height);
$image = imagecreatefromjpeg($filename);
imagecopyresampled($image_p, $image, 0, 0, 0, 0, $width, $height, $width_orig, $height_orig);
imagejpeg($image_p, null, 100);
I would like to change the size of a photo by a friendly URL, i.e. hero_medium.jpg to e.g. 768px in php using htaccess, is such a thing possible?
EDIT: Apple has an interesting situation on its website. The URL for the images looks like the following: https://www.apple.com/newsroom/images/environments/stores/Apple_Tower-Theatre-now-open-in-downtown-LA-store-interior-wide-shot_062421_big.jpg.medium.jpg, why is there big.jpg.medium.jpg? I suspect they may be doing something with htaccess file, because having that many photos it would be unreadable, so I think they are resizing them dynamically. What do you think?
EDIT 2: I noticed that it takes much longer to load and change the image via php, is this actually a good idea?
If you just want to return an image of adequate dimensions, there are several ways to achieve this.
Maybe you want to have a look at this:
https://css-tricks.com/a-guide-to-the-responsive-images-syntax-in-html/
I have stored multiple images in base64 inside my database. I get images using php as image path. But I want to reduce size of my image when decoding it from base64, because it slows down my app if I load full size image. (Full size image I need just in backend).
/*
DB stuff getting base64 string from database
$img = base64 string (can be with 'data:image/jpg;base64,' in front, thats for the str_replace())
*/
if($img){
header("Content-Type: image/png");
echo base64_decode(str_replace("data:image/jpg;base64,","",$img));
}
Everything works nice this way. I use it like this:
<img src="http://example.com/getimg.php?id=4" />
or in css. I need this because of security reasons, I cant store any image on server, also in path I have access_token variable, so random person cant see images.
Is there a way to do this without storing the actual image in server?
You can use imagecreatefromstring and imagecopyresized.
Live example here
<?php
if ($img) {
$percent = 0.5;
// Content type
header('Content-Type: image/jpeg');
$data = base64_decode($img);
$im = imagecreatefromstring($data);
$width = imagesx($im);
$height = imagesy($im);
$newwidth = $width * $percent;
$newheight = $height * $percent;
$thumb = imagecreatetruecolor($newwidth, $newheight);
// Resize
imagecopyresized($thumb, $im, 0, 0, 0, 0, $newwidth, $newheight, $width, $height);
// Output
imagejpeg($thumb);
}
Good day guys,
I inherited some legacy code that reads a BLOB from the database, parses it and returns the jpeg stream to be displayed in the browser. The legacy code used to run on PHP 5.5, it has since been deployed on a PHP 7.1 server where I am running into the issue.
I have made sure that the gd library for php is installed and loaded in Apache, I can verify that it is enabled and working with the php info file. I have also dumped the value from the DB and made sure the blob is a valid image, which it is. For some reason or another I cannot get the function to provide the image.
Here is the code for the function after the data has been retrieved from the DB and stored in $image
$image = imagecreatefromstring($image);
$originalWidth = imagesx($image);
$originalHeight = imagesy($image);
header ( 'Content-type:image/jpeg' );
if($originalWidth == $width && $originalHeight == $height)
{
if($thumb && $width >= $originalWidth) echo imageautocrop($image);
else echo imagejpeg($image);
}
else
{
$ratio = $originalHeight/$originalWidth;
$height = $width*$ratio;
$height = round($height);
$new = imagecreatetruecolor($width, $height);
imagecopyresampled($new, $image, 0, 0, 0, 0, $width, $height, imagesx($image),imagesy($image));
imageantialias($new, true);
if($thumb) imageautocrop($image);
else imagejpeg($new, null, $quality);
imagedestroy($new);
}
I have tried using the exact dimensions as well as the cropped dimensions, neither of which work.
Okay I'm really new to PHP I found the following script below. But I dont know how to use it I was wondering where do I put the link to the image for example images/photo.jpg inorder to get me started in learning this script thanks.
Here is the code.
<?php
function resizeImage($originalImage,$toWidth,$toHeight){
// Get the original geometry and calculate scales
list($width, $height) = getimagesize($originalImage);
$xscale=$width/$toWidth;
$yscale=$height/$toHeight;
// Recalculate new size with default ratio
if ($yscale>$xscale){
$new_width = round($width * (1/$yscale));
$new_height = round($height * (1/$yscale));
}
else {
$new_width = round($width * (1/$xscale));
$new_height = round($height * (1/$xscale));
}
// Resize the original image
$imageResized = imagecreatetruecolor($new_width, $new_height);
$imageTmp = imagecreatefromjpeg ($originalImage);
imagecopyresampled($imageResized, $imageTmp, 0, 0, 0, 0, $new_width, $new_height, $width, $height);
return $imageResized;
}
?>
There are a couple of potential gotchas here, so I'll give you a few of the potential issues you can run into:
You need to give the full path to the image so that it can be read. I use the following script for this:
function getRoot(){
$cwd = getcwd();
$splitCwd = explode("/", $cwd);
$root = "";
for($count=0; $count<count($splitCwd)-1;$count++){
$root .= '/' . $splitCwd[$count];
}
$root = $root . '/';
return $root;
}
Then you can pass in (getRoot() . $image, ...)
Create a switch that will check the file type (see my answer here). This will allow you to resize more than just jpegs, and output more than just jpegs, which is good when transparency may be involved.
There possible could be a final parameter or two, which is output filename. That way, you can make thumbnails while leaving the original image intact. In that case, you would do the imagejpeg (or imagepng, etc), and pass it the new name parameter if it is set.
You'd pass the link to the original JPEG as the first parameter in the function, which would be set as $originalImage.
So when calling the function, you'd use:
resizeImage("images/photo.jpg",800,600);
The two numbers would be your width/height values.
Instead of the return line you must add
header('Content-type: image/jpeg');
imagejpeg($imageResized);
Or check http://www.php.net/manual/en/function.imagejpeg.php and the Example #2 Saving a JPEG image
I want to allow users to upload avatar-type images in a variety of formats (GIF, JPEG, and PNG at least), but to save them all as PNG database BLOBs. If the images are oversized, pixelwise, I want to resize them before DB-insertion.
What is the best way to use GD to do the resizing and PNG conversion?
Edit: Sadly, only GD is available on the server I need to use, no ImageMagick.
<?php
/*
Resizes an image and converts it to PNG returning the PNG data as a string
*/
function imageToPng($srcFile, $maxSize = 100) {
list($width_orig, $height_orig, $type) = getimagesize($srcFile);
// Get the aspect ratio
$ratio_orig = $width_orig / $height_orig;
$width = $maxSize;
$height = $maxSize;
// resize to height (orig is portrait)
if ($ratio_orig < 1) {
$width = $height * $ratio_orig;
}
// resize to width (orig is landscape)
else {
$height = $width / $ratio_orig;
}
// Temporarily increase the memory limit to allow for larger images
ini_set('memory_limit', '32M');
switch ($type)
{
case IMAGETYPE_GIF:
$image = imagecreatefromgif($srcFile);
break;
case IMAGETYPE_JPEG:
$image = imagecreatefromjpeg($srcFile);
break;
case IMAGETYPE_PNG:
$image = imagecreatefrompng($srcFile);
break;
default:
throw new Exception('Unrecognized image type ' . $type);
}
// create a new blank image
$newImage = imagecreatetruecolor($width, $height);
// Copy the old image to the new image
imagecopyresampled($newImage, $image, 0, 0, 0, 0, $width, $height, $width_orig, $height_orig);
// Output to a temp file
$destFile = tempnam();
imagepng($newImage, $destFile);
// Free memory
imagedestroy($newImage);
if ( is_file($destFile) ) {
$f = fopen($destFile, 'rb');
$data = fread($f);
fclose($f);
// Remove the tempfile
unlink($destFile);
return $data;
}
throw new Exception('Image conversion failed.');
}
Your process steps should look like this:
Verify the filetype
Load the image if it is a supported filetype into GD using imagecreatefrom*
Resizing using imagecopyresize or imagecopyresampled
Save the image using imagepng($handle, 'filename.png', $quality, $filters)
ImageMagick is faster, generates better images, is more configurable, and finally is (IMO) much easier to code for.
#ceejayoz Just wait for the new GD - it's OOP like MySQLi and it's actually not bad :)
If you want to use gdlib, use gdlib 2 or higher. It has a function called imagecopyresampled(), which will interpolate pixels while resizing and look much better.
Also, I've always heard noted around the net that storing images in the database is bad form:
It's slower to access than the disk
Your server will need to run a script to get to the image instead
of simply serving a file
Your script now is responsible for a lot of stuff the web server used
to handle:
Setting the proper Content-Type header
Setting the proper caching/timeout/E-tag headers, so clients can properly cache the image. If do not do this properly, the image serving script will be hit on every request, increasing the load on the server even more.
The only advantage I can see is that you don't need to keep your database and image files synchronized. I would still recommend against it though.
Are you sure you have no ImageMagick on server?
I guest you use PHP (question is tagged with PHP). Hosting company which I use has no ImageMagick extension turned on according to phpinfo().
But when I asked them about they said here is the list of ImageMagick programs available from PHP code. So simply -- there are no IM interface in PHP, but I can call IM programs directly from PHP.
I hope you have the same option.
And I strongly agree -- storing images in database is not good idea.
Something like this, perhaps:
<?php
//Input file
$file = "myImage.png";
$img = ImageCreateFromPNG($file);
//Dimensions
$width = imagesx($img);
$height = imagesy($img);
$max_width = 300;
$max_height = 300;
$percentage = 1;
//Image scaling calculations
if ( $width > $max_width ) {
$percentage = ($height / ($width / $max_width)) > $max_height ?
$height / $max_height :
$width / $max_width;
}
elseif ( $height > $max_height) {
$percentage = ($width / ($height / $max_height)) > $max_width ?
$width / $max_width :
$height / $max_height;
}
$new_width = $width / $percentage;
$new_height = $height / $percentage;
//scaled image
$out = imagecreatetruecolor($new_width, $new_height);
imagecopyresampled($out, $img, 0, 0, 0, 0, $new_width, $new_height, $width, $height);
//output image
imagepng($out);
?>
I haven't tested the code so there might be some syntax errors, however it should give you a fair presentation on how it could be done. Also, I assumed a PNG file. You might want to have some kind of switch statement to determine the file type.
Is GD absolutely required? ImageMagick is faster, generates better images, is more configurable, and finally is (IMO) much easier to code for.
This article seems like it would fit what you want. You'll need to change the saving imagejpeg() function to imagepng() and have it save the file to a string rather than output it to the page, but other than that it should be easy copy/paste into your existing code.
I think this page is a good starting point. It uses imagecreatefrom(jpeg/gif/png) and resize and converts the image and then outputs to the browser. Instead of outputting the browser you could output to a BLOB in a DB without many minuttes of code-rewrite.
phpThumb is a high-level abstraction that may be worth looking at.