Day24 XD安全学习笔记--PHP 应用文件管理模块显示上传黑白名单类型过滤访问控制 目录一.文件上传1.代码实现1upload.html2upload.php2.遇到的问题及解决二.文件管理1.代码实现2.遇到的问题及解决实现简单的上传文件后缀黑名单过滤、上传文件后缀白名单过滤和文件类型白名单过滤MIME。从开发视角来看可能存在的漏洞绕过黑名单不包含文件类型改包。最安全的是多种过滤一起用增加攻击者的门槛。代码附详细的注释。B站免费课程链接 www.bilibili.com/video/BV12om2YTEgW/?spm_id_from333.788.videopod.episodesvd_source7d8464f1ea919ffdc1761c211c6d1c4ep25一.文件上传1.代码实现1upload.html!--用deepseek写一个纯html精致的文件上传表单-- !DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title文件上传/title style * { box-sizing: border-box; } html, body { height: 100%; } body { margin: 0; padding: 24px; display: flex; align-items: center; justify-content: center; font-family: -apple-system, BlinkMacSystemFont, Segoe UI, PingFang SC, Hiragino Sans GB, Microsoft YaHei, sans-serif; background: radial-gradient(circle at 15% 15%, rgba(255, 255, 255, .18), transparent 45%), radial-gradient(circle at 85% 85%, rgba(255, 255, 255, .12), transparent 45%), linear-gradient(135deg, #667eea 0%, #764ba2 100%); } /* 卡片 */ .card { width: 100%; max-width: 440px; padding: 38px 34px 34px; background: #ffffff; border-radius: 22px; box-shadow: 0 30px 60px -18px rgba(23, 20, 70, .45), 0 0 0 1px rgba(255, 255, 255, .6) inset; animation: rise .5s ease both; } keyframes rise { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: translateY(0); } } /* 头部 */ .head { text-align: center; } .badge { width: 60px; height: 60px; margin: 0 auto; display: flex; align-items: center; justify-content: center; border-radius: 18px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); box-shadow: 0 12px 24px -8px rgba(102, 126, 234, .85); } h1 { margin: 20px 0 8px; font-size: 22px; font-weight: 700; color: #1f2937; letter-spacing: .5px; } .subtitle { margin: 0; font-size: 13.5px; line-height: 1.7; color: #6b7280; } /* 文件选择框 */ .file-input { display: block; width: 100%; margin-top: 28px; padding: 10px; font-family: inherit; font-size: 13.5px; color: #6b7280; background: #f8f9ff; border: 2px dashed #cdd5f5; border-radius: 14px; cursor: pointer; transition: border-color .25s, background .25s, box-shadow .25s; } .file-input:hover { border-color: #8b95f0; background: #f2f4ff; } .file-input:focus { outline: none; border-color: #667eea; background: #f2f4ff; box-shadow: 0 0 0 4px rgba(102, 126, 234, .16); } /* 美化选择文件按钮标准写法 */ .file-input::file-selector-button { margin-right: 14px; padding: 10px 18px; border: 0; border-radius: 10px; font-family: inherit; font-size: 13.5px; font-weight: 600; color: #ffffff; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); box-shadow: 0 8px 16px -8px rgba(102, 126, 234, .95); cursor: pointer; transition: filter .2s, transform .15s; } .file-input::file-selector-button:hover { filter: brightness(1.08); } .file-input::file-selector-button:active { transform: scale(.97); } /* 兼容旧版 WebKit 内核 */ .file-input::-webkit-file-upload-button { margin-right: 14px; padding: 10px 18px; border: 0; border-radius: 10px; font-family: inherit; font-size: 13.5px; font-weight: 600; color: #ffffff; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); cursor: pointer; } /* 提示文字 */ .tip { margin: 14px 0 0; text-align: center; font-size: 12.5px; color: #9ca3af; line-height: 1.6; } /* 提交按钮 */ .btn { display: block; width: 100%; margin-top: 24px; padding: 15px; border: 0; border-radius: 13px; font-family: inherit; font-size: 16px; font-weight: 600; letter-spacing: 1px; color: #ffffff; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); box-shadow: 0 14px 26px -10px rgba(102, 126, 234, .95); cursor: pointer; transition: transform .2s, box-shadow .2s, filter .2s; } .btn:hover { transform: translateY(-2px); filter: brightness(1.06); box-shadow: 0 18px 30px -12px rgba(102, 126, 234, 1); } .btn:active { transform: translateY(0); box-shadow: 0 10px 18px -10px rgba(102, 126, 234, .95); } .btn:focus-visible { outline: 3px solid rgba(102, 126, 234, .45); outline-offset: 3px; } /* 移动端适配 */ media (max-width: 420px) { .card { padding: 30px 22px 26px; border-radius: 18px; } h1 { font-size: 20px; } } /style /head body form classcard action/upload.php methodpost enctypemultipart/form-data !-- 头部图标与标题 -- div classhead div classbadge svg width26 height26 viewBox0 0 24 24 fillnone stroke#ffffff stroke-width2 stroke-linecapround stroke-linejoinround path dM21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4/ polyline points17 8 12 3 7 8/ line x112 y13 x212 y215/ /svg /div h1上传文件/h1 p classsubtitle选择需要上传的文件然后点击下方按钮提交/p /div !-- 文件选择 -- input classfile-input typefile namefile idfile multiple required p classtip支持 PDF / Word / Excel / 图片等格式单个文件不超过 10MB/p !-- 提交 -- button classbtn typesubmit开始上传/button /form /body /html2upload.php?php $name$_FILES[file][name];//第一个参数是表单提交文件的name值也就是file。获取表单提交文件的名字 $type$_FILES[file][type];//获取表单提交文件的类型 $size$_FILES[file][size];//获取表单提交文件的大小 $tmp_name$_FILES[file][tmp_name];// 获取服务器上的临时文件路径 $error$_FILES[file][error];// 错误码 //echo $name.br; //echo $type.br; //echo $size.br; //echo $tmp_name.br; //echo $error.br; //if(move_uploaded_file($tmp_name,upload/.$name)){ ////move_uploaded_file函数是把临时文件移动到指定目录 // echo 文件上传成功; //} //文件后缀黑名单过滤(特定环境可以用类似php5来绕过但是也执行) //$black_extarray(php,asp,jsp,aspx);//将禁止上传文件的后缀写入数组 ////xxx.jpg xxx.jpg //$fengeexplode(.,$name);//将文件名用.分隔然后变成数组。比如上传的文件名为xxx.jpg分隔之后变成数组存储。等价于$fenge[xxx,jpg] //$extsend($fenge);//提取数组$fenge中最后一个元素传给变量$exts //if(in_array($exts,$black_ext)){ ////in_array函数用来判断黑名单数组$black_ext里面包不包含上传文件后缀$exts // echo 非法后缀文件.$exts; //}else{ // move_uploaded_file($tmp_name,,$name); // echo scriptalert(上传成功)/script; //} //文件后缀白名单过滤和上面的黑名单差不多只是判断条件不一样 $allow_extarray(png,jpg,gif,jpeg); //xxx.jpg xxx.png $fengeexplode(.,$name); $extsend($fenge); if(!in_array($exts,$allow_ext)){ echo 非法后缀文件.$exts; }else{ move_uploaded_file($tmp_name,upload/.$name); echo scriptalert(上传成功)/script; } //MIME白名单过滤(和上面的文件后缀白名单过滤差不多可以通过抓包将php后缀的文件类型改成image/png的) $allow_typearray(image/png,image/jpg,image/jpeg,image/gif); if(!in_array($type,$allow_type)){ echo 非法文件.$type; }else{ move_uploaded_file($tmp_name,upload/.$name); echo scriptalert(上传成功)/script; }2.遇到的问题及解决调试访问上传的文件报错可以用小皮的环境访问改文件类型绕过MIME将application/octet-stream改为image/png绕过文件后缀黑名单绕过test.php5解析?php phpinfo();?二.文件管理1.代码实现file-manage.php?php $dir$_GET[path] ?? ./; //获取路径默认./ //$dir./; function show_file($dir){ $dopendir($dir);//opendir() 函数用于打开指定的目录返回句柄用来读取目录中的文件和子目录 while(($filereaddir($d))!false){ //readdir() 函数用于从打开的目录句柄中读取目录中的文件和子目录 echo br; if(is_dir($file)){ //is_dir() 函数用于检查指定的路径是否是一个目录 echo 文件夹.a href?path$file$file/abr; //通过url地址栏加上path参数好让$dir接收 }else{ echo 文件.$file; } } } $black_filepatharray(../,..\\);//可以用./或者././等绕过太灵活了 if(in_array($dir,$black_filepath)){ echo scriptalert(禁止跨目录访问)/script; }else{ show_file($dir);//将函数filelist改为show_file() }2.遇到的问题及解决获取路径错误$dir$_GET[path] ?? ./;的空字符加入./怎么都是1遍历目录和文件while(($filereaddir($d))!false)里面的$filereaddir($d)要括起来不然按照运算符优先级先执行readdir($d)!false运算的结果为布尔赋值给$file了。为什么filelist函数报错因为filelist是小迪之前就写好的代码文件有这个函数用show_file替换就好了。可以找到小皮的php.ini文件来限制目录的访问