Image Hosting Security

Discussions of secure PHP coding. Security in software is important, so don't be afraid to ask. And when answering: be anal. Nitpick. No security vulnerability is too small.

Moderator: General Moderators

Post Reply
TS_Death_Angel
Forum Commoner
Posts: 31
Joined: Sat Dec 31, 2005 8:49 am

Image Hosting Security

Post by TS_Death_Angel »

I have made an image hosting script for my forum here:
http://z64.emedian.net/phpBB2/files/new/index.php

I've never made a proper one before, and it checks all the usual stuff when a user uploads a file: filetype and filesize, but I was just curious... are there any other security exploits I should look out for? Thanks! And also, a quick HTML question, when you see the image statistics, you'll notice that I've used a text area to display the image code for websites. Everytime I put that in a normal text box, it parsed the HTML inside... is there any way to bypass this? Cheers!

Sample:
Image
User avatar
Oren
DevNet Resident
Posts: 1640
Joined: Fri Apr 07, 2006 5:13 am
Location: Israel

Post by Oren »

Show us your code so we can tell... :wink:
TS_Death_Angel
Forum Commoner
Posts: 31
Joined: Sat Dec 31, 2005 8:49 am

Post by TS_Death_Angel »

Alright :P This script is a bit different than others, since it makes an HTTP request to resize images ;P But it works great, so it doesn't matter :)

Code: Select all

<?php

####################################
# Z64 Station Image Hosting Script #
####################################

# 07-7-06

# Functions
function file_type ($s)
{
	$a = explode('.',$s);
	$f = $a[count($a)-1];
	return strtolower($f);
}

function recursive_directory_size($directory, $format=FALSE)
{
	$size = 0;

	// if the path has a slash at the end we remove it here
	if(substr($directory,-1) == '/')
	{
		$directory = substr($directory,0,-1);
	}

	// if the path is not valid or is not a directory ...
	if(!file_exists($directory) || !is_dir($directory) || !is_readable($directory))
	{
		// ... we return -1 and exit the function
		return -1;
	}
	// we open the directory
	if($handle = opendir($directory))
	{
		// and scan through the items inside
		while(($file = readdir($handle)) !== false)
		{
			// we build the new path
			$path = $directory.'/'.$file;

			// if the filepointer is not the current directory
			// or the parent directory
			if($file != '.' && $file != '..')
			{
				// if the new path is a file
				if(is_file($path))
				{
					// we add the filesize to the total size
					$size += filesize($path);

				// if the new path is a directory
				}elseif(is_dir($path))
				{
					// we call this function with the new path
					$handlesize = recursive_directory_size($path);

					// if the function returns more than zero
					if($handlesize >= 0)
					{
						// we add the result to the total size
						$size += $handlesize;

					// else we return -1 and exit the function
					}else{
						return -1;
					}
				}
			}
		}
		// close the directory
		closedir($handle);
	}
	// if the format is set to human readable
	if($format == TRUE)
	{
		// if the total size is bigger than 1 MB
		if($size / 1048576 > 1)
		{
			return round($size / 1048576, 1).' MB';

		// if the total size is bigger than 1 KB
		}elseif($size / 1024 > 1)
		{
			return round($size / 1024, 1).' KB';

		// else return the filesize in bytes
		}else{
			return round($size, 1).' bytes';
		}
	}else{
		// return the total filesize in bytes
		return $size;
	}
}
// ------------------------------------------------------------

# Configuration
$upload_directory = 'uploads/';              // Directory where uploaded files will be placed. CHMOD to 777
$filesize_limit   = '2097152';               // Max Filesize Limit in bytes
$resize_x         = (int)$_POST['width'];    // Max width of the resized image
$resize_y         = 200;                     // Max height of resized image
$thumb_prefix     = '.thumb';                // Extension to be added to thumbnail image
$web_action       = $_POST['state'];         // Get Variable defining action to take
$html_dir         = 'HTML/';                 // Directory where HTML documents are stored
$all_filetypes    = array('png','bmp','gif', // 
					'jpg','jpeg');           // Accepted Filetypes
$stats_file       = 'stats.txt';             // Location of Statistics file

# Starting Actions
include('header.php');

# URL Configuration
$root_location    = 'http://z64.emedian.net/phpBB2/files/new/';

