文件上传漏洞(下)

本节小编将讲述文件上传漏洞的最终进阶篇,带领大家完成文件上传漏洞的全部学习内容。

PASS-11(0x00截断)

0x00截断 (原理)

核心原理:字符串结束符的 "欺骗"

0x00 是ASCII 码中的空字符(NULL) ,对应的转义字符是\0。它的关键特性是:在 C/C++ 等底层语言中,\0是字符串的结束标志 ------ 当程序读取到\0时,会立即停止解析后续内容,认为字符串已经结束;但PHP、Java 等上层语言 ,在部分场景下(如接收、拼接、校验数据时)不会将\0当作结束符,会完整保留整个字符串。

靶场的源代码如下:

javascript 复制代码
$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
    $ext_arr = array('jpg','png','gif');
    $file_ext = substr($_FILES['upload_file']['name'],strrpos($_FILES['upload_file']['name'],".")+1);
    if(in_array($file_ext,$ext_arr)){
        $temp_file = $_FILES['upload_file']['tmp_name'];
        $img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;

        if(move_uploaded_file($temp_file,$img_path)){
            $is_upload = true;
        } else {
            $msg = '上传出错!';
        }
    } else{
        $msg = "只允许上传.jpg|.png|.gif类型文件!";
    }
}

问题就出在这行代码,

javascript 复制代码
// 拼接文件存储路径:用户可控路径 + 随机数 + 时间戳 + 合法后缀
$img_path = $_GET['save_path'] . "/" . rand(10, 99) . date("YmdHis") . "." . $file_ext;

这段代码的核心漏洞点在于 **$_GET['save_path']完全可控 **,攻击者可在save_path中插入\0(0x00 空字符),利用 "PHP 校验完整字符串、底层 C 函数截断解析" 的差异,突破后缀和路径限制。

为帮助读者理解,小编将执行流程总结如下:

step1: 攻击者构造 URL 传入恶意save_path

javascript 复制代码
?save_path=upload/easy.php%00

%00\0的 URL 编码,PHP 接收后会自动解码为 0x00 空字符)

**step2:**PHP 层拼接路径:

javascript 复制代码
$img_path = "upload/easy.php\0" . "/" . rand(10,99) . date("YmdHis") . ".jpg";

PHP 会完整保留\0,此时$file_ext仍是jpg,后端校验认为后缀合法;

step3: 底层 C 函数执行保存(如move_uploaded_file):

解析路径时遇到\0立即截断,实际保存路径变为upload/easy.php

step4: 最终服务器生成easy.php恶意脚本,攻击者可远程控制。

实操步骤如下:

开启代理和BP,拦截抓包修改数据,

我们上传的是一个jpg格式文件,符合白名单的要求,

save_path 修改为**../upload/easy.php%00** ,在拼接步骤时会拼接为easy.php%00loudong.jpg

然而存在0X00截断,最终传入easy.php文件成功。

访问图像链接时删去URL地址中easy.php部分后面的内容即可拿到靶场的全部信息,

PASS-12(Post 0x00截断)

javascript 复制代码
$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
    $ext_arr = array('jpg','png','gif');
    $file_ext = substr($_FILES['upload_file']['name'],strrpos($_FILES['upload_file']['name'],".")+1);
    if(in_array($file_ext,$ext_arr)){
        $temp_file = $_FILES['upload_file']['tmp_name'];
        $img_path = $_POST['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;

        if(move_uploaded_file($temp_file,$img_path)){
            $is_upload = true;
        } else {
            $msg = "上传失败";
        }
    } else {
        $msg = "只允许上传.jpg|.png|.gif类型文件!";
    }
}

这是PASS-12的源代码,与PASS-11进行对比,我们发现save_pathGET 请求变成了POST请求

