ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

从PHP error_log到RCE:文件上传绕过与WAF防御盲区实战解析

2026/8/2 8:16:43 拓冰建站 浏览量
从PHP error_log到RCE:文件上传绕过与WAF防御盲区实战解析

1. 项目概述:从一道CTF题看文件上传攻防的实战演进

最近在复盘CISCN 2024的一道Web题目,它把PHP文件上传这个老生常谈的漏洞玩出了新花样。题目本身不算复杂,但解题过程中涉及到的绕过技巧和背后的WAF(Web应用防火墙)逻辑,恰恰是当前企业安全建设和红蓝对抗中最真实的缩影。文件上传漏洞之所以经久不衰,是因为它直接通向“代码执行”这个终极目标,而防御方构建的WAF规则又在不断迭代。这道题就像一个微缩战场,让我们能清晰地看到攻击者的思路如何层层递进,防御者的策略又该如何查漏补缺。

这篇文章,我就以这道题为引子,拆解其中用到的一个关键绕过技巧——利用PHP的error_log函数与include_path配置进行文件写入,并最终实现RCE(远程代码执行)。更重要的是,我会深入聊聊这个绕过手法所暴露的WAF防护盲区,以及我们该如何从规则、架构、代码三个层面进行实质性加固。无论你是正在打CTF的选手,还是负责企业应用安全的工程师,希望这些从实战中抠出来的细节能给你带来启发。

2. 题目场景还原与核心漏洞点分析

2.1 题目环境与功能简述

题目模拟了一个简单的文件上传服务。前端是一个上传表单,允许用户上传图片。后端是经典的PHP架构,大概的逻辑是:接收文件 -> 检查文件类型(通常通过MIME类型或后缀名)-> 将文件移动到服务器上的一个固定目录(比如uploads/)-> 返回文件的访问链接。

常见的防御代码可能长这样:

<?php $allowed_ext = array('jpg', 'png', 'gif'); $upload_dir = 'uploads/'; $file_name = $_FILES['file']['name']; $file_ext = strtolower(pathinfo($file_name, PATHINFO_EXTENSION)); $file_tmp = $_FILES['file']['tmp_name']; // 检查1:后缀名白名单 if (!in_array($file_ext, $allowed_ext)) { die('文件类型不允许!'); } // 检查2:MIME类型检查 $finfo = finfo_open(FILEINFO_MIME_TYPE); $mime_type = finfo_file($info, $file_tmp); if (!in_array($mime_type, array('image/jpeg', 'image/png', 'image/gif'))) { die('文件MIME类型不合法!'); } // 检查3:文件内容检查(简单版) if (getimagesize($file_tmp) === false) { die('文件不是有效的图片!'); } // 生成最终文件名并移动 $new_file_name = md5(uniqid()) . '.' . $file_ext; $destination = $upload_dir . $new_file_name; if (move_uploaded_file($file_tmp, $destination)) { echo "文件上传成功!路径:" . $destination; } else { die('文件移动失败。'); } ?>

这道题的“坑”就藏在后续的文件处理逻辑里。它可能不仅做了上述检查,还引入了一个“文件日志”或“错误处理”功能,而正是这个附加功能,成了整个防御链条中最脆弱的一环。

2.2 漏洞本质:error_log函数的不当使用

题目突破的关键,在于后端在处理某些异常时,使用了PHP的error_log()函数,并且错误日志的路径部分可控。error_log()函数原型如下:

bool error_log ( string $message [, int $message_type = 0 [, string $destination [, string $extra_headers ]]] )

message_type为3时,message会被写入到destination参数指定的文件中。如果destination是一个相对路径,PHP会尝试在include_path配置的目录列表中寻找或创建该文件。

假设后端有这样一段问题代码:

// 假设在处理上传的某个环节,如果发生“特定错误”,就记录日志 if ($some_error_condition) { $log_message = "用户上传文件时发生错误,文件名:" . $_POST['filename']; // 注意,文件名可能可控 $log_file = “./logs/upload_error.log”; // 看起来是固定路径,但真的固定吗? // 或者更糟糕的情况: // $log_file = “logs/” . date(‘Ymd’) . “.log”; // 日期命名,但路径基础可能被篡改 error_log($log_message, 3, $log_file); }

