如何保护此图片上传代码

how to secure this image upload code

我在过去两周内遭到黑客攻击,我在日志文件中看到黑客可以在注册时上传 php 个 shell

这是让用户注册并将数据保存到站点数据库的表单,重点是当他上传他的头像时,他可以上传 php 文件

<form method="post" name="regiterationForm" id="regiterationForm" enctype="multipart/form-data">
<table width="930" border="0" align="center" cellpadding="2" cellspacing="2">

<tr>
<td width="431" valign="top"><table width="500" border="0" cellspacing="2" cellpadding="2">
<tr>
<td width="215" align="right" class="registerpage_form_credential">Username:</td>
<td colspan="3" class="registersignupbox_bg">
<input onBlur="checkUserName(this.value);" type="text" name="userName" id="userName" class="register_signup_field" /> </td>
<div class="errormsg" id="error_userName" style="display:none;"><span class="noberr"></span><span id="error_userName_val">Please enter name</div>
</tr>
<tr>
<td width="215" align="right" class="registerpage_form_credential">Password:</td>
<td colspan="3" class="registersignupbox_bg"><input type="password" name="userPassword" id="userPassword" class="register_signup_field" />&nbsp;</td>
<div class="errormsg errpass" id="error_userPassword" style="display:none;"><span class="noberr"></span><span id="error_userPassword_val">Please enter password</div>
</tr>
<tr>
<td width="215" align="right" class="registerpage_form_credential">Retype Password:</td>
<td colspan="3" class="registersignupbox_bg"><input type="password" name="userRetypePassword" id="userRetypePassword" class="register_signup_field" />&nbsp;</td>
<div class="errormsg errpass1" id="error_userRetypePassword" style="display:none;"><span class="noberr"></span><span id="error_userRetypePassword_val">Please enter password</div>
</tr>
<tr>
<td width="215" align="right" class="registerpage_form_credential">Your real name:</td>
<td colspan="3" class="registersignupbox_bg"><input type="text" name="userRealName" id="userRealName" class="register_signup_field" />&nbsp;</td>
<div class="errormsg errpass2" id="error_userRealName" style="display:none;"><span class="noberr"></span><span id="error_userRealName_val">Please enter Name</div>
</tr>
<tr>
<td align="right" class="registerpage_form_credential">E-mail address:</td>
<td colspan="3" class="registersignupbox_bg"><input onBlur="checkUserEmail(this.value);" type="text" name="userEmail" id="userEmail" class="register_signup_field" />
&nbsp;</td>
<div class="errormsg errpass3" id="error_userEmail" style="display:none;"><span class="noberr"></span><span id="error_userEmail_val">Please enter Email</div>
</tr>
<tr>
<td align="right" class="registerpage_form_credential">Retype E-mail address:</td>
<td colspan="3" class="registersignupbox_bg"><input type="text" name="userRetypeEmail" id="userRetypeEmail" class="register_signup_field" />
&nbsp;</td>
<div class="errormsg errpass4" id="error_userRetypeEmail" style="display:none;"><span class="noberr"></span><span id="error_userRetypeEmail_val">Please enter Email</div>
</tr>
<tr>
<td align="right" class="registerpage_form_credential">Country:</td>
<td colspan="3" class="registersignupbox_bg"><select name="country" id="country" class="register_monthday_selection">
<option value="">Choose a Country</option>
<?php for($i=0; $i<count($allCountryName); $i++){ ?>
    <option value="<?php echo $allCountryName[$i]['name']; ?>"><?php echo $allCountryName[$i]['name']; ?></option>
<?php } ?>
</select>
&nbsp;</td>
<div class="errormsg errpass5" id="error_country" style="display:none;"><span class="noberr"></span><span id="error_country_val">Please Select Country</div>
</tr>
<tr>
<td align="right" class="registerpage_form_credential">Date of birth:</td>

<td class="register_day_bg"><select name="userDay" id="userDay" class="register_day_selection">
<option value="">Day</option>
<?php for($i=1; $i<=31; $i++){ ?>
    <option value="<?php echo $i; ?>"><?php echo $i; ?></option>
<?php } ?>
</select></td>

<td class="register_month_bg"><select name="userMonth" id="userMonth" class="register_month_selection">
<option value="">Month</option>
<?php $month_digit_array = array("1"=>"January","2"=>"February","3"=>"March","4"=>"April","5"=>"May","6"=>"June","7"=>"July","8"=>"August","9"=>"September","10"=>"October","11"=>"November","12"=>"December"); 
    for($i=1; $i<=12; $i++){
?>
<option value="<?php echo $i; ?>"><?php echo $month_digit_array[$i]; ?></option>
<?php } ?>
</select></td>

<td class="register_year_bg"><select name="userYear" id="userYear" class="register_year_selection">
<option value="">Year</option>
<?php $curYear = date('Y');  for($i=$curYear; $i>=1930; $i--){ ?>
    <option value="<?php echo $i; ?>"><?php echo $i; ?></option>
<?php } ?>
</select></td>
</tr>