javascript 复制代码
$img_path = $_GET['save_path'] . "/" . rand(10, 99) . date("YmdHis") . "." . $file_ext;
$img_path = $_POST['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;

GET型提交的内容会被⾃动进⾏URL解码,在POST请求中,%00不会被⾃动解码,这也导致我们增加了一个URL解码步骤,如图:

其他步骤与PASS-11大同小异,此处略去。

PASS-13(图**⽚⽊⻢**1)

javascript 复制代码
function getReailFileType($filename){
    // 以二进制只读模式打开文件(rb:避免Windows下换行符转换问题)
    $file = fopen($filename, "rb");
    // 读取文件的前2个字节(二进制数据)
    $bin = fread($file, 2);
    // 关闭文件句柄,释放资源
    fclose($file);

    // 解包二进制数据:C2chars表示将2字节数据转为2个无符号字符,存入$strInfo数组
    // 结果类似:$strInfo = ['chars1' => 255, 'chars2' => 216](对应jpg文件头)
    $strInfo = @unpack("C2chars", $bin);    
    // 将两个字节的数字拼接成整数,作为文件类型特征码
    // 例如jpg:255(chars1)+216(chars2)→ 255216
    $typeCode = intval($strInfo['chars1'].$strInfo['chars2']);    
    $fileType = '';    

    // 根据特征码匹配文件类型
    switch($typeCode){      
        case 255216: // jpg文件头:FF D8 → 十进制255 216 → 拼接为255216
            $fileType = 'jpg';
            break;
        case 13780: // png文件头:89 50 → 十进制137 80 → 拼接为13780
            $fileType = 'png';
            break;        
        case 7173: // gif文件头:47 49 → 十进制71 73 → 拼接为7173
            $fileType = 'gif';
            break;
        default: // 非上述类型,返回unknown
            $fileType = 'unknown';
    }    
    return $fileType;
}
// 初始化状态变量:是否上传成功、错误提示
$is_upload = false;
$msg = null;

// 判断用户是否点击了上传按钮(提交了表单)
if(isset($_POST['submit'])){
    // 获取上传文件的临时路径(PHP上传后会暂存到临时目录)
    $temp_file = $_FILES['upload_file']['tmp_name'];
    // 调用函数,校验文件真实类型
    $file_type = getReailFileType($temp_file);

    // 校验失败:文件类型不合法
    if($file_type == 'unknown'){
        $msg = "文件未知,上传失败!";
    }else{
        // 校验成功:拼接最终存储路径
        // UPLOAD_PATH:固定的上传目录(如/upload)
        // rand(10,99):2位随机数,避免文件名冲突
        // date("YmdHis"):时间戳(年月日时分秒),进一步保证文件名唯一
        // .$file_type:强制使用校验后的后缀(如.jpg/.png)
        $img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").".".$file_type;
        
        // 将临时文件移动到最终路径,完成上传
        if(move_uploaded_file($temp_file,$img_path)){
            $is_upload = true; // 上传成功
        } else {
            $msg = "上传出错!"; // 上传失败(如权限不足、路径不存在)
        }
    }
}

这个案例会验证上传内容,确认是图⽚格式,因此不能再像以前一样将php强制转换为jpg格式。

这是正常的图片,

接下来我们制作木马图片,在物理机 cmd中执⾏以下命令:

bash 复制代码
copy image.png /b + info.php /a webshell.png

如图,在划线位置输入cmd然后回车,执行命令后就会生成webshell.png文件

这就是含有木马的图片(记事本打开),将其上传即可。

因为上传的是一个图片,需要使用文件包含漏洞来把图片当作代码执行。

最终结果如图所示,

PASS-14(图**⽚⽊⻢**2)

PASS-14的源码如下:

javascript 复制代码
function isImage($filename){
    $types = '.jpeg|.png|.gif';
    if(file_exists($filename)){
        $info = getimagesize($filename);
        $ext = image_type_to_extension($info[2]);
        if(stripos($types,$ext)>=0){
            return $ext;
        }else{
            return false;
        }
    }else{
        return false;
    }
}

$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
    $temp_file = $_FILES['upload_file']['tmp_name'];
    $res = isImage($temp_file);
    if(!$res){
        $msg = "文件未知,上传失败!";
    }else{
        $img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").$res;
        if(move_uploaded_file($temp_file,$img_path)){
            $is_upload = true;
        } else {
            $msg = "上传出错!";
        }
    }
}
javascript 复制代码
$info = getimagesize($filename);

