您现在的位置是:首页 > 技术教程 正文

PHP的文件上传漏洞的前因后果

admin 阅读: 2024-03-17
后台-插件-广告管理-内容页头部广告(手机)

常见的文件上传漏洞包括任意文件上传、任意文件下载、任意文件删除以及任意文件读取和写入,本篇内容主要介绍常见的文件上传漏洞涉及到的 PHP 代码的相关部分,旨在帮助我们在阅读 PHP 代码的时候,能够对可能出现漏洞的地方有所了解。

文件上传代码

1.1.1 文件上传的前端代码

文件上传的前端代码较为简单,只要提供基本的文件上传功能,将数据传到 PHP 文件中即可,代码如下:

  1. <!DOCTYPE html>
  2. <meta charset="UTF-8">
  3. <html>
  4. <head>
  5. <title>文件上传示例</title>
  6. </head>
  7. <body>
  8. <h1>文件上传</h1>
  9. <form action="upload.php" method="post" enctype="multipart/form-data">
  10. 选择文件:<input type="file" name="file">
  11. <input type="submit" value="上传">
  12. </form>
  13. </body>
  14. </html>

注意,此处的

前端代码整体显示如下:

1.2 文件上传的后端代码

1.2.1本地开发处理函数

首先我们来看一段构造好的后端 PHP 处理代码,这段代码是根据 MIME 进行文件上传的判断:

  1. <?php
  2. // 检查是否有文件上传
  3. if (isset($_FILES['file'])) {
  4. // 获取文件信息
  5. $file =$_FILES['file'];
  6. $fileName =$file['name'];
  7. $fileTmp =$file['tmp_name'];
  8. $fileError =$file['error'];
  9. // 允许的文件类型(根据 MIME 类型)
  10. $allowMimeTypes = array(
  11. 'image/jpeg',
  12. 'image/png',
  13. 'image/gif',
  14. 'application/pdf',
  15. 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
  16. 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
  17. 'application/vnd.ms-excel',
  18. 'text/plain',
  19. // 在这里添加更多的允许 MIME 类型
  20. );
  21. // 获取文件的 MIME 类型
  22. $finfo = finfo_open(FILEINFO_MIME_TYPE);
  23. $mimeType = finfo_file($finfo, $fileTmp);
  24. finfo_close($finfo);
  25. // 检查 MIME 类型
  26. if (in_array($mimeType,$allowMimeTypes)) {
  27. // 如果没有错误,则上传文件
  28. if ($fileError === 0) {
  29. // 设定上传文件的路径
  30. $uploadPath = 'uploads/'.$fileName;
  31. // 保存文件
  32. if (move_uploaded_file($fileTmp,$uploadPath)) {
  33. echo "文件上传成功";
  34. } else {
  35. echo "文件上传失败";
  36. }
  37. } else {
  38. echo "文件上传过程中出现错误";
  39. }
  40. } else {
  41. echo "不允许的文件类型";
  42. }
  43. } else {
  44. echo "没有文件被上传";
  45. }
  46. ?>

这段后端代码的处理逻辑为:首先检查是否有文件上传,如果有文件上传就获取文件信息,并且根据文件信息去判断是否属于允许的文件类型,如果属于就上传,如果不属于就不上传。

其中需要关注的有两个问题,一个是 FILE 函数,一个是 MIME 校验

FILE 函数 数据结构如下:

  1. $_FILES['filename'] = array(
  2. 'name' => array(string), // 文件名
  3. 'type' => array(string), // 文件类型
  4. 'size' => array(int), // 文件大小
  5. 'tmp_name' => array(string), // 文件的临时存储路径
  6. 'error' => array(int) // 错误代码
  7. );

可以注意到我们根据 File函数处理上传的数据包,来判断文件的类型,在编辑的过程中,我们设置的过滤标准是根据 MIME 类型进行过滤,相关代码如下:

  1. // 允许的文件类型(根据 MIME 类型)
  2. $allowMimeTypes = array(
  3. 'image/jpeg',
  4. 'image/png',
  5. 'image/gif',
  6. 'application/pdf',
  7. 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
  8. 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
  9. 'application/vnd.ms-excel',
  10. 'text/plain',
  11. // 在这里添加更多的允许 MIME 类型
  12. );

实际在传输数据包时,我们首先上传一个名为test.php的文件,其发送数据包和返回数据包如下:

实际上数据包会显示给我们服务器端可以接受的文件 MIME 类型,大体可以知道是根据 MIME 过滤的,此时的绕过方法有将Content-Type删除,不用做其他任何的修改,这样在服务器端对 MIME 函数的接收为NULL,由于 in_array() 函数使用宽松比较,如果数组中存在 NULL 值,那么 in_array(NULL, $array) 将返回 true,从而达成了绕过的效果。

