关于 auditing-php-applications
面向 PHP 代码审计者的安全审查技能,将通用漏洞审计方法应用于 PHP 特有的危险点。重点覆盖版本相关行为、框架生态风险及从输入到危险操作的验证。
主要功能
- 01追踪输入到危险操作的路径
- 02检查 PHP 反序列化与对象注入
- 03审查文件包含、包装器及文件上传
- 04识别 SQL 注入与类型比较绕过
- 05结合 PHP 版本验证漏洞影响
适用场景
- 01审计 PHP 应用或插件源代码
- 02检查 WordPress、Magento、Joomla 或 Laravel 项目
- 03调查疑似 RCE、LFI、SQLi 或对象注入
- 04判断用户输入参与的 unserialize 或 == 是否可利用
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ trilwu/ secskills --skill auditing-php-applications安装后在 Claude Code 中输入/auditing-php-applications确认已加载。
详细介绍
审计 PHP 应用
这是构建在 auditing-code-for-vulnerabilities 之上的 PHP 专用层。
将该技能中的方法论——上下文 → 攻击面 → 漏洞类型排查 → 变体分析 → 四问验证关卡——应用于下面的 PHP 危险函数目录。之所以需要此技能,是因为 PHP 有一些任何与语言无关的方法论都无法涵盖的陷阱:某些特性会将文件读取变成远程代码执行,而某些比较规则会让 == 变成身份验证绕过。
本工具集有意不为其他语言提供专门的审计技能,只有这一项例外。 PHP 之所以例外,是因为它在遗留 Web 和 WordPress/Magento 领域占据主导地位,而严重漏洞往往就藏在这些地方;此外,PHP 的动态特性会以审查者必须专门留意的方式出问题。
适用场景
- 审查 PHP 代码库或插件,查找可利用的漏洞
- 审计 WordPress、Magento、Joomla 或 Laravel 应用的源代码
- 追踪 PHP 中疑似的 RCE、LFI、SQLi 或对象注入路径
- 判断用户输入中的
unserialize、include或==是否可被利用
不适用场景
- 通用审计方法,或非 PHP 语言——使用
auditing-code-for-vulnerabilities;此技能以其为前提 - 查找已植入的 Webshell 或后门,而不是漏洞——使用
hunting-web-backdoors - 在找到危险函数后构建反序列化利用链——使用
exploiting-deserialization - 对运行中的应用进行黑盒测试——使用
testing-web-applications - 只审查某项变更的差异——使用
reviewing-code-changes
按漏洞类型分类的危险函数
追踪用户输入(超全局变量、请求头、上传的文件名、曾经由用户输入产生的数据库值)是否到达以下任一危险函数。只有当输入源能够到达危险函数时,才构成漏洞。
远程代码执行
- 直接 eval 危险函数:
eval、assert(字符串参数,PHP 8.0 之前)、preg_replace(使用/e,PHP 7.0 之前)、create_function(PHP 8.0 之前)、mb_ereg_replace/mb_eregi_replace(使用e选项)。 - 命令执行:
system、exec、passthru、shell_exec、反引号、proc_open、popen、pcntl_exec。注意不要混淆escapeshellarg和escapeshellcmd——escapeshellcmd无法防止参数注入。 - 动态调用:
$func()(其中$func来自输入)、call_user_func/call_user_func_array、array_map/usort/preg_replace_callback(使用受用户输入影响的回调)。
对象注入(PHP 特有的漏洞)
- 对任何用户可控数据调用
unserialize()都会导致对象注入。利用方式是构造 POP 链:现有类中的__wakeup、__destruct、__toString或__call魔术方法在对象实例化时执行危险操作。存在漏洞的文件本身可能完全不包含危险代码——利用链组件可能位于框架或依赖项中。 - **
phar://是伪装的 unserialize,但受版本限制。**在 PHP 8.0 之前,对phar://路径执行任何文件操作(file_exists、fopen、include、getimagesize等)都会自动反序列化 Phar 的元数据。因此,如果路径遍历或 LFI 让文件函数访问已上传的.phar文件(或任何包含 Phar 元数据的文件),即使完全看不到unserialize调用,也会导致对象注入。PHP 8.0 移除了这种自动反序列化——现在只有显式调用Phar::getMetadata()才会反序列化。因此,在大量仍在使用的 PHP 7.x 环境中,此漏洞属于严重问题;而在 PHP 8.x 中,除非代码自行调用getMetadata(),否则无需担忧。排除或确认此漏洞前,先检查运行时版本。 - 修复时优先使用
json_decode;对所有非可信数据上的unserialize都应标记,即使看不到明显的利用链组件——下一次更新依赖项时可能就会引入它们。
LFI / RFI 和流包装器
- 将用户输入传入
include、require、include_once、require_once。 - 会彻底改变情况的包装器:
php://filter/...(读取源代码;以及通过convert.iconv/压缩过滤器实现的已知 RCE 利用链)、php://input(类似 RFI 的请求正文包含)、data://(内联载荷)、phar://(对象注入)、expect://、zip://。启用allow_url_include会将 LFI 变成远程 RFI。 - 文件读取危险点(
file_get_contents、readfile、fopen、highlight_file)结合目录遍历会泄露源代码和机密信息。
SQL 注入
- 将字符串拼接的输入传给旧版
mysql_query/mysqli_query。 $wpdb->query("... $var ...")未使用$wpdb->prepare()— 即使存在->prepare,但在占位符之外进行插值,仍然可以被注入。- 使用 PDO/mysqli 时,若通过构造字符串而非绑定参数,则存在风险。对表名/列名使用占位符(这些名称无法绑定)是常见的真实漏洞。
类型转换和魔术哈希身份验证绕过
==和!=是宽松比较。strcmp/hash_equals/===是安全的形式。经典绕过方式:if (md5($input) == $stored),两个输入的哈希值都为0e[0-9]+“魔术哈希”时会比较相等,因为两个数字字符串会按数字比较(0e123 == 0e456→0.0 == 0.0)。- 版本很重要,也可能造成陷阱。 PHP 8.0 更改了数字与字符串的比较规则:
0 == "foo"在 PHP 7 中为true,在 PHP 8 中为false,因此在 PHP 7 上有效的in_array(0, $userStrings)或0 == $token绕过,在 PHP 8 上会失效。但0e魔术哈希绕过在 PHP 8 中仍然有效,因为两边都是数字字符串,仍会按数字比较。排除或确认类型转换漏洞前,请先检查目标 PHP 版本。 - 在 PHP 7 中,将数组传给
strcmp($_GET['x'], $secret)会返回NULL(宽松比较时等于0)——可绕过身份验证;在 PHP 8 中则会抛出TypeError。相同代码,不同版本,结论不同。
超全局变量信任和可变变量
extract($_GET/$_POST/$_REQUEST)允许请求设置任意局部变量 — 覆盖身份验证标志、配置,以及任何尚未初始化的内容。$$var/${$key}可变变量、未传入结果数组的parse_str(会写入当前作用域)、import_request_variables(已在 5.4 中移除)—— 都是带有 register_globals 风格的变量注入方式。
SSRF 和文件上传
- 对用户提供的 URL 使用
file_get_contents/curl/fopen会造成 SSRF — 请通过exploiting-ssrf指引利用分析。 - 文件上传:检查目标位置是否在 Web 根目录之外、扩展名是否不是由攻击者指定(双扩展名、
.phtml、.php5、旧版 PHP 中的空字节),以及服务器是否会执行该类型的文件。在 Web 可访问目录中,若 MIME 已验证但扩展名由攻击者控制,则会导致 RCE。
生态系统背景
- WordPress: nonce 用于防护 CSRF,并非授权机制 — nonce 有效不代表用户有权执行操作;请查找缺失的
current_user_can()检查。插值时必须使用$wpdb->prepare。常见漏洞包括unfiltered_html、edit_*权限,以及未经过身份验证的 AJAX/REST(wp_ajax_nopriv_*、permission_callback => '__return_true')。 - Magento:
unserialize相关历史漏洞较多;布局 XML 注入、管理后台路径和补丁级别都会影响已知 CVE 是否可利用。 - Laravel: 批量赋值(
$fillable/$guarded)、Blade{!! !!}(不转义)与{{ }}(转义),以及泄露的APP_KEY可能导致 Cookie/decrypt对象注入。
报告前验证
应用 auditing-code-for-vulnerabilities 的检查:追踪源到危险点,确认路径可达(身份验证、路由,以及 — 尤其是 — 类型转换和已移除函数相关漏洞所涉及的 PHP 版本),并证明影响,而不是只作断言。无法触达可利用 gadget 的 unserialize,或 PHP 8 上的 /e 正则,属于加固建议,而非严重发现 — 请明确指出是哪一种。
应拒绝的错误说法
- “没有
unserialize,就不会有对象注入。”phar://会通过常规文件函数触发反序列化。检查所有路径可能受用户影响的文件操作。 - “它用了
==,用于哈希检查没问题。” 对哈希使用==会导致魔术哈希绕过。只有hash_equals/===才安全,而且 PHP 8 中仍可绕过。 - “比较漏洞有效,我确认过了。” 是哪个 PHP 版本?
0 == "x"和strcmp(array)在 PHP 8.0 中改变了判定结果。请说明版本。 - “调用了
prepare(),所以查询是安全的。” 只有占位符中的值是安全的。插值的表名/列名,以及拼接在占位符周围的文本,仍然可以被注入。 - “nonce 检查通过了,所以已获授权。” WordPress nonce 是 CSRF 令牌。授权检查是另外的
current_user_can(),你必须找到它。 - “上传会验证 MIME 类型。” MIME 类型可以伪造;如果攻击者能控制扩展名,且该目录会执行 PHP,那么 MIME 类型就无关紧要。
- “这是框架类,漏洞利用链 gadget 不在这里。” POP 链 gadget 本来就存在于依赖项中。易受攻击的文件中没有危险代码很正常,不能据此免责。
阅读外部来源
以 Markdown 格式获取公开公告、规范和供应商报告:
curl -sL "https://defuddle.md/<url>" # scheme in the path is optional
这会剔除网页的多余内容——对于以文字为主的页面,令牌数大约减少 78%——并返回全文而非摘要,因此你可以搜索内容,并相信未找到相关内容的结果。
它不适用于三种情况。获取 JSON 和 API 响应时应直接获取原始内容,因为可读性提取会破坏结构化数据。获取需要身份验证或由 JavaScript 渲染的页面时也应直接获取,因为它会以匿名方式检索页面。切勿通过此方式访问对手的基础设施(钓鱼链接、C2、恶意软件托管)、客户拥有的主机或测试任务 URL——请求会离开你的机器,发送给第三方;如果访问的是正在运行的对手基础设施,还会暴露你的行动,让对方察觉。
有些网站会屏蔽提取器,并返回错误信息而非页面内容——例如 freedesktop.org 返回的 {"error":"Failed to fetch: 418 I'm a teapot"}。这表示获取请求被拒绝,并不表示来源称该内容不存在。在据此得出任何结论前,请直接重新获取该 URL。
参考资料
auditing-code-for-vulnerabilities— 通用方法论,以及本技能所依托的审查关卡hunting-web-backdoors— 搜寻人为植入的恶意代码,而非漏洞exploiting-deserialization— 找到unserialize/phar://漏洞点后,构建 POP 链exploiting-ssrf— 利用用户可控 URL 获取功能testing-web-applications— 对正在运行的应用进行黑盒测试