如果攻击者能够通过某种方式影响$log_file这个变量的值,或者影响PHP解释器寻找这个文件的路径,那么就有可能将错误信息(其中包含我们可控的部分)写入到一个非预期的、可访问的位置,甚至写入一个带有.php后缀的文件中。

在这道题中,攻击点更加隐蔽。它利用了PHP的一个特性:当error_log()destination参数为空或为一个相对路径,且message_type为1(发送到PHP的系统日志记录器)或3(写入文件)时,其行为会受到php.inierror_log指令和include_path指令的影响。特别是,如果系统没有正确配置error_log,同时include_path包含了当前目录(.),那么在某些情况下,错误信息可能会被写入到include_path中的某个目录下。

攻击者的思路是:构造一个特殊的请求,触发一个PHP警告或错误,并让这个错误信息包含恶意PHP代码,然后利用路径配置问题,将这个错误日志写入到一个可通过Web访问的目录下,并拥有.php后缀,从而制造一个Webshell。

3. 核心绕过技巧深度解析:从路径混淆到代码写入

3.1 利用include_path进行路径穿越

这是整个绕过手法的技术基石。include_path是PHP的一个配置选项,它定义了requireinclude等函数查找文件的目录列表。在error_log函数处理类型3的日志写入时,如果提供的目标路径是相对路径,PHP也会在这些路径中查找。

假设服务器的php.ini配置中有一项:

include_path = “.:/var/www/html/includes:/usr/share/php”

这里的.代表当前脚本所在的目录。如果我们的上传功能脚本位于/var/www/html/upload.php,那么当前目录.就是/var/www/html/

现在,考虑一个有缺陷的错误处理代码:

// 开发者本意是将错误日志写到项目根目录的 ‘error.log’ $error_msg = “上传失败”; error_log($error_msg, 3, ‘error.log’); // 注意,这里是相对路径 ‘error.log’

PHP会尝试在include_path中寻找error.log。它会首先尝试./error.log,也就是/var/www/html/error.log。如果这个文件不存在,并且PHP进程对/var/www/html/目录有写权限,它就会创建这个文件。

攻击者的机会在哪里?如果他能控制错误信息$error_msg的一部分,并且能诱使系统将错误日志写入到一个可通过Web访问的目录(比如/var/www/html/本身),那么问题就来了。但仅仅写入.log文件还不够,因为Apache/Nginx通常不会将.log文件当作PHP解析。

3.2 结合文件名注入实现后缀控制

关键的一步是控制写入文件的文件名。在标准用法中,error_logdestination参数是硬编码的。但是,在一些框架或自定义错误处理器中,文件名可能会由动态变量拼接而成。

例如,一种可能的情景(题目可能简化或变形了此情景):

// 一种危险的写法:将用户输入的一部分用作日志文件名的一部分 $user_id = $_GET[‘uid’]; // 假设uid可控 $log_file = “user_{$user_id}_error.log”; error_log(“Some error”, 3, $log_file);

如果攻击者传入uid=../../shell.php%00,经过路径拼接,可能得到user_../../shell.php_error.log。但这里会受到%00空字节截断(PHP<5.3.4)或路径遍历检查的制约,不是最优雅的方式。

在这道题的精妙之处在于,它可能利用了error_log函数自身处理messagedestination的某种特性,或者结合了其他PHP配置(如open_basedir限制被绕过),使得攻击者能够注入目录分隔符../,最终让destination参数指向Web目录下的一个.php文件。

一个更直接的利用方式,是题目环境可能错误地允许将日志直接写入到已存在的、可写的.php文件末尾。例如,如果网站有一个info.php文件,内容为<?php phpinfo(); ?>,且该文件全局可写,那么通过error_log写入额外的PHP代码到该文件末尾,就可以在原有功能基础上附加恶意代码。但这需要文件本身已存在且可写,条件较为苛刻。

从这道题的热搜词“php错误处理”和“文件上传绕过”来看,更可能的一种综合利用链是:

  1. 首先通过一个合法的图片上传,将文件写入到uploads/目录。
  2. 然后,利用某个功能(可能是文件包含、图片处理库的漏洞等)触发一个PHP错误。
  3. 在触发错误时,通过精心构造的请求参数,影响错误处理流程,使得error_log函数被调用。
  4. 通过参数污染或配置篡改,让error_logdestination指向uploads/目录下刚刚上传的图片文件(或者一个通过路径穿越可以指向的位置)。
  5. 由于error_log以追加模式写入,它会把错误信息(包含我们注入的PHP代码)写到那个图片文件的末尾。
  6. 如果服务器配置不当(例如,配置了AddType application/x-httpd-php .jpg,或者使用了某些有解析漏洞的中间件),这个包含PHP代码的“图片”文件就可能被当作PHP脚本执行。