<tr>
<td align="right" class="registerpage_form_credential">I am a:</td>
<td colspan="3" class="registersignupbox_bg"><select name="gender" id="gender" class="register_monthday_selection">
<option value="">Select an option</option>
<option value="Male">Male</option>
<option value="Female">Female</option>

</select></td>
<div class="errormsg errpass6" id="error_gender" style="display:none;"><span class="noberr"></span><span id="error_gender_val">Please Select Gender</div>
</tr>
<tr>
<td align="right" class="registerpage_form_credential">Select an Avatar:</td>
<td colspan="3" class="registersignupbox_bg"><input type="file" id="image" name="userAvatar" /></td>
<div class="errormsg errpass7" id="error_image" style="display:none;"><span class="noberr"></span><span id="error_image_val">Please Select Image</div>
</tr>
<tr>
<td colspan="4" style="
    padding-left: 37%;
"><div class="g-recaptcha" data-sitekey="6LfhvAkTAAAAAFQyqmN2nf9hXEY1T0jF89SCGNVB"></div>
</td>
</tr>
</table></td>
<td width="455">

<p>

<iframe width="555" height="312" src="https://www.youtube.com/embed/vkjFm6ClTuw" frameborder="0" allowfullscreen></iframe>

</p>

<br />
<div style="width:468px ;margin: 0 auto; height:60px">

<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
<!-- Header/Footer Small -->
<ins class="adsbygoogle"
     style="display:inline-block;width:468px;height:60px"
     data-ad-client="ca-pub-5794587985510139"
     data-ad-slot="5064971004"></ins>
<script>
(adsbygoogle = window.adsbygoogle || []).push({});
</script>

</div>

</td>
</tr>
<tr>
<td colspan="2"><table width="709" border="0" align="center" cellpadding="2" cellspacing="2" style="margin-top:11px;    margin-top: 15px !important;
margin-right: 67px;" class="signp">
<tr>
<td>&nbsp;</td>
<td class="contactus_form_credentials">&nbsp;</td>
</tr>
<tr>
<td width="21" align="center"><label>
<input type="checkbox" name="promotion" id="promotion"/>
</label></td>
<td width="442" class="contactus_form_credentials">I agree that my account will be deleted if my email is not valid</td>
</tr>
<tr>
<td align="center"><input type="checkbox" name="checkbox2" id="checkbox2"/></td>
<td class="contactus_form_credentials">I have read and agree to the (example.com) <a href="terms.php" class="termsofuse">Terms of use</a></td>
</tr>
<tr>
<td>&nbsp;</td>
<td style="cursor:pointer;" onclick="registerFormValidation();"><img src="images/register_signup_btn.png" width="220" height="108" /></td>
</tr>
</table></td>
</tr>
</table>




<div class="errormsg errpass7" id="error_captcha" style="display:none;"><span class="noberr"></span><span id="error_captcha_val"></div>






</form> 

这是注册函数

function insertData($arr,$files){
    $username  =$arr['userName'];
    $pass      =$arr['userPassword'];
    $realname  =$arr['userRealName'];
    $email     =$arr['userEmail'];
    $country   =$arr['country'];
    $gender    =$arr['gender'];
    $dob       =$arr['userDay'].'-'.$arr['userMonth'].'-'.$arr['userYear'];
    if(self::userNameExit($username) != "not_exist"){
        echo "<script>document.location.href='".HTTP_PATH."register.php'</script>";
        exit();
    }
    if(self::userEmailExist($email) != "not_exist"){
        echo "<script>document.location.href='".HTTP_PATH."register.php'</script>";
        exit();
    }
    require_once(COMM_PATH."DatabaseManager.php"); 
    $db= new DatabaseManager();
    $emailcode =  rand().time();
    $sql       =  "Insert into users(username,pass,realname,email,country,dob,gender,lastlogin,register_date,email_varification,email_code)values('".$username."','".md5($pass)."','".$realname."','".$email."','".$country."','".$dob."','".$gender."', now(), now(),'n','".$emailcode."')";
    $result    =  $db->executeUpdate($sql);
    $user_id   =  $db->lastInsertId();
    self::autoFriends($user_id);
    self::sentMessage($user_id,$username);
    insertStatusNow($user_id,"user");
    if(isset($files['userAvatar']['name']) && $files['userAvatar']['name'] !=""){
        $nameOfImage    = time()."_".basename($files['userAvatar']['name']);
        $path           = USER_IMAGE_UPLOAD_PATH.$nameOfImage;
        if(move_uploaded_file($files['userAvatar']['tmp_name'], $path)){
            $sql="UPDATE `users` SET `image` = '".$nameOfImage."' WHERE `Id` =".$user_id;
            $db->executeUpdate($sql);
            if(isset($arr['promotion']) && $arr['promotion'] == "promotion_yes"){
                self::notificationSubmit($user_id,$email);
            }
        }
        return $user_id;
    }
}

我想知道我怎么了,网站被黑了,生意每天都在下降