再来看一种常见的过滤方法,根据白名单后缀名过滤后端处理代码

  1. <?php
  2. // 定义允许的文件扩展名
  3. $allowed_extensions = array('.jpg', '.jpeg', '.png', '.gif', '.bmp', '.svg');
  4. // 检查是否有文件上传
  5. if ($_FILES) {
  6. $file =$_FILES['file'];
  7. $file_name =$file['name'];
  8. $file_tmp_name =$file['tmp_name'];
  9. $file_error =$file['error'];
  10. $file_size =$file['size'];
  11. // 获取文件后缀名
  12. $file_extension = strrchr($file_name, '.');
  13. // 检查后缀名是否允许
  14. if (!in_array($file_extension,$allowed_extensions)) {
  15. echo "请上传以下类型的文件:jpg, jpeg, png, gif, bmp, svg";
  16. } elseif ($file_error !== UPLOAD_ERR_OK) {
  17. // 如果文件上传失败
  18. echo "文件上传失败。";
  19. } else {
  20. // 如果文件上传成功
  21. // 确保临时文件存在并且可读
  22. if (is_uploaded_file($file_tmp_name)) {
  23. // 文件上传的处理...
  24. $upload_success = move_uploaded_file($file['tmp_name'], 'uploads/' . $file['name']);
  25. // 检查文件是否成功上传
  26. if ($upload_success) {
  27. echo "文件上传成功。";
  28. } else {
  29. echo "文件上传失败,移动文件时出错。";
  30. }
  31. } else {
  32. echo "文件上传失败,临时文件不存在或不可读。";
  33. }
  34. }
  35. } else {
  36. echo "没有文件被上传。";
  37. }
  38. ?>

这里的核心逻辑就是先检查扩展名是否是允许的,如果不是允许的或者上传错误就进行上传失败,然后再进行上传的操作。

我们对比上传test.jpg和test.php后缀会发现这种是根据白名单后缀去验证的,实际绕过很困难。

在这种情况下,可行的思路是通过SVG-XSS去进行漏洞赏金的挖掘或者窃取管理员 Cookie,笔者曾经靠此种思路轻易获取了很不错的赏金漏洞。

其次通过黑名单处理文件上传也是常见的一种处理逻辑(实际现在随着安全意识的提升,不再变多),相关代码如下:

  1. <?php
  2. // 定义允许的文件扩展名
  3. $allowed_extensions = array('.php');
  4. // 检查是否有文件上传
  5. if ($_FILES) {
  6. $file =$_FILES['file'];
  7. $file_name =$file['name'];
  8. $file_tmp_name =$file['tmp_name'];
  9. $file_error =$file['error'];
  10. $file_size =$file['size'];
  11. // 获取文件后缀名
  12. $file_extension = strrchr($file_name, '.');
  13. // 检查后缀名是否允许
  14. if (in_array($file_extension,$allowed_extensions)) {
  15. echo "请不要上传php文件类型";
  16. } elseif ($file_error !== UPLOAD_ERR_OK) {
  17. // 如果文件上传失败
  18. echo "文件上传失败。";
  19. } else {
  20. // 如果文件上传成功
  21. // 确保临时文件存在并且可读
  22. if (is_uploaded_file($file_tmp_name)) {
  23. // 文件上传的处理...
  24. $upload_success = move_uploaded_file($file['tmp_name'], 'uploads/' . $file['name']);
  25. // 检查文件是否成功上传
  26. if ($upload_success) {
  27. echo "文件上传成功。";
  28. } else {
  29. echo "文件上传失败,移动文件时出错。";
  30. }
  31. } else {
  32. echo "文件上传失败,临时文件不存在或不可读。";
  33. }
  34. }
  35. } else {
  36. echo "没有文件被上传。";
  37. }
  38. ?>

实际传输数据包时,很容易达成绕过的效果,比如进行大小写绕过即可:

1.2.2 富文本编辑器处理上传

UEditor 是一款由百度前端技术部开源的在线富文本编辑器,它支持所见即所得的编辑模式,并且提供了丰富的功能,如图片上传、表格、代码高亮、多语言支持等。UEditor 设计简洁、功能强大,可以轻松集成到各种Web应用中,特别是PHP开发的Web应用。

在实际的网页中,我们常常会看到某些投稿网站使用 UEditor,如果 UEditor 版本过低,我们可以通过 UEditor 拿到网页的 Webshell,本次实验环境使用的是v1.4.3.3版本的富文本编辑器。

部署 UEdior 方法如下:

  1. 下载UEditor: 访问UEditor的GitHub页面(GitHub - fex-team/ueditor: rich text 富文本编辑器)或者官网(GitHub - fex-team/ueditor: rich text 富文本编辑器)下载最新版本的UEditor。下载后,你将得到一个包含ueditor.config.js、ueditor.all.min.js等文件的压缩包。
  2. 解压UEditor: 将下载的压缩包解压,并将解压后的文件夹上传到你的PHP服务器上的一个合适目录。
  3. 配置UEditor: 在你的HTML页面中,引入UEditor的JS文件和配置文件。例如:
  1. <script type="text/javascript" src="path/to/ueditor.config.js"></script>
  2. <script type="text/javascript" src="path/to/ueditor.all.min.js"></script>
  1. 初始化UEditor: 在你的HTML页面中,添加一个