注意:这种利用方式对服务器环境配置有特定要求,并非通用。但它揭示了WAF防御中的一个典型盲区:WAF通常严格检查文件上传时的初始内容,但对于文件上传成功后内容的后续变更(如通过错误日志追加、通过文件包含写入等),往往缺乏持续性的监控和校验。

3.3 最终Payload构造与RCE实现

假设我们经过测试,发现了以下可利用点:

  • 上传点对图片内容检查严格,无法直接上传Webshell。
  • 网站存在一个API接口/api/debug.php,当传入参数debug=1时,会开启详细错误报告,并将错误信息记录到./logs/debug_加上当前日期.log的文件中。
  • 我们可以控制触发错误的信息,例如通过传入一个不存在的函数名。

那么,一个可能的攻击Payload构造如下:

  1. 第一步:信息收集

    # 探测debug功能 curl -X POST ‘http://target.com/api/debug.php’ -d ‘debug=1&action=test’ # 查看响应,确认是否开启了详细报错,并观察错误日志的路径提示。
  2. 第二步:尝试路径穿越我们需要让日志文件不是写在./logs/下,而是写到Web目录./下。如果debug.php中构造日志路径的代码是:

    $log_file = “./logs/debug_” . date(‘Ymd’) . “.log”;

    我们似乎无法控制。但如果我们能控制date(‘Ymd’)的输出?这不可能。换个思路,如果错误信息本身包含了我们可以注入的路径呢?但error_logmessage参数内容会被写入文件,而不是决定文件名。

    真正的突破口可能在别处。回顾include_path。如果./logs/目录不存在,或者PHP进程没有写入权限,而include_path中包含.(当前目录),且当前目录(Web根目录)有写权限,PHP可能会因为无法在指定路径创建文件,而回退到include_path中的其他可写目录尝试创建吗?根据PHP手册,error_log对于类型3,如果destination无法创建或打开,函数会返回FALSE,并不会自动回退到include_path。所以这个思路可能行不通。

    因此,题目更可能采用了另一种模式:它可能不是直接利用error_log写入Webshell,而是利用error_log将包含PHP代码的错误信息,写入到一个可以通过其他漏洞(如文件包含、本地文件包含)引用的临时文件或特定位置,再通过包含来执行。例如,error_log在某些系统配置下,当message_type=01时,会写入到系统日志(如syslog)或PHP的错误日志文件(由php.ini中的error_log指定)。如果这个指定的PHP错误日志文件位于Web目录下,并且攻击者能控制部分错误信息,那就能达成目的。

    假设管理员错误配置了php.ini

    error_log = /var/www/html/php_errors.log log_errors = On

    那么,任何PHP错误都会被记录到Web可访问的/var/www/html/php_errors.log文件中。攻击者只需要触发一个错误,并且错误信息中包含<?php system($_GET[‘cmd’]);?>,就能污染这个日志文件。然后直接访问http://target.com/php_errors.log?cmd=id,如果服务器配置了将该.log文件解析为PHP(错误配置!),就能执行命令。

    但这道题是文件上传题,所以很可能需要将“文件上传”和“错误日志污染”结合起来。例如,上传一个文件,其文件名、文件内容或元数据中包含能触发PHP错误并携带Payload的字符。

  3. 第三步:构造上传Payload我们上传一个图片,但在文件名或某个表单字段中注入Payload。

    POST /upload.php HTTP/1.1 Host: target.com Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryABC123 ------WebKitFormBoundaryABC123 Content-Disposition: form-data; name=“file”; filename=“shell.php<?php eval($_POST[‘a’]);?>.jpg” Content-Type: image/jpeg [合法的JPEG文件二进制内容] ------WebKitFormBoundaryABC123 Content-Disposition: form-data; name=“action” debug ------WebKitFormBoundaryABC123

    当服务器端尝试处理这个文件名时,shell.php<?php eval($_POST[‘a’]);?>.jpg可能会因为包含特殊字符<?而导致pathinfo()或其他字符串函数报错(如抛出警告)。如果此时服务器配置了log_errors = Onerror_log指向Web目录,这个警告信息(包含完整的文件名,即我们的Payload)就会被记录到Web可访问的错误日志文件中。

  4. 第四步:访问错误日志文件执行代码假设错误日志文件是/var/www/html/php_errors.log,访问http://target.com/php_errors.log。查看日志末尾,如果发现了我们注入的<?php eval($_POST[‘a’]);?>代码,并且服务器将其作为PHP解析,那么我们就获得了Webshell。

    我们可以直接POST请求该日志文件:

    POST /php_errors.log HTTP/1.1 Host: target.com Content-Type: application/x-www-form-urlencoded a=system(‘whoami’);

    如果配置正确,服务器会执行whoami命令并返回结果。