更新::

    if(isset($files['userAvatar']['name']) && $files['userAvatar']['name'] !=""){
            $nameOfImage    = $files['userAvatar']['name'];
            $path           = USER_IMAGE_UPLOAD_PATH.$nameOfImage;


$fileInfo = new finfo();

$allowedMIMETypes = ['image/bmp', 'image/gif', 'image/jpeg', 'image/png']; //the most common image MIME types, you can add more if you want

if(in_array($fileType = $fileInfo->file($nameOfImage, FILEINFO_MIME_TYPE, $allowedMIMETypes))){
                if(move_uploaded_file($files['userAvatar']['tmp_name'], $path)){
                $sql="UPDATE `users` SET `image` = '".$nameOfImage."' WHERE `Id` =".$user_id;
                $db->executeUpdate($sql);
                if(isset($arr['promotion']) && $arr['promotion'] == "promotion_yes"){
                    self::notificationSubmit($user_id,$email);
                }
            }
            return $user_id;
} 

else {
    die("check you reg");
}
return null;

我已经制作了这样的代码,但它允许用户注册但没有照片

所以你想要的是人们只上传图片而不是 PHP 脚本。那么答案很简单,检查文件扩展名。但话虽这么说,还是有一些方法,比如注入 %00(不确定操作码)来欺骗 PHP 服务器。最好获得一个现有的图书馆来为您检查。一旦完成,为了不执行上传的脚本,不要给它执行权限。这样即使攻击者上传 php 文件,它也不会被您的服务器处理。如果你正确检查扩展,当攻击者假设上传 c99 shell 时,只会在上传路径中看到损坏的图像,而不是实际的 shell 页面,因为它正在通过图像网络服务器

使用它来检查文件的 MIME 类型:

$file = $files['userAvatar']['name'];
$fileInfo = new finfo();

$allowedMIMETypes = ['image/bmp', 'image/gif', 'image/jpeg', 'image/png']; //the most common image MIME types, you can add more if you want

if(in_array($fileType = $fileInfo->file($file, FILEINFO_MIME_TYPE, $allowedMIMETypes)){
    //the file is an image
} else {
    //file doesn't have a whitelisted MIME-type
}

更新: 如果你想阻止用户在没有头像的情况下注册,只需在将用户插入数据库之前输入我给你的代码即可。你的整个函数会变成这样:

function insertData($arr,$files){
    $username  =$arr['userName'];
    $pass      =$arr['userPassword'];
    $realname  =$arr['userRealName'];
    $email     =$arr['userEmail'];
    $country   =$arr['country'];
    $gender    =$arr['gender'];
    $dob       =$arr['userDay'].'-'.$arr['userMonth'].'-'.$arr['userYear'];
    if(self::userNameExit($username) != "not_exist"){
        echo "<script>document.location.href='".HTTP_PATH."register.php'</script>";
        exit();
    }
    if(self::userEmailExist($email) != "not_exist"){
        echo "<script>document.location.href='".HTTP_PATH."register.php'</script>";
        exit();
    }

    //*************
    if(!isset($files['userAvatar']['name']) || empty($files['userAvatar']['name'])){
        //no file was uploaded, give some kind of error message
    }

    $fileInfo = new finfo();
    $allowedMIMETypes = ['image/bmp', 'image/gif', 'image/jpeg', 'image/png']; //the most common image MIME types, you can add more if you want
    if(!in_array($fileType = $fileInfo->file($nameOfImage, FILEINFO_MIME_TYPE, $allowedMIMETypes))){
        //the uploaded file wasn't recognized as an image, give some kind of error message
    }
    *************//

    require_once(COMM_PATH."DatabaseManager.php"); 
    $db= new DatabaseManager();
    $emailcode =  rand().time();
    $sql       =  "Insert into users(username,pass,realname,email,country,dob,gender,lastlogin,register_date,email_varification,email_code)values('".$username."','".md5($pass)."','".$realname."','".$email."','".$country."','".$dob."','".$gender."', now(), now(),'n','".$emailcode."')";
    $result    =  $db->executeUpdate($sql);
    $user_id   =  $db->lastInsertId();
    self::autoFriends($user_id);
    self::sentMessage($user_id,$username);
    insertStatusNow($user_id,"user");
    $nameOfImage    = time()."_".basename($files['userAvatar']['name']);
    $path           = USER_IMAGE_UPLOAD_PATH.$nameOfImage;
    if(move_uploaded_file($files['userAvatar']['tmp_name'], $path)){
        $sql="UPDATE `users` SET `image` = '".$nameOfImage."' WHERE `Id` =".$user_id;
        $db->executeUpdate($sql);
        if(isset($arr['promotion']) && $arr['promotion'] == "promotion_yes"){
            self::notificationSubmit($user_id,$email);
        }
    }
    return $user_id;
}

此外,我看到您通过使用 JavaScrip 将用户重定向到注册页面来处理错误(甚至没有使用 PHP header() 函数)。这不是很优雅,用户不会知道他们做错了什么,所以我建议您 return 来自该函数的错误消息,然后在注册页面上回显