wordpress中文网站优化烟台平台公司
2026/4/17 10:34:28 网站建设 项目流程
wordpress中文网站优化,烟台平台公司,网站建设有几个文件夹,营销型网站推广第 5 章:文件操作风险管控——安全上传与文件管理 章节介绍 学习目标 通过本章学习,您将能够: 深刻理解文件上传功能中潜藏的多重安全风险(如 Webshell 上传、路径遍历等)掌握构建多层防御的文件上传安全校验流程学会安全地管理用户上传的文件,包括存储、访问和清理理解并防…第 5 章:文件操作风险管控——安全上传与文件管理章节介绍学习目标通过本章学习,您将能够:深刻理解文件上传功能中潜藏的多重安全风险(如 Webshell 上传、路径遍历等)掌握构建多层防御的文件上传安全校验流程学会安全地管理用户上传的文件,包括存储、访问和清理理解并防范文件系统操作中的目录遍历攻击能够在实际项目中实现一个完整的、符合安全标准的文件上传模块本章作用与定位在前四章中,我们学习了输入验证、SQL 注入防护、XSS 与 CSRF 防御等 Web 安全基础知识.本章将聚焦于另一个高频攻击面——文件操作.文件上传是 Web 应用中极为常见的功能,也是攻击者最青睐的突破口之一.一个未经严格校验的文件上传点,可能瞬间成为攻击者控制整个服务器的后门.本章将系统性地讲解文件上传漏洞的攻防原理,并指导您构建从客户端到服务器端的完整安全防御体系.与前面章节的衔接本章内容与**第 2 章(输入验证)紧密相关,文件上传本质上是特殊类型的用户输入.与第 4 章(XSS 防护)也有交叉,因为恶意文件可能包含脚本代码.同时,安全的文件管理也会用到第 7 章(加密)**中的部分知识.掌握本章内容后,您将对 Web 应用的攻击面有更全面的认识.本章主要内容概览文件上传漏洞深度剖析:分析攻击者如何通过文件上传获取服务器控制权安全校验多层防御:从客户端到服务器端的完整校验流程设计安全存储与访问控制:文件存储策略、权限设置与访问隔离文件系统操作安全:防范目录遍历攻击的最佳实践综合实战项目:构建一个带完整安全防护的图片上传相册系统核心概念讲解文件上传漏洞的本质与危害文件上传功能的安全风险源于一个简单的事实:服务器执行了用户可控的文件内容.攻击者通过精心构造,可以上传并执行恶意脚本,从而控制服务器.主要攻击方式Webshell 上传:上传包含 PHP、ASP、JSP 等服务器端脚本语言代码的文件,通过浏览器访问该文件即可在服务器上执行任意命令.钓鱼文件:上传伪装成正常文件的恶意程序(如.exe、.bat),诱骗其他用户下载执行.文件覆盖:通过目录遍历或文件名预测,覆盖服务器上的关键系统文件或应用配置文件.拒绝服务攻击:上传超大文件耗尽服务器磁盘空间或处理资源.客户端攻击:上传包含恶意脚本的 HTML/JS 文件,当其他用户访问时触发 XSS 攻击.漏洞利用条件要使文件上传漏洞被成功利用,通常需要满足以下条件之一:服务器配置不当,允许直接执行上传目录中的脚本文件应用程序未对文件内容进行有效校验,仅检查扩展名存在文件解析漏洞(如 Apache 的mod_mime解析缺陷)能够结合其他漏洞(如目录遍历)将文件上传到可执行目录安全的文件校验流程(纵深防御)单一防御措施极易被绕过,应采用多层防御策略:客户端校验 → 服务器端扩展名白名单 → MIME类型检查 → 文件头校验 → 内容安全检查 → 随机重命名 → 安全存储1. 客户端校验(辅助层)作用:提供即时反馈,提升用户体验,减少无效请求限制:完全不可信,可被轻易绕过实现:HTML5 的accept属性、JavaScript 文件类型/大小校验2. 服务器端扩展名白名单(基础层)原则:只允许已知安全的扩展名,拒绝其他所有实现:维护一个小型的、明确允许的扩展名列表(如.jpg,.png,.gif)注意:不要使用黑名单攻击者总能找到不在名单中的危险扩展名3. MIME 类型检查(增强层)原理:检查 HTTP 请求头中的Content-Type信息限制:可被篡改,不能单独依赖实现:通过$_FILES[file][type]获取,但需结合其他校验4. 文件头校验(内容层)原理:检查文件的实际二进制内容开头部分(魔术字节)优势:难以伪造,是判断文件真实类型的可靠方法实现:使用getimagesize()检查图片,或直接读取文件头字节5. 内容安全检查(深度层)目的:防止图片马(在正常图片中嵌入恶意代码)方法:对图片进行二次渲染、使用防病毒软件扫描、检查文件内容是否包含 PHP 标签等6. 随机重命名(隔离层)目的:防止攻击者预测文件路径,避免文件覆盖攻击方法:使用不可预测的随机字符串(如 UUID)作为文件名,保留原始扩展名7. 安全存储(物理层)原则:上传文件存储在 Web 根目录之外,或通过脚本代理访问配置:正确设置文件系统权限(最小权限原则)目录遍历攻击(Path Traversal)目录遍历攻击通过使用../等路径遍历序列,访问或操作应用程序预期目录之外的文件.攻击示例文件下载功能:download.php?file../../../../etc/passwd文件上传功能:通过文件名../../../var/www/html/shell.php将文件上传到可执行目录文件包含功能:include($_GET[page] . .php),传入../../../etc/passwd%00防护方法规范化路径:使用realpath()获取绝对路径,并与允许的基准目录比较白名单过滤:只允许文件名,不允许路径剥离目录遍历序列:过滤掉../、..\等序列使用索引存储:将文件存储在数据库中,通过 ID 引用而非直接路径代码示例示例 1:存在严重漏洞的文件上传代码(反面教材)这是一个典型的、存在多个安全漏洞的文件上传实现:?php// 存在严重安全漏洞的文件上传代码 - 请勿在生产环境使用// 1. 没有任何输入验证if(isset($_FILES[uploaded_file])){$file$_FILES[uploaded_file];// 2. 直接使用用户提供的文件名 - 存在路径遍历风险$target_pathuploads/.$file[name];// 3. 没有任何文件类型校验if(move_uploaded_file($file[tmp_name],$target_path)){echo文件上传成功: a href$target_path查看文件/a;}else{echo文件上传失败;}}?攻击演示:上传名为shell.php的文件,内容为?php system($_GET[cmd]); ?访问http:// example.com/uploads/shell.php?cmdwhoami即可执行系统命令上传名为../../../var/www/html/shell.php的文件,可能将 Webshell 写入 Web 根目录示例 2:基础安全防护的文件上传代码以下是添加了基础安全防护的上传代码:?php// 基础安全防护的文件上传示例// 定义允许的文件类型白名单$allowed_extensions[jpg,jpeg,png,gif,pdf];$max_file_size2*1024*1024;// 2MB// 检查是否有文件上传if(!isset($_FILES[uploaded_file])||$_FILES[uploaded_file][error]!UPLOAD_ERR_OK){die(文件上传失败或未选择文件);}$file$_FILES[uploaded_file];// 1. 检查文件大小if($file[size]$max_file_size){die(文件大小超过限制(最大2MB));}// 2. 获取文件扩展名并进行白名单校验$file_name$file[name];$file_extstrtolower(pathinfo($file_name,PATHINFO_EXTENSION));if(!in_array($file_ext,$allowed_extensions)){die(不支持的文件类型,仅允许: .implode(, ,$allowed_extensions));}// 3. 生成安全的随机文件名(防止文件覆盖和路径遍历)$safe_file_nameuniqid(file_,true)...$file_ext;$upload_diruploads/;$target_path$upload_dir.$safe_file_name;// 4. 确保目标目录存在if(!is_dir($upload_dir)){mkdir($upload_dir,0755,true);}// 5. 移动上传的文件if(move_uploaded_file($file[tmp_name],$target_path)){// 6. 设置安全权限(仅所有者可读写,其他人只读)chmod($target_path,0644);echo文件上传成功br;echo保存为: .htmlspecialchars($safe_file_name).br;// 仅对图片文件显示预览if(in_array($file_ext,[jpg,jpeg,png,gif])){echoimg src$target_path stylemax-width: 300px; alt上传的图片;}}else{die(文件保存失败);}?示例 3:包含文件头校验的增强版上传代码?php// 包含文件头校验的增强安全上传classSecureFileUploader{private$allowed_extensions[jpg,jpeg,png,gif];private$allowed_mime_types[image/jpeg,image/png,image/gif];private$max_file_size2*1024*1024;// 2MBprivate$upload_dirsecure_uploads/;// 图片文件的魔术字节签名private$image_signatures[jpg\xFF\xD8\xFF,png\x89\x50\x4E\x47,gifGIF89a];publicfunctionupload($file_field_name){// 验证上传状态if(!isset($_FILES[$file_field_name])){thrownewException(没有文件被上传);}$file$_FILES[$file_field_name];// 检查上传错误码if($file[error]!UPLOAD_ERR_OK){$error_messages[UPLOAD_ERR_INI_SIZE文件大小超过服务器限制,UPLOAD_ERR_FORM_SIZE文件大小超过表单限制,UPLOAD_ERR_PARTIAL文件只有部分被上传,UPLOAD_ERR_NO_FILE没有文件被上传,UPLOAD_ERR_NO_TMP_DIR缺少临时文件夹,UPLOAD_ERR_CANT_WRITE文件写入失败,UPLOAD_ERR_EXTENSIONPHP扩展阻止了文件上传];thrownewException($error_messages[$file[error]]??未知上传错误);}// 1. 文件大小校验if($file[size]$this-max_file_size){thrownewException(文件大小不能超过 .($this-max_file_size/1024/1024).MB);}// 2. 扩展名白名单校验$original_namebasename($file[name]);// 使用basename防止路径遍历$file_extstrtolower(pathinfo($original_name,PATHINFO_EXTENSION));if(!in_array($file_ext,$this-allowed_extensions)){thrownewException(不允许的文件类型.仅支持: .implode(, ,$this-allowed_extensions));}// 3. MIME类型校验(不可单独依赖)$detected_mimemime_content_type($file[tmp_name]);if(!in_array($detected_mime,$this-allowed_mime_types)){thrownewException(检测到非法的MIME类型: .$detected_mime);}// 4. 文件头(魔术字节)校验if(!$this-validateFileSignature($file[tmp_name],$file_ext)){thrownewException(文件内容与扩展名不匹配,可能被篡改);}// 5. 图片文件二次校验if(in_array($file_ext,[jpg,jpeg,png,gif])){if(!$this-validateImageFile($file[tmp_name])){thrownewException(图片文件损坏或包含恶意内容);}}// 6. 生成安全的随机文件名$safe_filename$this-generateSafeFilename($file_ext);$target_path$this-upload_dir.$safe_filename;// 7. 确保上传目录存在且安全$this-ensureSecureUploadDir();// 8. 移动文件并设置权限if(!move_uploaded_file($file[tmp_name],$target_path)){thrownewException(文件保存失败);}// 9. 设置安全文件权限chmod($target_path,0644);return[original_name$original_name,saved_name$safe_filename,file_path$target_path,file_size$file[size],file_type$detected_mime];}/** * 验证文件魔术字节签名 */privatefunctionvalidateFileSignature($tmp_file_path,$expected_ext){if(!file_exists($tmp_file_path)){returnfalse;}$handlefopen($tmp_file_path,rb);if(!$handle){returnfalse;}// 根据扩展名检查对应的魔术字节$signature_lengthstrlen($this-image_signatures[$expected_ext]??);$file_signaturefread($handle,$signature_length);fclose($handle);return$file_signature($this-image_signatures[$expected_ext]??);}/** * 验证图片文件 */privatefunctionvalidateImageFile($tmp_file_path){// 使用getimagesize验证图片有效性$image_infogetimagesize($tmp_file_path);if($image_infofalse){returnfalse;}// 检查图片是否包含PHP标签(图片马检测简化版)$file_contentfile_get_contents($tmp_file_path);if(strpos($file_content,?php)!false||strpos($file_content,?)!false){returnfalse;}returntrue;}/** * 生成安全的随机文件名 */privatefunctiongenerateSafeFilename($extension){// 使用更安全的随机生成方式$random_bytesrandom_bytes(16);$safe_namebin2hex($random_bytes)...$extension;return$safe_name;}/** * 确保上传目录安全 */privatefunctionensureSecureUploadDir(){if(!is_dir($this-upload_dir)){mkdir($this-upload_dir,0755,true);}// 在目录中放置.htaccess文件防止直接执行PHP(Apache服务器)$htaccess_contentHTACCESS# 防止直接执行PHP文件 Files *.php Order Deny,Allow Deny from all /Files # 防止目录列表 Options -Indexes # 设置文件缓存头(针对图片) FilesMatch \.(jpg|jpeg|png|gif)$ Header set Cache-Control max-age604800, public /FilesMatchHTACCESS;$htaccess_path$this-upload_dir..htaccess;if(!file_exists($htaccess_path)){file_put_contents($htaccess_path,$htaccess_content);}// 放置一个空白的index.html防止目录遍历$index_path$this-upload_dir.index.html;if(!file_exists($index_path)){file_put_contents($index_path,htmlbody!-- 目录访问被阻止 --/body/html);}}}// 使用示例try{$uploadernewSecureFileUploader();$result$uploader-upload(userfile);echo文件上传成功br;echo原始文件名: .htmlspecialchars($result[original_name]).br;echo保存文件名: .htmlspecialchars($result[saved_name]).br;echo文件大小: .round($result[file_size]/1024,2). KBbr;// 显示图片预览if(strpos($result[file_type],image/)0){echoimg src{$result[file_path]} stylemax-width: 400px; border: 1px solid #ddd;;}}catch(Exception$e){echo上传失败: .htmlspecialchars($e-getMessage());}?示例 4:防止目录遍历攻击的文件下载代码?php// 安全的文件下载实现 - 防止目录遍历攻击classSecureFileDownload{private$base_dir;// 允许访问的基准目录private$allowed_extensions[pdf,txt,jpg,png,docx];publicfunction__construct($base_directory){// 规范化基准目录路径$this-base_dirrealpath($base_directory);if($this-base_dirfalse){thrownewException(基准目录不存在: .$base_directory);}}/** * 安全地提供文件下载 */publicfunctiondownloadFile($requested_file){// 1. 只允许文件名,不允许路径$file_namebasename($requested_file);// 2. 验证扩展名$file_extstrtolower(pathinfo($file_name,PATHINFO_EXTENSION));if(!in_array($file_ext,$this-allowed_extensions)){http_response_code(403);die(不允许的文件类型);}// 3. 构建完整路径$file_path$this-base_dir.DIRECTORY_SEPARATOR.$file_name;// 4. 规范化并验证路径(防止目录遍历)$real_pathrealpath($file_path);if($real_pathfalse||strpos($real_path,$this-base_dir)!0){// 文件不存在或路径不在基准目录内http_response_code(404);die(文件不存在);}// 5. 验证确实是文件(不是目录)if(!is_file($real_path)){http_response_code(403);die(拒绝访问);}// 6. 设置下载头header(Content-Description: File Transfer);header(Content-Type: application/octet-stream);header(Content-Disposition: attachment; filename.rawurlencode($file_name).);header(Content-Transfer-Encoding: binary);header(Expires: 0);header(Cache-Control: must-revalidate);header(Pragma: public);header(Content-Length: .filesize($real_path));// 7. 清空输出缓冲区并发送文件ob_clean();flush();readfile($real_path);exit;}/** * 安全的文件查看(仅限图片) */publicfunctionviewImage($requested_file){$file_namebasename($requested_file);$allowed_image_ext[jpg,jpeg,png,gif,webp];$file_extstrtolower(pathinfo($file_name,PATHINFO_EXTENSION));if(!in_array($file_ext,$allowed_image_ext)){http_response_code(403);die(只允许查看图片文件);}$file_path$this-base_dir.DIRECTORY_SEPARATOR.$file_name;$real_pathrealpath($file_path);if($real_pathfalse||strpos($real_path,$this-base_dir)!0){http_response_code(404);die(图片不存在);}if(!is_file($real_path)){http_response_code(403);die(拒绝访问);}// 根据扩展名设置正确的Content-Type$mime_types[jpgimage/jpeg,jpegimage/jpeg,pngimage/png,gifimage/gif,webpimage/webp];header(Content-Type: .($mime_types[$file_ext]??image/jpeg));header(Content-Length: .filesize($real_path));readfile($real_path);exit;}}// 使用示例try{// 假设我们的文件存储在files/目录下$downloadernewSecureFileDownload(__DIR__./files);// 从URL参数获取请求的文件名$requested_file$_GET[file]??;if(empty($requested_file)){die(请指定要下载的文件名);}// 根据参数决定是下载还是查看$action$_GET[action]??download;if($actionview){$downloader-viewImage($requested_file);}else{$downloader-downloadFile($requested_file);}}catch(Exception$e){http_response_code(500);echo错误: .htmlspecialchars($e-getMessage());}?示例 5:文件上传的 HTML 表单与客户端校验!DOCTYPEhtmlhtmllangzh-CNheadmetacharsetUTF-8/metanameviewportcontentwidthdevice-width, initial-scale1.0/title安全的文件上传表单/titlestyle.upload-container{max-width:500px;margin:50px auto;padding:20px;border:1px solid #ddd;border-radius:5px;background-color:#f9f9f9;}.form-group{margin-bottom:15px;}label{display:block;margin-bottom:5px;font-weight:bold;}input[typefile]{width:100%;padding:8px;border:1px solid #ddd;border-radius:3px;}button{background-color:#4caf50;color:white;padding:10px 20px;border:none;border-radius:3px;cursor:pointer;font-size:16px;}button:hover{background-color:#45a049;}.error{color:#d9534f;margin-top:5px;font-size:14px;}.progress-container{display:none;margin-top:15px;}.progress-bar{width:100%;height:20px;background-color:#f0f0f0;border-radius:3px;overflow:hidden;}.progress-fill{height:100%;background-color:#4caf50;width:0%;transition:width 0.3s;}/style/headbodydivclassupload-containerh2安全文件上传演示/h2p仅允许上传JPG、PNG、GIF格式的图片文件,大小不超过2MB./pformiduploadFormactionsecure_upload.phpmethodPOSTenctypemultipart/form-datadivclassform-grouplabelforuserfile选择文件:/labelinputtypefilenameuserfileiduserfileaccept.jpg,.jpeg,.png,.gifrequired/dividfileErrorclasserror/div/divdivclassform-grouplabelfordescription文件描述(可选):/labelinputtypetextnamedescriptioniddescriptionmaxlength100//divdivclassprogress-containeridprogressContainerdivclassprogress-bardivclassprogress-fillidprogressFill/div/divdividprogressText上传中: 0%/div/divbuttontypesubmitiduploadButton上传文件/button/formdividresultstylemargin-top:20px;/div/divscriptdocument.addEventListener(DOMContentLoaded,function(){constformdocument.getElementById(uploadForm);constfileInputdocument.getElementById(userfile);constfileErrordocument.getElementById(fileError);constuploadButtondocument.getElementById(uploadButton);constprogressContainerdocument.getElementById(progressContainer);constprogressFilldocument.getElementById(progressFill);constprogressTextdocument.getElementById(progressText);constresultDivdocument.getElementById(result);// 最大文件大小(2MB)constMAX_FILE_SIZE2*1024*1024;// 允许的文件类型constALLOWED_TYPES[image/jpeg,image/png,image/gif];// 客户端文件验证fileInput.addEventListener(change,function(){constfilethis.files[0];fileError.textContent;if(!file){return;}// 1. 验证文件大小if(file.sizeMAX_FILE_SIZE){fileError.textContent文件大小不能超过2MB;this.value;// 清空文件选择return;}// 2. 验证文件类型if(!ALLOWED_TYPES.includes(file.type)){fileError.textContent只允许JPG、PNG、GIF格式的图片文件;this.value;return;}// 3. 验证扩展名(双重检查)constfileNamefile.name.toLowerCase();constvalidExtensions[.jpg,.jpeg,.png,.gif];consthasValidExtensionvalidExtensions.some((ext)fileName.endsWith(ext));if(!hasValidExtension){fileError.textContent文件扩展名不被允许;this.value;return;}// 验证通过,可以显示文件信息console.log(文件验证通过:,{name:file.name,size:(file.size/1024/1024).toFixed(2) MB,type:file.type,});});// AJAX文件上传(可选增强功能)form.addEventListener(submit,function(e){e.preventDefault();constfilefileInput.files[0];if(!file){fileError.textContent请选择文件;return;}// 禁用上传按钮,防止重复提交uploadButton.disabledtrue;uploadButton.textContent上传中...;// 显示进度条progressContainer.style.displayblock;// 使用FormData对象constformDatanewFormData(this);// 使用XMLHttpRequest以便获取上传进度constxhrnewXMLHttpRequest();// 上传进度事件xhr.upload.addEventListener(progress,function(e){if(e.lengthComputable){constpercentCompleteMath.round((e.loaded/e.total)*100);progressFill.style.widthpercentComplete%;progressText.textContent上传中: percentComplete%;}});// 请求完成xhr.addEventListener(load,function(){progressContainer.style.displaynone;uploadButton.disabledfalse;uploadButton.textContent上传文件;if(xhr.status200){try{constresponseJSON.parse(xhr.responseText);if(response.success){resultDiv.innerHTMLdiv stylecolor: green; padding: 10px; background-color: #dff0d8; border: 1px solid #d6e9c6; border-radius: 3px;strong上传成功!/strongbr文件名: response.filenamebr文件大小: response.size KBbr(response.preview?img srcresponse.preview stylemax-width: 100%; margin-top: 10px;:)/div;// 重置表单form.reset();}else{resultDiv.innerHTMLdiv stylecolor: #d9534f; padding: 10px; background-color: #f2dede; border: 1px solid #ebccd1; border-radius: 3px;strong上传失败:/strong response.message/div;}}catch(e){resultDiv.innerHTMLdiv stylecolor: #d9534f; padding: 10px; background-color: #f2dede; border: 1px solid #ebccd1; border-radius: 3px;strong服务器响应解析失败/strong/div;}}else{resultDiv.innerHTMLdiv stylecolor: #d9534f; padding: 10px; background-color: #f2dede; border: 1px solid #ebccd1; border-radius: 3px;strong服务器错误:/strong HTTP xhr.status/div;}});// 请求错误xhr.addEventListener(error,function(){progressContainer.style.displaynone;uploadButton.disabledfalse;uploadButton.textContent上传文件;resultDiv.innerHTMLdiv stylecolor: #d9534f; padding: 10px; background-color: #f2dede; border: 1px solid #ebccd1; border-radius: 3px;strong网络错误,请稍后重试/strong/div;});// 发送请求xhr.open(POST,form.action);xhr.send(formData);});// 传统表单提交方式(备用)consttraditionalSubmitBtndocument.createElement(button);traditionalSubmitBtn.typesubmit;traditionalSubmitBtn.textContent传统方式上传;traditionalSubmitBtn.style.marginLeft10px;traditionalSubmitBtn.style.backgroundColor#337ab7;traditionalSubmitBtn.addEventListener(click,function(e){// 允许表单默认提交行为form.removeEventListener(submit,arguments.callee);});// 将传统提交按钮添加到表单中form.appendChild(traditionalSubmitBtn);});/script/body/html预期输出:当用户选择文件后,客户端 JavaScript 会立即验证文件大小和类型.如果选择了一个 3MB 的文件,会立即显示文件大小不能超过 2MB的错误信息.如果选择了一个 PDF 文件,会显示只允许 JPG、PNG、GIF 格式的图片文件.只有通过验证的文件才能被提交.实战项目:构建安全图片相册系统项目需求分析我们将构建一个完整的图片相册系统,包含以下功能:用户认证系统:用户注册、登录、会话管理安全图片上传:实现多层安全校验的图片上传功能图片管理:查看、删除用户自己的图片相册分享:生成安全的分享链接管理员功能:管理所有用户和图片(可选)技术方案前端:HTML5 CSS3 JavaScript(客户端校验)后端:PHP 7.4(服务器端处理)数据库:MySQL(存储用户信息和图片元数据)文件存储:本地文件系统(存储上传的图片)安全措施:图片文件多层校验防止 SQL 注入(使用 PDO 预处理语句)防止 XSS 攻击(输出转义)防止 CSRF 攻击(Token 验证)安全的会话管理项目结构secure_gallery/ ├── index.php # 首页 ├── login.php # 登录页面 ├── register.php # 注册页面 ├── logout.php # 退出登录 ├── upload.php # 文件上传处理 ├── gallery.php # 个人相册 ├── share.php # 分享查看 ├── admin/ # 管理后台 │ ├── index.php │ ├── users.php │ └── images.php ├── includes/ # 包含文件 │ ├── config.php # 配置文件 │ ├── database.php # 数据库连接 │ ├── auth.php # 认证函数 │ ├── uploader.php # 文件上传类 │ └── functions.php # 通用函数 ├── uploads/ # 上传文件存储目录 │ ├── images/ # 图片文件(Web根目录外) │ └── thumbs/ # 缩略图 └── assets/ # 静态资源 ├── css/ ├── js/ └── images/分步骤实现步骤 1:数据库设计与初始化-- 创建数据库CREATEDATABASEIFNOTEXISTSsecure_galleryCHARACTERSETutf8mb4COLLATEutf8mb4_unicode_ci;USEsecure_gallery;-- 用户表CREATETABLEusers(idINTPRIMARYKEYAUTO_INCREMENT,usernameVARCHAR(50)UNIQUENOTNULL,emailVARCHAR(100)UNIQUENOTNULL,password_hashVARCHAR(255)NOTNULL,full_nameVARCHAR(100),roleENUM(user,admin)DEFAULTuser,statusENUM(active,inactive,suspended)DEFAULTactive,created_atTIMESTAMPDEFAULTCURRENT_TIMESTAMP,updated_atTIMESTAMPDEFAULTCURRENT_TIMESTAMPONUPDATECURRENT_TIMESTAMP,last_loginTIMESTAMPNULL,INDEXidx_username(username),INDEXidx_email(email));-- 图片表CREATETABLEimages(idINTPRIMARYKEYAUTO_INCREMENT,user_idINTNOTNULL,original_nameVARCHAR(255)NOTNULL,stored_nameVARCHAR(255)UNIQUENOTNULL,file_pathVARCHAR(500)NOTNULL,file_sizeINTNOTNULL,mime_typeVARCHAR(50)NOTNULL,widthINT,heightINT,titleVARCHAR(200),descriptionTEXT,is_publicBOOLEANDEFAULTFALSE,share_tokenCHAR(32)UNIQUE,view_countINTDEFAULT0,uploaded_atTIMESTAMPDEFAULTCURRENT_TIMESTAMP,FOREIGNKEY(user_id)REFERENCESusers(id)ONDELETECASCADE,INDEXidx_user_id(user_id),INDEXidx_share_token(share_token),INDEXidx_uploaded_at(uploaded_at));-- 创建管理员用户(密码:Admin123)INSERTINTOusers(username,email,password_hash,full_name,role)VALUES(admin,adminexample.com,$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi,系统管理员,admin);-- 创建测试用户(密码:Test123)INSERTINTOusers(username,email,password_hash,full_name)VALUES(testuser,userexample.com,$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi,测试用户);步骤 2:配置文件与数据库连接?php// includes/config.php// 安全图片相册系统 - 配置文件// 错误报告设置(开发环境)error_reporting(E_ALL);ini_set(display_errors,1);// 生产环境应设置为:// error_reporting(0);// ini_set(display_errors, 0);// ini_set(log_errors, 1);// ini_set(error_log, /path/to/php-error.log);// 时区设置date_default_timezone_set(Asia/Shanghai);// 会话安全设置ini_set(session.cookie_httponly,1);ini_set(session.cookie_secure,1);// 仅HTTPS启用ini_set(session.use_only_cookies,1);ini_set(session.cookie_samesite,Strict);// 应用配置define(APP_NAME,安全图片相册);define(APP_VERSION,1.0.0);define(BASE_URL,http:// localhost/secure_gallery);// 根据实际修改// 数据库配置define(DB_HOST,localhost);define(DB_NAME,secure_gallery);define(DB_USER,root);// 根据实际修改define(DB_PASS,);// 根据实际修改define(DB_CHARSET,utf8mb4);// 文件上传配置define(UPLOAD_MAX_SIZE,5*1024*1024);// 5MBdefine(ALLOWED_IMAGE_TYPES,[image/jpeg,image/png,image/gif,image/webp]);define(ALLOWED_EXTENSIONS,[jpg,jpeg,png,gif,webp]);// 文件存储路径(建议放在Web根目录外)define(UPLOAD_BASE_DIR,dirname(__DIR__)./private_uploads/);define(THUMBNAIL_DIR,thumbs/);// 安全配置define(CSRF_TOKEN_NAME,csrf_token);define(SESSION_TIMEOUT,1800);// 30分钟// 管理员邮箱define(ADMIN_EMAIL,adminexample.com);// 自动加载类spl_autoload_register(function($class_name){$file__DIR__./../classes/.$class_name..php;if(file_exists($file)){require_once$file;}});// 启动会话(放在配置加载后)if(session_status()PHP_SESSION_NONE){session_start();// 会话固定防护:定期更新会话IDif(!isset($_SESSION[created])){$_SESSION[created]time();}elseif(time()-$_SESSION[created]600){// 每10分钟更新一次session_regenerate_id(true);$_SESSION[created]time();}}// 设置CSRF Token(如果不存在)if(empty($_SESSION[CSRF_TOKEN_NAME])){$_SESSION[CSRF_TOKEN_NAME]bin2hex(random_bytes(32));}// 通用函数:生成CSRF Token字段functioncsrf_field(){returninput typehidden name.CSRF_TOKEN_NAME. value.$_SESSION[CSRF_TOKEN_NAME].;}// 通用函数:验证CSRF Tokenfunctionvalidate_csrf_token($token){returnisset($_SESSION[CSRF_TOKEN_NAME])hash_equals($_SESSION[CSRF_TOKEN_NAME],$token);}// 通用函数:安全的跳转functionredirect($url,$permanentfalse){if($permanent){header(HTTP/1.1 301 Moved Permanently);}header(Location: .$url);exit();}// 通用函数:安全的输出functionescape($string){returnhtmlspecialchars($string,ENT_QUOTES,UTF-8);}??php// includes/database.php// 数据库连接类(使用PDO,防止SQL注入)classDatabase{privatestatic$instancenull;private$connection;privatefunction__construct(){try{$dsnmysql:host.DB_HOST.;dbname.DB_NAME.;charset.DB_CHARSET;$options[PDO::ATTR_ERRMODEPDO::ERRMODE_EXCEPTION,PDO::ATTR_DEFAULT_FETCH_MODEPDO::FETCH_ASSOC,PDO::ATTR_EMULATE_PREPARESfalse,// 禁用预处理模拟,提高安全性PDO::ATTR_STRINGIFY_FETCHESfalse];$this-connectionnewPDO($dsn,DB_USER,DB_PASS,$options);}catch(PDOException$e){// 生产环境应记录到日志文件,而不是直接显示error_log(数据库连接失败: .$e-getMessage());die(数据库连接失败,请稍后重试);}}publicstaticfunctiongetInstance(){if(self::$instancenull){self::$instancenewDatabase();}returnself::$instance;}publicfunctiongetConnection(){return$this-connection;}/** * 安全的查询执行(预处理语句) */publicfunctionquery($sql,$params[]){try{$stmt$this-connection-prepare($sql);$stmt-execute($params);return$stmt;}catch(PDOException$e){error_log(数据库查询错误: .$e-getMessage(). SQL: .$sql);thrownewException(数据库操作失败);}}/** * 获取单行结果 */publicfunctionfetch($sql,$params[]){$stmt$this-query($sql,$params);return$stmt-fetch();}/** * 获取所有结果 */publicfunctionfetchAll($sql,$params[]){$stmt$this-query($sql,$params);return$stmt-fetchAll();}/** * 插入数据并返回最后插入的ID */publicfunctioninsert($sql,$params[]){$this-query($sql,$params);return$this-connection-lastInsertId();}/** * 开始事务 */publicfunctionbeginTransaction(){return$this-connection-beginTransaction();}/** * 提交事务 */publicfunctioncommit(){return$this-connection-commit();}/** * 回滚事务 */publicfunctionrollBack(){return$this-connection-rollBack();}}?步骤 3:增强版安全文件上传类?php// includes/uploader.php// 安全文件上传类 - 完整的多层防御实现classSecureUploader{private$db;private$allowed_mime_types;private$allowed_extensions;private$max_file_size;private$upload_base_dir;publicfunction__construct(){$this-dbDatabase::getInstance()-getConnection();$this-allowed_mime_typesALLOWED_IMAGE_TYPES;$this-allowed_extensionsALLOWED_EXTENSIONS;$this-max_file_sizeUPLOAD_MAX_SIZE;$this-upload_base_dirUPLOAD_BASE_DIR;// 确保上传目录存在且安全$this-ensureSecureDirectories();}/** * 处理文件上传 */publicfunctionupload($file_field,$user_id,$title,$description,$is_publicfalse){// 验证上传状态if(!isset($_FILES[$file_field])||$_FILES[$file_field][error]!UPLOAD_ERR_OK){thrownewException($this-getUploadErrorMessage($_FILES[$file_field][error]??UPLOAD_ERR_NO_FILE));}$file$_FILES[$file_field];// 1. 文件大小校验if($file[size]$this-max_file_size){thrownewException(文件大小不能超过 .($this-max_file_size/1024/1024).MB);}// 2. 扩展名白名单校验$original_namebasename($file[name]);$file_extstrtolower(pathinfo($original_name,PATHINFO_EXTENSION));if(!in_array($file_ext,$this-allowed_extensions)){thrownewException(不支持的文件类型.仅允许: .implode(, ,$this-allowed_extensions));}// 3. MIME类型校验$detected_mimemime_content_type($file[tmp_name]);if(!in_array($detected_mime,$this-allowed_mime_types)){thrownewException(检测到非法的MIME类型: .$detected_mime);}// 4. 文件头(魔术字节)校验if(!$this-validateFileSignature($file[tmp_name],$file_ext)){thrownewException(文件内容与扩展名不匹配,可能被篡改);}// 5. 图片文件深度校验if(!$this-validateImageFile($file[tmp_name])){thrownewException(图片文件损坏或包含恶意内容);}// 6. 检查图片中是否包含Webshell代码(简化版)if($this-containsMaliciousContent($file[tmp_name])){thrownewException(检测到潜在的安全威胁,文件被拒绝);}// 7. 生成安全的随机文件名$stored_name$this-generateSafeFilename($file_ext);$file_path$this-upload_base_dir.images/.$stored_name;// 8. 移动文件if(!move_uploaded_file($file[tmp_name],$file_path)){thrownewException(文件保存失败,请检查目录权限);}// 9. 设置安全权限chmod($file_path,0644);// 10. 获取图片尺寸$image_infogetimagesize($file_path);$width$image_info[0]??0;$height$image_info[1]??0;// 11. 生成缩略图$thumbnail_path$this-generateThumbnail($file_path,$stored_name,$width,$height);// 12. 生成分享令牌(如果图片是公开的)$share_token$is_public?$this-generateShareToken():null;// 13. 保存到数据库$image_id$this-saveImageToDatabase($user_id,$original_name,$stored_name,$file_path,$file[size],$detected_mime,$width,$height,$title,$description,$is_public,$share_token);// 14. 记录安全日志$this-logSecurityEvent(file_upload,[user_id$user_id,image_id$image_id,original_name$original_name,file_size$file[size],ip_address$_SERVER[REMOTE_ADDR]??unknown]);return[id$image_id,original_name$original_name,stored_name$stored_name,file_path$file_path,thumbnail_path$thumbnail_path,share_token$share_token,width$width,height$height];}/** * 验证文件魔术字节签名 */privatefunctionvalidateFileSignature($tmp_file_path,$expected_ext){$signatures[jpg\xFF\xD8\xFF,jpeg\xFF\xD8\xFF,png\x89\x50\x4E\x47\x0D\x0A\x1A\x0A,gifGIF89a,webpRIFF];if(!isset($signatures[$expected_ext])){returnfalse;}$expected_signature$signatures[$expected_ext];$signature_lengthstrlen($expected_signature);$handlefopen($tmp_file_path,rb);if(!$handle){returnfalse;}$file_signaturefread($handle,$signature_length);fclose($handle);returnstrncmp($file_signature,$expected_signature,$signature_length)0;}/** * 验证图片文件 */privatefunctionvalidateImageFile($tmp_file_path){// 使用GD库验证图片$image_infogetimagesize($tmp_file_path);if($image_infofalse){returnfalse;}// 验证图片是否可以成功加载$image_type$image_info[2];$supported_types[IMAGETYPE_JPEG,IMAGETYPE_PNG,IMAGETYPE_GIF,IMAGETYPE_WEBP];if(!in_array($image_type,$supported_types)){returnfalse;}returntrue;}/** * 检查文件是否包含恶意内容 */privatefunctioncontainsMaliciousContent($file_path){$contentfile_get_contents($file_path);// 检查常见的Webshell特征(简化示例)$dangerous_patterns[/\?php\s*(system|exec|shell_exec|passthru|eval|assert)/i,/script[^]*.*?(eval|document\.write|document\.cookie).*?\/script/is,/onload\s*|onerror\s*|onclick\s*/i,/javascript:/i];foreach($dangerous_patternsas$pattern){if(preg_match($pattern,$content)){returntrue;}}// 检查是否包含PHP标签(对于图片文件不应该有)if(preg_match(/\?php|%|\?/i,$content)){returntrue;}returnfalse;}/** * 生成安全的随机文件名 */privatefunctiongenerateSafeFilename($extension){// 使用密码学安全的随机数生成器$random_bytesrandom_bytes(16);$safe_namebin2hex($random_bytes)...$extension;return$safe_name;}/** * 生成分享令牌 */privatefunctiongenerateShareToken(){returnbin2hex(random_bytes(16));}/** * 生成缩略图 */privatefunctiongenerateThumbnail($source_path,$stored_name,$width,$height){$thumb_dir$this-upload_base_dir.THUMBNAIL_DIR;$thumb_path$thumb_dir.$stored_name;// 创建缩略图目录if(!is_dir($thumb_dir)){mkdir($thumb_dir,0755,true);}// 最大缩略图尺寸$max_width200;$max_height200;// 计算缩略图尺寸if($width$height){$new_width$max_width;$new_heightintval($height*($max_width/$width));}else{$new_height$max_height;$new_widthintval($width*($max_height/$height));}// 创建缩略图$source_imageimagecreatefromstring(file_get_contents($source_path));$thumbnailimagecreatetruecolor($new_width,$new_height);// 保持透明度(针对PNG和GIF)imagealphablending($thumbnail,false);imagesavealpha($thumbnail,true);imagecopyresampled($thumbnail,$source_image,0,0,0,0,$new_width,$new_height,$width,$height);// 保存缩略图$extensionstrtolower(pathinfo($stored_name,PATHINFO_EXTENSION));switch($extension){casejpg:casejpeg:imagejpeg($thumbnail,$thumb_path,85);break;casepng:imagepng($thumbnail,$thumb_path,8);break;casegif:imagegif($thumbnail,$thumb_path);break;casewebp:imagewebp($thumbnail,$thumb_path,85);break;}imagedestroy($source_image);imagedestroy($thumbnail);return$thumb_path;}/** * 保存图片信息到数据库 */privatefunctionsaveImageToDatabase($user_id,$original_name,$stored_name,$file_path,$file_size,$mime_type,$width,$height,$title,$description,$is_public,$share_token){try{$sqlINSERT INTO images (user_id, original_name, stored_name, file_path, file_size, mime_type, width, height, title, description, is_public, share_token) VALUES (:user_id, :original_name, :stored_name, :file_path, :file_size, :mime_type, :width, :height, :title, :description, :is_public, :share_token);$stmt$this-db-prepare($sql);$stmt-execute([:user_id$user_id,:original_name$original_name,:stored_name$stored_name,:file_path$file_path,:file_size$file_size,:mime_type$mime_type,:width$width,:height$height,:title$title,:description$description,:is_public$is_public?1:0,:share_token$share_token]);return$this-db-lastInsertId();}catch(PDOException$e){// 如果数据库保存失败,删除已上传的文件if(file_exists($file_path)){unlink($file_path);}thrownewException(图片信息保存失败: .$e-getMessage());}}/** * 确保上传目录安全 */privatefunctionensureSecureDirectories(){$directories[$this-upload_base_dir,$this-upload_base_dir.images/,$this-upload_base_dir.THUMBNAIL_DIR];foreach($directoriesas$dir){if(!is_dir($dir)){mkdir($dir,0755,true);}// 在目录中放置.htaccess(Apache)或web.config(IIS)防止直接执行$this-createSecurityFile($dir);// 放置空白的index.html防止目录列表$index_file$dir.index.html;if(!file_exists($index_file)){file_put_contents($index_file,!DOCTYPE htmlhtmlbody/body/html);}}}/** * 创建安全配置文件 */privatefunctioncreateSecurityFile($directory){// Apache服务器$htaccess_contentHTACCESS# 防止直接执行脚本文件 FilesMatch \.(php|php5|phtml|pl|py|jsp|asp|sh|cgi)$ Order Deny,Allow Deny from all /FilesMatch # 防止目录列表 Options -Indexes # 设置缓存头 FilesMatch \.(jpg|jpeg|png|gif|webp)$ Header set Cache-Control max-age2592000, public /FilesMatch # 限制文件访问(仅允许图片类型) FilesMatch \.(jpg|jpeg|png|gif|webp)$ Allow from all /FilesMatch FilesMatch \.(php|txt|sql|log)$ Deny from all /FilesMatchHTACCESS;$htaccess_path$directory..htaccess;if(!file_exists($htaccess_path)){file_put_contents($htaccess_path,$htaccess_content);}}/** * 记录安全事件 */privatefunctionlogSecurityEvent($event_type,$data){$log_entrysprintf([%s] %s: %s\n,date(Y-m-d H:i:s),$event_type,json_encode($data,JSON_UNESCAPED_UNICODE));$log_file$this-upload_base_dir.security.log;file_put_contents($log_file,$log_entry,FILE_APPEND|LOCK_EX);}/** * 获取上传错误信息 */privatefunctiongetUploadErrorMessage($error_code){$errors[UPLOAD_ERR_INI_SIZE文件大小超过服务器限制,UPLOAD_ERR_FORM_SIZE文件大小超过表单限制,UPLOAD_ERR_PARTIAL文件只有部分被上传,UPLOAD_ERR_NO_FILE没有文件被上传,UPLOAD_ERR_NO_TMP_DIR缺少临时文件夹,UPLOAD_ERR_CANT_WRITE文件写入失败,UPLOAD_ERR_EXTENSIONPHP扩展阻止了文件上传];return$errors[$error_code]??未知上传错误;}/** * 获取用户图片列表 */publicfunctiongetUserImages($user_id,$limit20,$offset0){$sqlSELECT * FROM images WHERE user_id :user_id ORDER BY uploaded_at DESC LIMIT :limit OFFSET :offset;$stmt$this-db-prepare($sql);$stmt-bindValue(:user_id,$user_id,PDO::PARAM_INT);$stmt-bindValue(:limit,$limit,PDO::PARAM_INT);$stmt-bindValue(:offset,$offset,PDO::PARAM_INT);$stmt-execute();return$stmt-fetchAll();}/** * 通过分享令牌获取图片 */publicfunctiongetImageByToken($token){$sqlSELECT i.*, u.username FROM images i JOIN users u ON i.user_id u.id WHERE i.share_token :token AND i.is_public 1;$stmt$this-db-prepare($sql);$stmt-execute([:token$token]);$image$stmt-fetch();if($image){// 更新查看次数$update_sqlUPDATE images SET view_count view_count 1 WHERE id :id;$this-db-prepare($update_sql)-execute([:id$image[id]]);}return$image;}/** * 删除图片 */publicfunctiondeleteImage($image_id,$user_id){// 获取图片信息$sqlSELECT * FROM images WHERE id :id AND user_id :user_id;$stmt$this-db-prepare($sql);$stmt-execute([:id$image_id,:user_id$user_id]);$image$stmt-fetch();if(!$image){thrownewException(图片不存在或无权删除);}// 删除物理文件if(file_exists($image[file_path])){unlink($image[file_path]);}// 删除缩略图$thumb_path$this-upload_base_dir.THUMBNAIL_DIR.$image[stored_name];if(file_exists($thumb_path)){unlink($thumb_path);}// 删除数据库记录$delete_sqlDELETE FROM images WHERE id :id;$this-db-prepare($delete_sql)-execute([:id$image_id]);// 记录安全日志$this-logSecurityEvent(file_delete,[user_id$user_id,image_id$image_id,ip_address$_SERVER[REMOTE_ADDR]??unknown]);returntrue;}}?步骤 4:用户认证与权限管理?php// includes/auth.php// 用户认证与权限管理classAuth{private$db;publicfunction__construct(){$this-dbDatabase::getInstance()-getConnection();}/** * 用户注册 */publicfunctionregister($username,$email,$password,$full_name){// 验证输入$this-validateRegistrationInput($username,$email,$password);// 检查用户名是否已存在if($this-usernameExists($username)){thrownewException(用户名已存在);}// 检查邮箱是否已存在if($this-emailExists($email)){thrownewException(邮箱地址已被注册);}// 创建密码哈希$password_hashpassword_hash($password,PASSWORD_DEFAULT);if($password_hashfalse){thrownewException(密码加密失败);}// 插入用户记录$sqlINSERT INTO users (username, email, password_hash, full_name) VALUES (:username, :email, :password_hash, :full_name);try{$stmt$this-db-prepare($sql);$stmt-execute([:username$username,:email$email,:password_hash$password_hash,:full_name$full_name]);$user_id$this-db-lastInsertId();// 记录安全日志$this-logSecurityEvent(user_register,[user_id$user_id,username$username,ip_address$_SERVER[REMOTE_ADDR]??unknown]);return$user_id;}catch(PDOException$e){thrownewException(用户注册失败: .$e-getMessage());}}/** * 用户登录 */publicfunctionlogin($username,$password){// 防止暴力破解:检查失败次数if($this-isLoginBlocked($username)){thrownewException(账户暂时被锁定,请稍后重试);}// 获取用户信息$user$this-getUserByUsername($username);if(!$user){// 记录失败尝试$this-recordFailedLogin($username);thrownewException(用户名或密码错误);}// 验证密码if(!password_verify($password,$user[password_hash])){// 记录失败尝试$this-recordFailedLogin($username);thrownewException(用户名或密码错误);}// 检查账户状态if($user[status]!active){thrownewException(账户状态异常,请联系管理员);}// 清除失败记录$this-clearFailedLogins($username);// 更新最后登录时间$this-updateLastLogin($user[id]);// 设置会话$this-setUserSession($user);// 记录安全日志$this-logSecurityEvent(user_login,[user_id$user[id],username$username,ip_address$_SERVER[REMOTE_ADDR]??unknown,user_agent$_SERVER[HTTP_USER_AGENT]??unknown]);returntrue;}/** * 设置用户会话 */privatefunctionsetUserSession($user){// 销毁旧会话,创建新会话(防止会话固定)session_regenerate_id(true);$_SESSION[user_id]$user[id];$_SESSION[username]$user[username];$_SESSION[role]$user[role];$_SESSION[login_time]time();// 设置会话过期时间$_SESSION[expire_time]time()SESSION_TIMEOUT;// 设置用户指纹(防止会话劫持)$_SESSION[user_fingerprint]$this-generateUserFingerprint();}/** * 生成用户指纹(用于检测会话劫持) */privatefunctiongenerateUserFingerprint(){$components[$_SERVER[HTTP_USER_AGENT]??,$_SERVER[REMOTE_ADDR]??,// 可以添加更多组件,但注意隐私问题];returnhash(sha256,implode(|,$components));}/** * 验证用户指纹 */publicfunctionvalidateUserFingerprint(){if(!isset($_SESSION[user_fingerprint])){returnfalse;}$current_fingerprint$this-generateUserFingerprint();returnhash_equals($_SESSION[user_fingerprint],$current_fingerprint);}/** * 检查用户是否已登录 */publicfunctionisLoggedIn(){if(!isset($_SESSION[user_id],$_SESSION[expire_time])){returnfalse;}// 检查会话是否过期if(time()$_SESSION[expire_time]){$this-logout();returnfalse;}// 检查用户指纹(防止会话劫持)if(!$this-validateUserFingerprint()){// 记录可疑活动$this-logSecurityEvent(session_hijack_attempt,[user_id$_SESSION[user_id]??unknown,ip_address$_SERVER[REMOTE_ADDR]??unknown]);$this-logout();returnfalse;}// 延长会话过期时间(滑动过期)$_SESSION[expire_time]time()SESSION_TIMEOUT;returntrue;}/** * 获取当前用户信息 */publicfunctiongetCurrentUser(){if(!$this-isLoggedIn()){returnnull;}$sqlSELECT id, username, email, full_name, role, status FROM users WHERE id :id;$stmt$this-db-prepare($sql);$stmt-execute([:id$_SESSION[user_id]]);return$stmt-fetch();}/** * 检查用户是否是管理员 */publicfunctionisAdmin(){if(!$this-isLoggedIn()){returnfalse;}return$_SESSION[role]admin;}/** * 用户退出 */publicfunctionlogout(){// 记录安全日志if(isset($_SESSION[user_id])){$this-logSecurityEvent(user_logout,[user_id$_SESSION[user_id],ip_address$_SERVER[REMOTE_ADDR]??unknown]);}// 清除所有会话数据$_SESSION[];// 删除会话cookieif(ini_get(session.use_cookies)){$paramssession_get_cookie_params();setcookie(session_name(),,time()-42000,$params[path],$params[domain],$params[secure],$params[httponly]);}// 销毁会话session_destroy();}/** * 验证注册输入 */privatefunctionvalidateRegistrationInput($username,$email,$password){// 用户名验证if(strlen($username)3||strlen($username)50){thrownewException(用户名长度必须在3-50个字符之间);}if(!preg_match(/^[a-zA-Z0-9_]$/,$username)){thrownewException(用户名只能包含字母、数字和下划线);}// 邮箱验证if(!filter_var($email,FILTER_VALIDATE_EMAIL)){thrownewException(请输入有效的邮箱地址);}// 密码验证if(strlen($password)8){thrownewException(密码长度至少8个字符);}// 密码强度检查if(!preg_match(/[A-Z]/,$password)||!preg_match(/[a-z]/,$password)||!preg_match(/[0-9]/,$password)){thrownewException(密码必须包含大小写字母和数字);}}/** * 检查用户名是否存在 */privatefunctionusernameExists($username){$sqlSELECT COUNT(*) FROM users WHERE username :username;$stmt$this-db-prepare($sql);$stmt-execute([:username$username]);return$stmt-fetchColumn()0;}/** * 检查邮箱是否存在 */privatefunctionemailExists($email){$sqlSELECT COUNT(*) FROM users WHERE email :email;$stmt$this-db-prepare($sql);$stmt-execute([:email$email]);return$stmt-fetchColumn()0;}/** * 通过用户名获取用户 */privatefunctiongetUserByUsername($username){$sqlSELECT * FROM users WHERE username :username;$stmt$this-db-prepare($sql);$stmt-execute([:username$username]);return$stmt-fetch();}/** * 更新最后登录时间 */privatefunctionupdateLastLogin($user_id){$sqlUPDATE users SET last_login NOW() WHERE id :id;$this-db-prepare($sql)-execute([:id$user_id]);}/** * 记录失败登录 */privatefunctionrecordFailedLogin($username){$ip_address$_SERVER[REMOTE_ADDR]??unknown;$keylogin_failures:.md5($username.|.$ip_address);// 使用文件缓存模拟,实际应用中应使用Redis或Memcached$cache_filesys_get_temp_dir()./.$key;$failures0;$first_failure_timetime();if(file_exists($cache_file)){$datajson_decode(file_get_contents($cache_file),true);if($datais_array($data)){$failures$data[failures]??0;$first_failure_time$data[first_failure_time]??time();}}$failures;$data[failures$failures,first_failure_time$first_failure_time,last_attempttime(),username$username,ip_address$ip_address];file_put_contents($cache_file,json_encode($data));// 记录安全日志$this-logSecurityEvent(login_failure,[username$username,ip_address$ip_address,failure_count$failures]);}/** * 清除失败登录记录 */privatefunctionclearFailedLogins($username){$ip_address$_SERVER[REMOTE_ADDR]??unknown;$keylogin_failures:.md5($username.|.$ip_address);$cache_filesys_get_temp_dir()./.$key;if(file_exists($cache_file)){unlink($cache_file);}}/** * 检查登录是否被阻止 */privatefunctionisLoginBlocked($username){$ip_address$_SERVER[REMOTE_ADDR]??unknown;$keylogin_failures:.md5($username.|.$ip_address);$cache_filesys_get_temp_dir()./.$key;if(!file_exists($cache_file)){returnfalse;}$datajson_decode(file_get_contents($cache_file),true);if(!$data||!is_array($data)){returnfalse;}$failures$data[failures]??0;$first_failure_time$data[first_failure_time]??time();// 如果30分钟内失败5次,则锁定15分钟if($failures5(time()-$first_failure_time)1800){// 检查是否已经锁定15分钟if((time()-$first_failure_time)900){returntrue;}}returnfalse;}/** * 记录安全事件 */privatefunctionlogSecurityEvent($event_type,$data){$log_entrysprintf([%s] %s: %s\n,date(Y-m-d H:i:s),$event_type,json_encode($data,JSON_UNESCAPED_UNICODE));$log_dirdirname(__DIR__)./logs/;if(!is_dir($log_dir)){mkdir($log_dir,0755,true);}$log_file$log_dir.security.log;file_put_contents($log_file,$log_entry,FILE_APPEND|LOCK_EX);}}?步骤 5:主要页面实现?php// upload.php - 文件上传处理页面require_onceincludes/config.php;require_onceincludes/auth.php;require_onceincludes/uploader.php;$authnewAuth();$uploadernewSecureUploader();// 检查用户是否登录if(!$auth-isLoggedIn()){header(Location: login.php?redirect.urlencode($_SERVER[REQUEST_URI]));exit;}$user$auth-getCurrentUser();$message;$error;// 处理表单提交if($_SERVER[REQUEST_METHOD]POST){// 验证CSRF Token$csrf_token$_POST[CSRF_TOKEN_NAME]??;if(!validate_csrf_token($csrf_token)){$error安全验证失败,请重试;}else{try{$titleescape($_POST[title]??);$descriptionescape($_POST[description]??);$is_publicisset($_POST[is_public])$_POST[is_public]1;$result$uploader-upload(image_file,$user[id],$title,$description,$is_public);$message文件上传成功;// 如果开启了公开分享,显示分享链接if($is_public$result[share_token]){$share_urlBASE_URL./share.php?token.$result[share_token];$message. 分享链接: a href.$share_url..$share_url./a;}}catch(Exception$e){$error上传失败: .$e-getMessage();}}}?!DOCTYPEhtmlhtml langzh-CNheadmeta charsetUTF-8meta nameviewportcontentwidthdevice-width, initial-scale1.0title上传图片-?phpechoAPP_NAME;?/titlelink relstylesheethrefassets/css/style.css/headbody?phpincludeincludes/header.php;?divclasscontainerh1上传图片/h1?phpif($message):?divclassalert alert-success?phpecho$message;?/div?phpendif;??phpif($error):?divclassalert alert-danger?phpecho$error;?/div?phpendif;?divclassupload-formform actionupload.phpmethodPOSTenctypemultipart/form-dataiduploadForm?phpechocsrf_field();?divclassform-grouplabelfortitle图片标题(可选):/labelinput typetextnametitleidtitlemaxlength200placeholder请输入图片标题/divdivclassform-grouplabelfordescription描述(可选):/labeltextarea namedescriptioniddescriptionrows3placeholder请输入图片描述maxlength1000/textarea/divdivclassform-grouplabelforimage_file选择图片文件:/labelinput typefilenameimage_fileidimage_fileaccept.jpg,.jpeg,.png,.gif,.webprequiredsmallclassform-text仅支持JPG,PNG,GIF,WebP 格式,最大5MB/smalldiv idfilePreviewclassfile-preview/div/divdivclassform-grouplabelclasscheckbox-labelinput typecheckboxnameis_publicvalue1idis_public公开分享(生成可分享的链接)/label/divdivclassform-groupbutton typesubmitclassbtn btn-primaryiduploadButton上传图片/buttona hrefgallery.phpclassbtn btn-secondary返回相册/a/div/form/divdivclassupload-tipsh3安全提示:/h3ulli系统会对上传的图片进行多重安全校验,包括文件类型、大小、内容等/lili上传的图片会被随机重命名,防止文件覆盖攻击/lili所有图片文件都存储在安全目录中,无法直接通过URL访问执行/lili建议不要上传包含个人隐私信息的图片/li/ul/div/div?phpincludeincludes/footer.php;?script srcassets/js/upload.js/script/body/html步骤 6:相册展示页面?php// gallery.php - 个人相册页面require_onceincludes/config.php;require_onceincludes/auth.php;require_onceincludes/uploader.php;$authnewAuth();$uploadernewSecureUploader();// 检查用户是否登录if(!$auth-isLoggedIn()){header(Location: login.php);exit;}$user$auth-getCurrentUser();// 获取用户图片$pageisset($_GET[page])?max(1,intval($_GET[page])):1;$limit12;$offset($page-1)*$limit;$images$uploader-getUserImages($user[id],$limit,$offset);// 获取图片总数(用于分页)$dbDatabase::getInstance()-getConnection();$count_sqlSELECT COUNT(*) FROM images WHERE user_id :user_id;$stmt$db-prepare($count_sql);$stmt-execute([:user_id$user[id]]);$total_images$stmt-fetchColumn();$total_pagesceil($total_images/$limit);// 处理删除请求if($_SERVER[REQUEST_METHOD]POSTisset($_POST[delete_id])){// 验证CSRF Token$csrf_token$_POST[CSRF_TOKEN_NAME]??;if(validate_csrf_token($csrf_token)){try{$delete_idintval($_POST[delete_id]);$uploader-deleteImage($delete_id,$user[id]);$success_message图片删除成功;// 刷新页面header(Location: gallery.php?page.$page.msg.urlencode($success_message));exit;}catch(Exception$e){$error_message删除失败: .$e-getMessage();}}else{$error_message安全验证失败;}}// 获取消息参数$success_message$_GET[msg]??;?!DOCTYPEhtmlhtml langzh-CNheadmeta charsetUTF-8meta nameviewportcontentwidthdevice-width, initial-scale1.0title我的相册-?phpechoAPP_NAME;?/titlelink relstylesheethrefassets/css/style.csslink relstylesheethrefassets/css/gallery.css/headbody?phpincludeincludes/header.php;?divclasscontainerdivclassgallery-headerh1我的相册/h1a hrefupload.phpclassbtn btn-primaryiclassupload-icon↑/i上传新图片/a/div?phpif($success_message):?divclassalert alert-success?phpechohtmlspecialchars($success_message);?/div?phpendif;??phpif(isset($error_message)):?divclassalert alert-danger?phpechohtmlspecialchars($error_message);?/div?phpendif;??phpif(empty($images)):?divclassempty-gallerydivclassempty-icon️/divh3相册空空如也/h3p还没有上传任何图片,赶快上传第一张图片吧/pa hrefupload.phpclassbtn btn-primary上传图片/a/div?phpelse:?divclassgallery-statsp共?phpecho$total_images;?张图片/p/divdivclassimage-grid?phpforeach($imagesas$image):?divclassimage-carddivclassimage-preview?php$thumbnail_pathstr_replace(UPLOAD_BASE_DIR,private_uploads/,UPLOAD_BASE_DIR.THUMBNAIL_DIR.$image[stored_name]);?img src?php echo$thumbnail_path; ?alt?php echo escape($image[title]?:$image[original_name]); ?loadinglazy/divdivclassimage-infoh4?phpechoescape($image[title]?:未命名图片);?/h4pclassimage-meta?phpechodate(Y-m-d H:i,strtotime($image[uploaded_at]));?|?phpechoround($image[file_size]/1024);?KB/p?phpif($image[is_public]$image[share_token]):?pclassshare-infospanclassshare-badge公开/spana hrefshare.php?token?php echo$image[share_token]; ?target_blankclassshare-link分享链接/a/p?phpendif;?divclassimage-actionsbutton typebuttonclassbtn btn-sm btn-info view-btndata-image-id?php echo$image[id]; ?查看/buttonform methodPOSTclassdelete-formonsubmitreturn confirm(确定要删除这张图片吗);?phpechocsrf_field();?input typehiddennamedelete_idvalue?php echo$image[id]; ?button typesubmitclassbtn btn-sm btn-danger删除/button/form/div/div/div?phpendforeach;?/div!--分页--?phpif($total_pages1):?navclasspaginationul?phpif($page1):?lia href?page?php echo$page- 1; ?上一页/a/li?phpendif;??phpfor($i1;$i$total_pages;$i):??phpif($i$page):?liclassactivespan?phpecho$i;?/span/li?phpelse:?lia href?page?php echo$i; ??phpecho$i;?/a/li?phpendif;??phpendfor;??phpif($page$total_pages):?lia href?page?php echo$page 1; ?下一页/a/li?phpendif;?/ul/nav?phpendif;??phpendif;?/div!--图片查看模态框--div idimageModalclassmodaldivclassmodal-contentspanclassclose-modaltimes;/spandiv idmodalImageContainer/divdiv idmodalImageInfo/div/div/div?phpincludeincludes/footer.php;?script srcassets/js/gallery.js/script/body/html项目测试与部署指南1. 环境准备# 安装必要的PHP扩展sudoapt-getinstallphp php-mysql php-gd php-mbstring php-xml# 检查扩展是否启用php -m|grep-Emysql|gd|mbstring# 创建项目目录结构mkdir-p secure_gallery/{includes,uploads,assets/{css,js,images},logs}mkdir-p private_uploads/{images,thumbs}# 设置目录权限chmod755secure_gallerychmod755private_uploadschmod755private_uploads/imageschmod755private_uploads/thumbschmod644private_uploads/.htaccesschmod644private_uploads/images/.htaccesschmod644private_uploads/thumbs/.htaccess# 创建日志目录并设置权限mkdir-p logschmod755logschmod644logs/security.log2. 数据库配置-- 执行数据库初始化脚本(见步骤1)-- 修改includes/config.php中的数据库连接配置3. 安全配置检查清单创建安全检查脚本check_security.php:?php// 安全检查脚本echo PHP安全配置检查 \n\n;// 1. 检查PHP版本echo1. PHP版本: .PHP_VERSION.\n;if(version_compare(PHP_VERSION,7.4.0)0){echo 建议升级到PHP 7.4或更高版本\n;}// 2. 检查必要的扩展$required_extensions[pdo_mysql,gd,mbstring,openssl];foreach($required_extensionsas$ext){echo2. 扩展{$ext}: .(extension_loaded($ext)?✓ 已启用:✗ 未启用).\n;}// 3. 检查重要的PHP配置$important_settings[allow_url_fopenOff,allow_url_includeOff,display_errorsOff,log_errorsOn,expose_phpOff,session.cookie_httponly1,session.cookie_secure1,session.use_only_cookies1];foreach($important_settingsas$setting$recommended){$currentini_get($setting);echo3.{$setting}:{$current};if($current$recommended){echo ✓ 安全\n;}else{echo 建议设置为:{$recommended}\n;}}// 4. 检查文件权限$directories_to_check[private_uploads0755,private_uploads/images0755,private_uploads/thumbs0755,logs0755];foreach($directories_to_checkas$dir$recommended_perm){if(is_dir($dir)){$permsfileperms($dir)0777;echo4. 目录权限{$dir}: .decoct($perms);if($perms$recommended_perm){echo ✓ 安全\n;}else{echo 建议设置为: .decoct($recommended_perm).\n;}}else{echo4. 目录{$dir}: ✗ 不存在\n;}}// 5. 检查.htaccess文件$htaccess_files[private_uploads/.htaccess,private_uploads/images/.htaccess,private_uploads/thumbs/.htaccess];foreach($htaccess_filesas$file){if(file_exists($file)){echo5. 安全文件{$file}: ✓ 存在\n;// 检查内容$contentfile_get_contents($file);if(strpos($content,Deny from all)!false||strpos($content,Order Deny,Allow)!false){echo ✓ 包含基本安全规则\n;}}else{echo5. 安全文件{$file}: ✗ 不存在\n;}}echo\n 检查完成 \n;echo✓ 表示安全配置正确\n;echo 表示需要关注或优化\n;echo✗ 表示存在安全问题\n;?

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询