4. WAF防御策略的盲区与加固实战

这道题清晰地展示了,一个功能点(文件上传)的安全,不能仅仅依赖于对该功能点的直接输入检查。攻击面可能来自关联的功能、服务器的配置、甚至是编程语言本身的特性。下面我们从WAF和代码层面,探讨如何防御此类“曲线救国”的攻击。

4.1 传统WAF在文件上传防护上的典型策略与局限

大多数云WAF或硬件WAF对于文件上传的防护,主要基于以下几个层面:

  1. 请求体解析与检查:识别multipart/form-data请求,对上传的文件名(filename参数)进行关键词过滤(如过滤../php等),对文件内容进行静态特征码扫描(如查找<?phpeval(assert(等字符串)。
  2. 文件类型校验:通过检测文件头魔术数字(Magic Bytes)来判断文件真实类型,并与后缀名进行比对。
  3. 规则拦截:匹配到预设的危险规则(如“文件名包含PHP标签”)则中断请求。

其局限性在于:

  • 滞后性:规则库依赖于已知的攻击模式。对于error_log滥用这种非常规的、依赖特定环境配置的组合利用手法,WAF可能没有对应的规则。
  • 上下文缺失:WAF通常孤立地检查单个请求。它能看到上传请求,也能看到后续访问php_errors.log的请求,但它很难将这两个请求逻辑关联起来,识别出“上传行为污染了日志文件,后续访问日志文件执行了代码”这一完整攻击链。
  • 对服务器配置无感知:WAF不知道后端php.inierror_loginclude_path的配置,因此无法评估“将错误信息写入Web目录”这一风险。
  • 无法防御已写入的攻击:一旦恶意代码通过某种方式(如本题的日志污染)被写入服务器文件系统,后续对其的访问就是一个看似正常的“访问静态文件”或“访问日志文件”的请求,WAF很难判断这个.log.jpg文件是否包含恶意代码,除非它具备动态文件内容检测能力(成本极高)。

4.2 针对“日志写入Webshell”的加固方案

防御的核心思路是:隔离、限制、监控。

4.2.1 配置层面加固(治本之策)
  1. 错误日志隔离

    • 绝对禁止将PHP错误日志、任何应用日志写入Web可访问目录。这是铁律。在php.ini中,确保error_log指令指向一个Web根目录之外的路径,例如:
      error_log = /var/log/php/php_errors.log
    • 确保该目录权限严格,仅允许PHP进程用户(如www-data)写入,其他用户无读权限,尤其不能让Web服务器用户(同是www-data)有读权限?这里需要区分:PHP进程需要写,Web服务器进程如果和PHP是同一用户,那它自然能读。更安全的做法是让日志目录的所属用户和组与Web进程不同,并通过设置ACL或让PHP进程以有写权限的其他用户运行来实现。但更常见的做法是,确保日志文件不在Web目录树下,并通过文件系统权限防止外部访问。
  2. open_basedir限制

    • 启用并合理配置open_basedir,将PHP脚本可访问的文件系统范围限制在项目必需的目录内。这可以防止路径穿越到系统关键目录或Web目录外的其他区域。
      open_basedir = /var/www/html/:/tmp/
  3. include_path净化

    • php.ini或虚拟主机配置中,将include_path中的.(当前目录)移除,除非有绝对必要。指定明确的、安全的目录列表。
      include_path = “/usr/share/php:/var/www/html/includes”
  4. 禁用危险函数

    • php.inidisable_functions中,禁用不必要的危险函数。虽然error_log本身是运维常用函数,但在严格的生产环境中,可以考虑仅允许通过指定的日志框架(如Monolog)记录日志,并在代码层面禁用原生error_log。不过,更务实的做法是规范其使用,而非一刀切禁用。
      disable_functions = exec,passthru,shell_exec,system,proc_open,popen,…
4.2.2 代码层面加固
  1. 安全的错误处理

    • 使用try-catch结构捕获异常,而不是依赖全局错误处理器和error_log
    • 如果必须记录错误,使用经过安全封装的自定义日志类。该类应:
      • 对日志内容进行严格的过滤和转义,确保不会写入可执行的PHP代码。
      • 使用绝对路径指定日志文件位置,避免使用相对路径。
      • 在写入前,校验目标路径是否在允许的白名单目录内。
      class SafeLogger { private $log_dir = ‘/var/log/myapp/’; public function log($message) { // 过滤或转义PHP标签 $filtered_msg = str_replace(array(‘<?’, ‘?>’), array(‘< ?’, ‘? >’), $message); // 使用绝对路径 $log_file = $this->log_dir . date(‘Ymd’) . ‘.log’; // 可选的路径校验(虽然这里log_dir是固定的) if (strpos(realpath($log_file), $this->log_dir) !== 0) { throw new Exception(‘Invalid log path!’); } file_put_contents($log_file, $filtered_msg . PHP_EOL, FILE_APPEND | LOCK_EX); } }
  2. 文件上传处理强化

    • 白名单+二次渲染:这是目前最有效的防御手段。不仅检查后缀名和MIME类型,更关键的是使用GD库或Imagick对上传的图片进行二次渲染。即读取图片,创建一个新的图片资源,将原图内容画上去,再保存。这样可以彻底剥离任何嵌入在文件元数据(如EXIF)或文件末尾的恶意代码。
      function resizeImage($src_path, $dst_path) { $info = getimagesize($src_path); $type = $info[2]; switch ($type) { case IMAGETYPE_JPEG: $src_img = imagecreatefromjpeg($src_path); break; case IMAGETYPE_PNG: $src_img = imagecreatefrompng($src_path); break; case IMAGETYPE_GIF: $src_img = imagecreatefromgif($src_path); break; default: return false; } $width = imagesx($src_img); $height = imagesy($src_img); $dst_img = imagecreatetruecolor($width, $height); // 处理透明度(PNG/GIF) imagecopyresampled($dst_img, $src_img, 0, 0, 0, 0, $width, $height, $width, $height); imagejpeg($dst_img, $dst_path, 90); // 保存为JPEG,彻底重置文件结构 imagedestroy($src_img); imagedestroy($dst_img); return true; }
    • 文件内容扫描:对保存后的文件,可以使用静态恶意代码扫描引擎(集成ClamAV等)进行二次检查。
    • 随机化文件名与目录:使用不可预测的字符串(如UUID)重命名上传的文件,并避免使用用户输入的任何部分作为文件名。目录结构也可以按日期或其他方式散列,增加攻击者猜测文件路径的难度。
4.2.3 WAF规则补充建议

对于WAF管理员,可以针对此类攻击特征补充规则:

  1. 请求关联分析:尝试建立会话级别的关联规则。例如,如果同一个会话在短时间内,先有一个上传请求(其中文件名或参数包含可疑的PHP标签片段),随后又访问了一个位于非静态资源目录下的.log.txt等文本文件,则可以产生中高危告警。
  2. 错误信息泄露检测:在WAF的响应检测规则中,加入对PHP错误信息、警告信息的识别和过滤,防止其被返回给客户端。同时,可以监控流出流量中是否包含PHP WarningPHP Notice等关键词,这可能是攻击者在探测。
  3. 文件访问模式异常检测:监控对非标准静态文件(如.log.ini.bak等)的访问,特别是当这些请求还携带了POST参数时,应产生严重告警。
  4. 增强的文件上传检测
    • 不仅检查filename,还要检查整个multipart请求体中所有部分的内容,防止在name或其他字段中注入Payload。
    • 对文件内容进行更深入的语法分析,而不仅仅是头字节检查。例如,检测图片文件中是否包含<?php等标签,即使它们位于文件末尾。

5. 从防御到猎杀:构建纵深防御与监控体系

一道CTF题目的价值,在于它揭示了单一漏洞点可能引发的连锁反应。真正的安全建设,需要从这道题中提炼出更深层次的防御思想。

5.1 纵深防御策略落地

  1. 边界防御(WAF/网关):作为第一道防线,拦截已知的攻击模式、恶意IP、异常请求频率。但需明白其局限性,不过度依赖。
  2. 应用自身防御:这是最核心的一环。遵循安全编码规范,对用户输入进行严格的校验、过滤和转义。使用参数化查询防SQL注入,对输出进行编码防XSS,对文件上传采用“白名单+二次渲染”组合拳。像本题中的错误日志路径,必须在代码或配置中写死为绝对路径,并确保其在Web目录之外。
  3. 服务器与环境加固
    • 最小权限原则:运行PHP-FPM或Apache的进程用户,应仅拥有必要目录的读写权限。例如,Web目录只读(uploads/子目录可写),日志目录只写。
    • 安全配置:定期审计php.ininginx.conf.htaccess等配置文件,关闭不必要的功能(如allow_url_fopenallow_url_include),设置严格的open_basedir
    • 容器化与隔离:使用Docker等容器技术,将应用封装在独立的环境中,通过命名空间、cgroups等机制实现文件系统、网络、进程的隔离,即使被攻破,影响范围也有限。
  4. 运行时保护(RASP):在应用内部嵌入保护模块,监控关键函数(如evalsystemfile_put_contents)的调用栈、参数和上下文。当发现error_log函数试图向Web目录下的.php文件写入内容时,RASP可以实时拦截并告警。这能有效防御未知的利用手法。

5.2 监控与应急响应

  1. 日志集中与分析:将Web访问日志、应用日志、系统日志、WAF日志全部收集到SIEM(安全信息和事件管理)平台。利用关联规则,去发现诸如“上传可疑文件”后“访问日志文件”的异常序列。
  2. 文件完整性监控(FIM):对Web目录下的文件,特别是uploads/目录和可能的日志目录,进行监控。当有新的.php.jsp等可执行文件被创建,或现有文件被修改(尤其是追加了可疑内容)时,立即告警。
  3. ** webshell检测**:定期使用静态扫描工具(如河马、D盾)或动态流量分析手段,扫描Web目录中是否存在webshell。也可以部署HIDS(主机入侵检测系统)监控进程行为,发现异常的命令执行。
  4. 应急响应预案:一旦发现此类攻击,响应流程应包括:立即隔离服务器或暂停上传功能;排查日志定位攻击入口和攻击者IP;检查文件系统,清除恶意文件;分析漏洞根因,修复代码和配置;最后才是恢复服务。

6. 总结与个人实战心得

回过头看CISCN 2024的这道题,它与其说是在考察一个具体的PHP漏洞,不如说是在考察选手的攻击面发现能力知识串联能力。从文件上传这个点,联想到错误处理,再关联到PHP配置,最终利用环境特性达成RCE。这种“迂回战术”在真实网络攻击中极为常见。

在防守端,给我的最大启示是:安全是一个链条,最薄弱的一环决定了整体强度。你可能有最严格的WAF,但如果开发人员在某个不起眼的调试功能里写了一句不安全的error_log,所有的前端防御都可能被绕过。因此:

  • 对开发者:安全意识培训至关重要。要理解“数据即代码”的危险性,任何用户输入、任何写入文件的操作,都必须慎之又慎。使用安全的编程模式,避免“魔幻字符串”式的配置和路径拼接。
  • 对运维与安全工程师:配置安全与代码安全同等重要。上线前的安全基线检查必须包含php.ini等运行时配置的审计。监控体系要能覆盖攻击链的多个环节,而不是只看单点。
  • 对所有人:保持对新技术、新漏洞的好奇心。CTF题目是很好的练兵场,它把复杂的现实攻击简化、抽象,让我们能在短时间内看到攻击与防御的逻辑本质。把从中学到的思路,应用到日常的代码审查、架构设计和应急演练中,才能构筑起真正有效的防御。

最后,分享一个我在代码审计时的小习惯:每当在代码里看到file_put_contentsfwriteerror_loginclude/require(参数部分可控)这些函数时,我都会停下来,多问几个问题:这个路径是绝对路径吗?用户能影响它吗?写入的内容过滤了吗?写入的位置Web能访问吗?这个习惯帮我发现了不止一个潜在的高危漏洞。安全很多时候就是多一点点的“不信任”和“穷追猛打”。