i don't know if going happen, try it.
for past hour did research on image upload safety. learned there lot of functions test upload.
in project, need safe images uploaded. there may big amount of , may require lot of bandwidth, buying api not option.
so decided full php script secure image upload. think many of people out there, because it's impossible find secure one. not expert in php, it's headache me add functions, ask community create 1 full script of secure image upload.
really great topics here (however, telling needed trick, not how this, , said not master on php, not able myself): php image upload security check list https://security.stackexchange.com/questions/32852/risks-of-a-php-image-upload-form
in summary, telling needed security image upload (i quote above pages):
- disable php running inside upload folder using .httaccess.
- do not allow upload if file name contains string "php".
- allow extensions: jpg,jpeg,gif , png.
- allow image file type.
- disallow image 2 file type.
- change image name. upload sub-directory not root directory.
also:
- re-process image using gd (or imagick) , save processed image. others fun boring hackers"
- as rr pointed out, use move_uploaded_file() upload"
- by way, you'd want restrictive upload folder. places 1 of dark corners many exploits
happen. valid type of upload , programming
language/server. check
https://www.owasp.org/index.php/unrestricted_file_upload- level 1: check extension (extension file ends with)
- level 2: check mime type ($file_info = getimagesize($_files['image_file']; $file_mime = $file_info['mime'];)
- level 3: read first 100 bytes , check if have bytes in following range: ascii 0-8, 12-31 (decimal).
- level 4: check magic numbers in header (first 10-20 bytes of file). can find of files header bytes here:
http://en.wikipedia.org/wiki/magic_number_%28programming%29#examples- you might want run "is_uploaded_file" on $_files['my_files']['tmp_name'] well. see
http://php.net/manual/en/function.is-uploaded-file.php
here's big part of it, still that's not all. (if know more make upload safier, please share.)
this got now
main php:
function uploadfile ($file_field = null, $check_image = false, $random_name = false) { //config section //set file upload path $path = 'uploads/'; //with trailing slash //set max file size in bytes $max_size = 1000000; //set default file extension whitelist $whitelist_ext = array('jpeg','jpg','png','gif'); //set default file type whitelist $whitelist_type = array('image/jpeg', 'image/jpg', 'image/png','image/gif'); //the validation // create array hold output $out = array('error'=>null); if (!$file_field) { $out['error'][] = "please specify valid form field name"; } if (!$path) { $out['error'][] = "please specify valid upload path"; } if (count($out['error'])>0) { return $out; } //make sure there file if((!empty($_files[$file_field])) && ($_files[$file_field]['error'] == 0)) { // filename $file_info = pathinfo($_files[$file_field]['name']); $name = $file_info['filename']; $ext = $file_info['extension']; //check file has right extension if (!in_array($ext, $whitelist_ext)) { $out['error'][] = "invalid file extension"; } //check file of right type if (!in_array($_files[$file_field]["type"], $whitelist_type)) { $out['error'][] = "invalid file type"; } //check file not big if ($_files[$file_field]["size"] > $max_size) { $out['error'][] = "file big"; } //if $check image set true if ($check_image) { if (!getimagesize($_files[$file_field]['tmp_name'])) { $out['error'][] = "uploaded file not valid image"; } } //create full filename including path if ($random_name) { // generate random filename $tmp = str_replace(array('.',' '), array('',''), microtime()); if (!$tmp || $tmp == '') { $out['error'][] = "file must have name"; } $newname = $tmp.'.'.$ext; } else { $newname = $name.'.'.$ext; } //check if file exists on server if (file_exists($path.$newname)) { $out['error'][] = "a file name exists"; } if (count($out['error'])>0) { //the file has not correctly validated return $out; } if (move_uploaded_file($_files[$file_field]['tmp_name'], $path.$newname)) { //success $out['filepath'] = $path; $out['filename'] = $newname; return $out; } else { $out['error'][] = "server error!"; } } else { $out['error'][] = "no file uploaded"; return $out; } } if (isset($_post['submit'])) { $file = uploadfile('file', true, true); if (is_array($file['error'])) { $message = ''; foreach ($file['error'] $msg) { $message .= '<p>'.$msg.'</p>'; } } else { $message = "file uploaded successfully".$newname; } echo $message; }
and form:
<form action="<?php echo $_server['php_self']; ?>" method="post" enctype="multipart/form-data" name="form1" id="form1"> <input name="file" type="file" id="imagee" /> <input name="submit" type="submit" value="upload" /> </form>
so, asking posting snippets of codes me (and else) make image upload script make super secure. or sharing/creating full script snippets added.
when start working on secure image upload script, there many things considder. i'm no near expert on this, i've been asked develop once in past. i'm gonna walk through entire process i've been through here can follow along. i'm gonna start basic html form , php script handles files.
html form:
<form name="upload" action="upload.php" method="post" enctype="multipart/form-data"> select image upload: <input type="file" name="image"> <input type="submit" name="upload" value="upload"> </form>
php file:
<?php $uploaddir = 'uploads/'; $uploadfile = $uploaddir . basename($_files['image']['name']); if (move_uploaded_file($_files['image']['tmp_name'], $uploadfile)) { echo "image succesfully uploaded."; } else { echo "image uploading failed."; } ?>
first problem: file types
attackers don't have use form on website upload files server. post requests can intercepted in number of ways. think browser addons, proxies, perl scripts. no matter how hard try, can't prevent attacker trying upload (s)he isn't supposed to. of our security has done serverside.
the first problem file types. in script above attacker upload (s)he wants, php script example, , follow direct link execute it. prevent this, implement content-type verification:
<?php if($_files['image']['type'] != "image/png") { echo "only png images allowed!"; exit; } $uploaddir = 'uploads/'; $uploadfile = $uploaddir . basename($_files['image']['name']); if (move_uploaded_file($_files['image']['tmp_name'], $uploadfile)) { echo "image succesfully uploaded."; } else { echo "image uploading failed."; } ?>
unfortunetely isn't enough. mentioned before, attacker has full control on request. nothing prevent him/her modifying request headers , change content type "image/png". instead of relying on content-type header, better validate content of uploaded file. here's php gd library comes in handy. using getimagesize()
, we'll processing image gd library. if isn't image, fail , therefor entire upload fail:
<?php $verifyimg = getimagesize($_files['image']['tmp_name']); if($verifyimg['mime'] != 'image/png') { echo "only png images allowed!"; exit; } $uploaddir = 'uploads/'; $uploadfile = $uploaddir . basename($_files['image']['name']); if (move_uploaded_file($_files['image']['tmp_name'], $uploadfile)) { echo "image succesfully uploaded."; } else { echo "image uploading failed."; } ?>
we're still not there yet though. image file types allow text comments added them. again, nothing prevents attacker adding php code comment. gd library evaluate valid image. php interpreter ignore image , run php code in comment. it's true depends on php configuration file extensions processed php interpreter , not, since there many developers out there have no control on configuration due use of vps, can't assume php interpreter won't process image. why adding file extension white list isn't safe enough either.
the solution store images in location attacker can't access file directly. outside of document root or in directory protected .htaccess file:
order deny,allow deny allow 127.0.0.1
edit: after talking other php programmers, highly suggest using folder outside of document root, because htaccess isn't reliable.
we still need user or other visitor able view image though. we'll use php retrieve image them:
<?php $uploaddir = 'uploads/'; $name = $_get['name']; // assuming file name in url example readfile($uploaddir.$name); ?>
second problem: local file inclusion attacks
although our script reasonably secure now, can't assume server doesn't suffer other vulnerabilities. common security vulnerability known local file inclusion. explain need add example code:
<?php if(isset($_cookie['lang'])) { $lang = $_cookie['lang']; } elseif (isset($_get['lang'])) { $lang = $_get['lang']; } else { $lang = 'english'; } include("language/$lang.php"); ?>
in example we're talking multi language website. sites language not considdered "high risk" information. try visitors prefered language through cookie or request , include required file based on it. considder happen when attacker enters following url:
www.example.com/index.php?lang=../uploads/my_evil_image.jpg
php include file uploaded attacker bypassing fact (s)he can't access file directly , we're @ square one.
the solution problem make sure user doesn't know filename on server. instead, we'll change file name , extension using database keep track of it:
create table `uploads` ( `id` int(11) not null auto_increment, `name` varchar(64) not null, `original_name` varchar(64) not null, `mime_type` varchar(20) not null, primary key (`id`) ) engine=innodb auto_increment=0 default charset=utf8;
<?php if(!empty($_post['upload']) && !empty($_files['image']) && $_files['image']['error'] == 0)) { $uploaddir = 'uploads/'; /* generates random filename , extension */ function tempnam_sfx($path, $suffix){ { $file = $path."/".mt_rand().$suffix; $fp = @fopen($file, 'x'); } while(!$fp); fclose($fp); return $file; } /* process image gd library */ $verifyimg = getimagesize($_files['image']['tmp_name']); /* make sure mime type image */ $pattern = "#^(image/)[^\s\n<]+$#i"; if(!preg_match($pattern, $verifyimg['mime']){ die("only image files allowed!"); } /* rename both image , extension */ $uploadfile = tempnam_sfx($uploaddir, ".tmp"); /* upload file secure directory new name , extension */ if (move_uploaded_file($_files['image']['tmp_name'], $uploadfile)) { /* setup database connection pdo */ $dbhost = "localhost"; $dbuser = ""; $dbpass = ""; $dbname = ""; // set dsn $dsn = 'mysql:host='.$dbhost.';dbname='.$dbname; // set options $options = array( pdo::attr_persistent => true, pdo::attr_errmode => pdo::errmode_exception ); try { $db = new pdo($dsn, $dbuser, $dbpass, $options); } catch(pdoexception $e){ die("error!: " . $e->getmessage()); } /* setup query */ $query = 'insert uploads (name, original_name, mime_type) values (:name, :oriname, :mime)'; /* prepare query */ $db->prepare($query); /* bind parameters */ $db->bindparam(':name', basename($uploadfile)); $db->bindparam(':oriname', basename($_files['image']['name'])); $db->bindparam(':mime', $_files['image']['type']); /* execute query */ try { $db->execute(); } catch(pdoexception $e){ // remove uploaded file unlink($uploadfile); die("error!: " . $e->getmessage()); } } else { die("image upload failed!"); } } ?>
so we've done following:
- we've created secure place save images
- we've processed image gd library
- we've checked image mime type
- we've renamed file name , changed extension
- we've saved both new , original filename in our database
- we've saved mime type in our database
we still need able display image visitors. use id column of our database this:
<?php $uploaddir = 'uploads/'; $id = 1; /* setup database connection pdo */ $dbhost = "localhost"; $dbuser = ""; $dbpass = ""; $dbname = ""; // set dsn $dsn = 'mysql:host='.$dbhost.';dbname='.$dbname; // set options $options = array( pdo::attr_persistent => true, pdo::attr_errmode => pdo::errmode_exception ); try { $db = new pdo($dsn, $dbuser, $dbpass, $options); } catch(pdoexception $e){ die("error!: " . $e->getmessage()); } /* setup query */ $query = 'select name, original_name, mime_type uploads id=:id'; /* prepare query */ $db->prepare($query); /* bind parameters */ $db->bindparam(':id', $id); /* execute query */ try { $db->execute(); $result = $db->fetch(pdo::fetch_assoc); } catch(pdoexception $e){ die("error!: " . $e->getmessage()); } /* original filename */ $newfile = $result['original_name']; /* send headers , file visitor */ header('content-description: file transfer'); header('content-disposition: attachment; filename='.basename($newfile)); header('expires: 0'); header('cache-control: must-revalidate'); header('pragma: public'); header('content-length: ' . filesize($uploaddir.$result['name'])); header("content-type: " . $result['mime_type']); readfile($uploaddir.$result['name']); ?>
thanks script visitor able view image or download original filename. however, (s)he can't access file on server direcly nor (s)he able fool server access file him/her (s)he has no way of knowing file is. (s)he can't brute force upload directory either doesn't allow access directory except server itself.
and concludes secure image upload script.
i'd add didn't include maximum file size script, should able yourself.
imageupload class
due high demand of script, i've writting imageupload class should make lot easier of securely handle images uploaded website visitors. class can handle both single , multiple files @ once, , provides additional features displaying, downloading , deleting images.
since code large post here, can download class mega here:
just read readme.txt , follow instructions.
Comments
Post a Comment