# Main Action, based off of switches... WEEEEEEEEEEEEEEE!
switch($web_action)
{
	case '1':
	# Notify user of status
	echo 'Uploading file, please wait...<br />';
	
	# Check File Integrity
	if($_FILES['userfile']['size']>$filesize_limit)
	{
		echo 'Error. Filesize was larger than allowed!';
		include('footer.php');
		die();
	} else if (!in_array(file_type($_FILES['userfile']['name']),$all_filetypes)) {
		echo 'Error! You have uploaded an invalid file! Extensions allowed: <br /><pre>';
		print_r($all_filetypes);
		include('footer.php');
		die();
	}
	
	# Process Uploaded File
	$random_dir       = $upload_directory.rand(1000,9999);
	$ft1              = file_type($_FILES['userfile']['name']);
	$ft               = '.'.file_type($_FILES['userfile']['name']);
	$rand_filename    = md5(time()+rand(0,999)).$ft;
	$rand_filename_th = md5(time()+rand(0,999)).$thumb_prefix.$ft;
	
	if(!is_dir($random_dir))
	{
		mkdir($random_dir);
	}
	
	$uploadfile       = $random_dir.'/'.$rand_filename;
	$upload_result    = move_uploaded_file($_FILES['userfile']['tmp_name'], $uploadfile);
	
	if($upload_result)
	{
		# Resize Image
		$image_resized = fopen($random_dir.'/'.$rand_filename_th,'w');
		
		if(strlen($resize_x)<2)
		{
			$resize_x = 140;
		} else if($resize_x>1000) {
			$resize_x = 140;
		}
		$url = 'http://z64.emedian.net/phpBB2/files/img/phpThumb.php'.
					'?src=../new/'.$uploadfile.'&w='.$resize_x.'&fltr[]=fram|1|1|000000|000000|000000'.
					'&fltr[]=wmt'.
					'|^Fkkb+^Xx^Y|0|B|FFFFFF|arial.ttf|100|0||000000|100|x&f='.$ft1.'';
		#if(ft1==='jpeg' or $ft1==='jpg')
		#{
		#	$url .= '&JPEGquality=50%';
		#}
		fwrite($image_resized,file_get_contents($url));
		fclose($image_resized);
		
		
		# Generate Code
		$bbcode = '[url='.$root_location.$random_dir.'/'.$rand_filename.'][img]'.
		$root_location.$random_dir.'/'.$rand_filename_th.'[/img][/url]';
		
		$dlink  = $root_location.$random_dir.'/'.$rand_filename;
		
		$htmlc  = '<a href="'.$root_location.$random_dir.'/'.$rand_filename.'"><img src="'.
		$root_location.$random_dir.'/'.$rand_filename_th.'"></a>';
		
		
		# Display File Results
		$results = '<table width="1015" border="0" cellspacing="2" cellpadding="2">
		  <tr>
			<td width="420"><form id="form1" name="form1" method="post" action="">
			  <label> Direct link: <br />
			  <input name="textfield" type="text" size="70" value="'.$dlink.'" />
			  </label>
			  <br />
			  <br />
			  Image BBCode for forums:<br />
		  <input name="textfield2" type="text" size="70" value="'.$bbcode.'" />
		  <br />
		  <br />
			  Image code for websites:<br />
			  <textarea name="textarea" cols="67" rows="2" wrap="off">'.$htmlc.'</textarea>
			</form></td>
			<td align="center" valign="middle">Sample:<br />'.$htmlc.'</td>
		  </tr>
		</table>';
		echo $results;
		
		# Send Email
		if($_POST['send'] === '1')
		{
			include('html_mime_mail_2.5/htmlMimeMail.php');
			$mail       = new htmlMimeMail();
			$attach1    = $mail->getFile($uploadfile);
			$attach2    = $mail->getFile($random_dir.'/'.$rand_filename_th);
			$mail->setHtml($results);
			$mail->addAttachment($attach1, 'image'.$ft1, 'image/'.$ft1);
			$mail->addAttachment($attach2, 'image_thumbnail'.$ft1, 'image/'.$ft1);
			$mail->setReturnPath('no-reply@z64.emedian.net');
			$mail->setFrom('"Image Hosting" <no-reply@z64.emedian.net>');
			$mail->setSubject('Your Uploaded Image Details');
			$mail->setHeader('X-Mailer', 'HTML Mime mail class (http://www.phpguru.org, thanks guys!)');
			$result = $mail->send(array($_POST['email']));
		}
		
		# Update Sidebar Statistics
		$stats = file_get_contents($stats_file);
		$s_arr = explode('{{{',$stats);
		$s_arr[0] = (int)$s_arr[0]+1;
		$s_arr[1] = time();
		$s_arr[2] = round((recursive_directory_size($upload_directory)/1024),2);
		$s_arr = implode('{{{',$s_arr);
		$handle = fopen($stats_file,'w');
		fwrite($handle,$s_arr);
		fclose($handle);
	}
	
	break;
	
	default:
	# Display the main page
	$s1 = $html_dir.'1.php';
	include($s1); # Include file to make this code easier to look at
}

# Ending Actions
include('footer.php');

?>
User avatar
feyd
Neighborhood Spidermoddy
Posts: 31559
Joined: Mon Mar 29, 2004 3:24 pm
Location: Bothell, Washington, USA

Post by feyd »

file_type() should use getimagesize() to determine the file type instead of the file extension.
TS_Death_Angel
Forum Commoner
Posts: 31
Joined: Sat Dec 31, 2005 8:49 am

Post by TS_Death_Angel »

Ah, I see... thanks! :D
User avatar
Oren
DevNet Resident
Posts: 1640
Joined: Fri Apr 07, 2006 5:13 am
Location: Israel

Post by Oren »

Actually, you may want to check out this: exif_imagetype()

P.S It will give you the same result as getimagesize() would, but it is much faster (according to the PHP Manual. I personally haven't had a chance to test it myself) and you don't need to work with the array returned by the getimagesize() function :wink:
Post Reply