发布于14小时前14小时 本文记录一种在授权渗透测试中较常见的 WordPress 插件漏洞:插件提供上传接口,但仅依赖前端限制或未校验当前用户权限,导致未登录用户或低权限用户可以上传任意文件。该问题的实际风险取决于上传目录是否可执行 PHP、Web 服务器是否允许脚本解析,以及文件是否能够被外部访问。验证时应使用隔离测试环境和无害标记文件,不要上传 WebShell、反弹脚本或其他可执行载荷。漏洞确认的目标是证明“权限校验缺失”和“文件类型校验可绕过”,而不是获得不必要的持久化权限。一、典型场景目标站点安装了一个自定义媒体、头像或表单附件插件,接口类似 /wp-admin/admin-ajax.php?action=plugin_upload。插件代码只检查了 $_FILES 是否存在,或者仅依赖 JavaScript 将文件选择框限制为图片类型,但服务端没有调用 WordPress 的权限和文件类型校验函数。常见危险代码如下:<?php add_action('wp_ajax_nopriv_plugin_upload', 'plugin_upload'); add_action('wp_ajax_plugin_upload', 'plugin_upload'); function plugin_upload() { if (empty($_FILES['file'])) { wp_send_json_error('missing file'); } $dir = WP_CONTENT_DIR . '/uploads/plugin/'; move_uploaded_file($_FILES['file']['tmp_name'], $dir . $_FILES['file']['name']); wp_send_json_success(); } 这里至少存在四个问题:允许未登录用户访问处理函数;没有检查用户是否具备上传权限;直接使用客户端提供的文件名;没有使用 WordPress 的安全上传接口;上传目录如果位于可执行路径,可能进一步演变为远程代码执行。二、问题定位与代码审计审计插件时,优先搜索上传入口、AJAX action 和文件移动函数:grep -RInE "wp_ajax_nopriv|wp_ajax_|move_uploaded_file|media_handle_upload|wp_handle_upload|\$_FILES" wp-content/plugins/target-plugin/重点检查以下位置:是否注册了 wp_ajax_nopriv_*,该钩子表示未登录用户也可调用接口。处理函数中是否调用 current_user_can()、check_admin_referer() 或等效的 nonce 校验。是否直接拼接 $_FILES['name']、$_POST['filename'] 到文件路径。是否只根据扩展名或请求头判断文件类型,而没有使用服务端 MIME 检测。上传目录是否位于 wp-content/uploads,且 Web 服务器对其中的 PHP 文件启用了脚本解析。注意:nonce 不是权限控制。WordPress nonce 主要用于防止跨站请求伪造,不能单独证明调用者具备上传权限。即使请求包含有效 nonce,也必须执行能力检查。三、授权环境中的安全验证验证前先确认测试范围、目标域名和允许的请求类型。建议准备一个不包含 PHP 代码的文本文件,例如:printf 'authorized-upload-test-2025\n' > marker.txt sha256sum marker.txt如果接口参数已经通过代码审计确认,且测试账号和测试范围均得到授权,可使用无害文件进行请求验证:curl -i -k -X POST \ -F 'action=plugin_upload' \ -F '[email protected];type=text/plain' \ ' HTTP 状态码。应同时记录:响应是否返回了可访问的文件路径或文件 URL。服务器是否生成了新的上传文件。请求是否在未携带登录 Cookie 的情况下成功。上传文件是否能够通过浏览器或 curl 读取。服务端日志中是否记录了该请求和最终文件路径。如果接口要求 nonce,应使用测试账号在合法流程中获取 nonce,再验证低权限账号是否能够调用。不要通过暴力猜测或绕过生产环境认证来获取令牌。四、确认是否存在脚本执行风险在没有明确书面授权的情况下,不应上传 PHP 文件进行执行测试。可先通过配置和静态分析判断风险:# Nginx 配置检查 nginx -T 2>/dev/null | grep -nE 'location|fastcgi_pass|wp-content/uploads' # Apache 配置及上传目录规则检查 find /var/www -path '*/wp-content/uploads/*' -type f \( -name '.htaccess' -o -name '*.conf' \) -print grep -RInE 'AddHandler|SetHandler|FilesMatch|php-fpm' /etc/apache2 /etc/httpd 2>/dev/null如果上传目录允许 PHP-FPM 处理,且攻击者能控制文件内容,就不应继续进行执行验证,应立即停止测试并按高风险漏洞处理。对于必须确认的场景,应在离线克隆环境中使用固定的无害响应程序,并由系统所有者执行复核。五、日志与留痕排查漏洞验证期间应保存时间、源 IP、请求方法、接口路径、响应状态和生成文件名,便于复盘。Linux 环境可以使用以下方式快速定位:# Nginx 访问日志 awk '$7 ~ /admin-ajax.php|plugin_upload/ {print}' /var/log/nginx/access.log # Apache 访问日志 grep -E 'admin-ajax\.php|plugin_upload' /var/log/apache2/access.log # 查找近期新增的上传文件 find /var/www/html/wp-content/uploads -type f -mmin -60 -printf '%TY-%Tm-%Td %TH:%TM:%TS %p\n' | sort重点关注未登录请求、异常 User-Agent、短时间内大量不同扩展名文件、文件名包含路径分隔符或双扩展名,以及上传后紧接着访问同一路径的行为。若使用 CDN、WAF 或反向代理,还应对照边缘节点日志,避免只查看源站日志造成误判。六、安全修复示例修复时应同时处理身份、权限、文件类型、文件名和存储位置,而不是只增加扩展名黑名单。一个较安全的 WordPress 处理示例如下:<?php function safe_plugin_upload() { if (!is_user_logged_in() || !current_user_can('upload_files')) { wp_send_json_error('forbidden', 403); } check_ajax_referer('plugin_upload', 'nonce'); if (empty($_FILES['file']) || !empty($_FILES['file']['error'])) { wp_send_json_error('invalid upload', 400); } require_once ABSPATH . 'wp-admin/includes/file.php'; $allowed = array( 'jpg|jpeg|jpe' => 'image/jpeg', 'png' => 'image/png', 'pdf' => 'application/pdf', ); $overrides = array( 'test_form' => false, 'mimes' => $allowed, ); $result = wp_handle_upload($_FILES['file'], $overrides); if (isset($result['error'])) { wp_send_json_error($result['error'], 400); } wp_send_json_success(array('url' => esc_url_raw($result['url']))); } 代码层面还应注意:根据业务需要选择能力,例如普通媒体上传通常使用 upload_files,不要直接信任角色名称。使用随机化后的服务端文件名,避免覆盖已有文件和路径穿越。限制单文件大小、请求体大小和上传频率。对图片进行实际解码或重编码,不能只检查文件扩展名。上传目录禁止执行脚本。Nginx 应避免将该目录交给 PHP-FPM;Apache 可在上传目录配置 php_admin_flag engine off 或禁用相应处理器。插件接口不需要匿名访问时,应删除 wp_ajax_nopriv_* 注册。七、复测与风险判定修复后至少使用三类请求复测:未登录请求、普通低权限账号请求和具备明确上传权限的测试账号请求。前两类应返回 401 或 403,合法账号只能上传允许的文件类型,且服务端生成的文件不能被当作脚本执行。风险判定可以按实际影响区分:仅能上传不可执行的图片或文档,且目录不可列出:通常为中低风险,但仍可能造成存储滥用或钓鱼内容托管。未登录用户可上传任意可访问文件:风险上升,可能导致存储型 XSS、恶意内容分发或敏感信息覆盖。上传目录可解析 PHP,且接口无需认证:属于高危,可能直接导致服务器被接管。总结WordPress 上传漏洞的确认不能停留在“接口返回成功”。漏洞库记录应同时说明入口、认证状态、能力校验、文件类型处理、存储路径、脚本解析配置和日志证据。授权验证优先使用无害标记文件,结合源码、Web 服务器配置和访问日志完成闭环;修复则必须同时补齐权限控制、nonce、服务端类型校验、随机文件名和上传目录隔离,最后用未登录、低权限和合法权限三种身份复测。 代码审计、调用链与关键函数定位示意 网络请求、日志与边界流量分析示意
13小时前13小时 这类问题建议把“能上传”与“能执行”分开验证,避免直接在生产环境放置 PHP 文件。优先在测试副本中使用一个无害的 .txt 文件,确认未登录请求是否能到达上传处理逻辑,并记录响应中的文件路径、文件名和状态码。 审计插件时可以先定位上传入口: grep -RInE 'wp_handle_upload|media_handle_upload|move_uploaded_file|wp_ajax_nopriv|upload_bits|file_put_contents' wp-content/plugins/target-plugin/ 重点检查以下几点: 入口是否同时注册了 wp_ajax_nopriv_*、REST 公共路由或公开表单处理函数; 是否只校验了 nonce。nonce 不是权限控制,必须再校验用户身份和能力; 是否直接信任 $_FILES['type']、原始扩展名或客户端文件名; 上传目录是否位于 Web 根目录下,服务器是否会解析其中的 PHP; 文件名是否可控,是否存在路径穿越、覆盖已有文件或符号链接处理问题。 一个最低限度的上传处理应类似下面这样,具体能力名按业务场景调整: if ( ! is_user_logged_in() || ! current_user_can( 'upload_files' ) ) { wp_send_json_error( 'forbidden', 403 ); } check_ajax_referer( 'plugin_upload', 'nonce' ); if ( empty( $_FILES['file'] ) || UPLOAD_ERR_OK !== $_FILES['file']['error'] ) { wp_send_json_error( 'invalid_upload', 400 ); } $file = $_FILES['file']; $allowed = array( 'jpg|jpeg' => 'image/jpeg', 'png' => 'image/png', 'pdf' => 'application/pdf', ); $check = wp_check_filetype_and_ext( $file['tmp_name'], $file['name'], $allowed ); if ( empty( $check['ext'] ) || empty( $check['type'] ) ) { wp_send_json_error( 'file_type_not_allowed', 400 ); } require_once ABSPATH . 'wp-admin/includes/file.php'; $result = wp_handle_upload( $file, array( 'test_form' => false, 'mimes' => $allowed, ) ); if ( isset( $result['error'] ) ) { wp_send_json_error( $result['error'], 400 ); } 如果插件只是保存附件,最好交给 WordPress 的上传 API,不要自行调用 move_uploaded_file()。图片类文件还应使用 wp_getimagesize() 或图像库重新解码,不能仅凭扩展名判断格式。 部署侧也应做第二道防线:对插件自建上传目录禁止脚本执行,Apache 可放置: <FilesMatch "\.(php|phtml|phar|pht)$"> Require all denied </FilesMatch> Nginx 则不要把该目录交给 PHP-FPM,例如: location ^~ /wp-content/uploads/plugin-private/ { location ~ \.(php|phtml|phar)$ { deny all; } } 修复后不要只验证返回 403,还要检查三件事:未登录和低权限用户确实无法进入处理函数;伪造 MIME、双扩展名和超长文件名会被拒绝;历史上传目录中已经存在的脚本文件无法被 Web 服务器执行。最后结合 Web 访问日志和 PHP 日志检索该接口的异常请求,必要时清理旧版本留下的可疑文件。
创建帐户或登录后发表意见