getimagesize() 函数核心作用

getimagesize() 是 PHP 内置函数,专门用于获取图片文件的尺寸、类型、MIME 等信息 ,如果文件不是合法的图片(或损坏的图片),函数会返回 false

它的核心能力:

  1. 读取文件的完整图片结构(不只是前 2 字节),校验文件是否符合图片格式规范;
  2. 返回图片的宽度、高度、类型(如 IMAGETYPE_JPEG)、MIME 类型(如 image/jpeg)等信息;
  3. 对损坏的图片、非图片文件会直接返回 false,比 "仅读前 2 字节" 更难被简单绕过。

虽然与PASS-13的源码不同,但是效果殊途同归,操作步骤同PASS-13。

PASS-15(图⽚⽊⻢3)

PASS-15的源码如下:

javascript 复制代码
function isImage($filename){
    //需要开启php_exif模块
    $image_type = exif_imagetype($filename);
    switch ($image_type) {
        case IMAGETYPE_GIF:
            return "gif";
            break;
        case IMAGETYPE_JPEG:
            return "jpg";
            break;
        case IMAGETYPE_PNG:
            return "png";
            break;    
        default:
            return false;
            break;
    }
}

$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
    $temp_file = $_FILES['upload_file']['tmp_name'];
    $res = isImage($temp_file);
    if(!$res){
        $msg = "文件未知,上传失败!";
    }else{
        $img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").".".$res;
        if(move_uploaded_file($temp_file,$img_path)){
            $is_upload = true;
        } else {
            $msg = "上传出错!";
        }
    }
}

exif_imagetype() 是 PHP 中用于判断图像真实类型的内置函数,核心逻辑是读取文件 ** 首个字节的签名(文件头)** 并匹配,而非依赖文件名后缀,安全性优于单纯后缀校验,但仍存在局限性。

虽然与PASS-13的源码不同,但是效果殊途同归,操作步骤同PASS-13。

PASS-16(⼆次渲染绕过)

相关推荐
志栋智能2 小时前
自动化运维还有这样一种模式。
运维·人工智能·安全·机器人·自动化
优选资源分享6 小时前
Sandboxie Plus v1.17.1/5.72.1 开源沙盘工具 系统安全防护
安全·系统安全
智驱力人工智能6 小时前
机场鸟类活动智能监测 守护航空安全的精准工程实践 飞鸟检测 机场鸟击预防AI预警系统方案 机场停机坪鸟类干扰实时监测机场航站楼鸟击预警
人工智能·opencv·算法·安全·yolo·目标检测·边缘计算
够快云库6 小时前
企业数据安全实战复盘:基于零信任架构的数据安全闭环解析
安全·架构·企业文件管理
Lust Dusk6 小时前
CTFHUB靶场HTTP协议——响应包源代码
web安全·网络安全
AC赳赳老秦7 小时前
预见2026:DeepSeek与云平台联动的自动化流程——云原生AI工具演进的核心引擎
人工智能·安全·云原生·架构·自动化·prometheus·deepseek
Amy187021118237 小时前
从“用上电”到“用好电”——微电网点亮乡村“新夜态”
安全
EasyGBS8 小时前
从“联网互通”到“安全可信”:EasyGBS支持GB35114国密协议,覆盖全场景安防合规升级
大数据·人工智能·安全·gb28181·gb35114
zhangshuang-peta8 小时前
基于人工智能的客户支持,配备安全人工智能客服机器人
人工智能·安全·机器人·ai agent·mcp·peta