I think the most important of this question is breaking the paradigm:
"Images are innocent".
Images are not innocent, and are very dangerous, take a look on this article: "Stegosploit hides malicious code in images, this is the future of online attacks".
The short answer is:
Creating a new image file and storing it in a static environment, without execution permissions is a safe way to display eternal picture. But I would like to go a little deeper.
The method described above is used by Facebook, and other sites with Twitter and Google plus.
When you share an image link, they create a kind of "proxy" to protect your page from external intervention. Keep in mind that malicious code could be a javascript sending all key press events to a host to capture information like your credit card numbers with secure code, your password or something else.
For example, Google plus creates a new link to access this file:
https://lh3.googleusercontent.com/proxy/X2NOfJnTtrux8w_5sczYpXHckBLOYKCTJj3p3Zhpf6bnreayAnU7bMNGrHpjvRmfDHWP9oMln_Od5Oxdilarc8AACg=w346-h192
This method is more expensive because you need the proper infrastructure to store this image.
Services like Imgur can provide a safe API to upload file with safety in low costs. This image below is the same image used in my example (end of this answer), the Imgur creates a new image removing all disposable content.
A few months ago I got a problem, I have a website with a service that allowed people publish your image and put it just to use your external links. One day, I discovered a lot of images used to create a kind of DDOS attacks, every visitor redirects the access to a domain, that domain has a lot of other "iframes" redirecting their views.
I'll share a piece of this code, if you can give any contribution to it, please do it..
$filename = "http://s13.postimg.org/f7728bnqv/php_logo_virus.jpg"; //Unsafe image in a external hosting(this file executes a php_info();)
/* 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:
*
*/
$safefilename = trim(basename(stripslashes($filename)), ".\x00..\x20");
/Try to get possible reall extention by imagem type
$tempSafeFile = tempnam(sys_get_temp_dir(), "JLC"); //Create a Temp File to store content in a static env.
//Must have PHP GD lib do execute Details: http://php.net/manual/en/book.image.php
switch (exif_imagetype($safefilename)) {
case IMAGETYPE_JPEG:
$safefilecontent = imagejpeg(imagecreatefromjpeg($safefilename), $tempSafeFile, 100); //Get only file content created a new image and storeing it on my tempfile
$extensions = array('jpg', 'jpeg');
$mime = "image/jpeg";
break;
case IMAGETYPE_PNG:
$safefilecontent = imagepng(imagecreatefrompng($safefilename), $tempSafeFile, 100);
$extensions = array('png');
$mime = "image/png";
break;
case IMAGETYPE_GIF:
$safefilecontent = imagegif(imagecreatefromgif($safefilename), $tempSafeFile, 100);
$extensions = array('gif');
$mime = "image/gif";
break;
case IMAGETYPE_BMP:
$safefilecontent = image2wbmp(imagecreatefromwbmp($safefilename), $tempSafeFile, 100);
$extensions = array('bmp');
$mime = "image/x-MS-bmp";
break;
//There is a lot of other image types... I use this 4 just for a example
default :
throw new Exception("May its a unsafe image file!",500,null);
break;
}
// Adjust incorrect image file extensions:
if (!empty($extensions)) {
$parts = explode('.', $safefilename);
$extIndex = count($parts) - 1;
$ext = strtolower(@$parts[$extIndex]);
if (!in_array($ext, $extensions)) {
$parts[$extIndex] = $extensions[0];
$safefilename = implode('.', $parts);
}
}
//Now you can save, move, store this file in a safe place or just display it:
header("Pragma: public");
header("Expires: -1");
header("Cache-Control: public, must-revalidate, post-check=0, pre-check=0");
//header("Content-Disposition: attachment; filename=\"$safefilename\""); //Download instead
header("Content-Type: " . $mime);
echo file_get_contents($tempSafeFile);
exit;
Full code here: https://gist.github.com/LeonanCarvalho/8064a5d66b990b1dafc9
We know that there are no miracle cures, but you can hinder the discovery of the breach.
With my negative experience I could observe the following good points to make the external display safest images: