PHP安全审计需人工验证输入处理、输出转义及敏感操作隔离;SQL注入重点查$_GET/$_POST拼接、危险函数调用及宽字节绕过;XSS须按HTML/JS/属性/富文本上下文分别防护;filter_input()仅作初步校验,不可替代上下文防护;配置文件、错误信息、phpinfo()、.git等易被忽略的泄露点须严格管控。

PHP安全审计不是“扫一遍工具就完事”,核心在于确认输入是否被可信方式处理、输出是否被正确转义、敏感操作是否有隔离机制。工具只能帮你定位可疑点,真正判断是否可利用得靠人工验证。
怎么快速定位 SQL 注入风险点
别指望工具自动识别所有拼接逻辑。重点盯三类代码:直接用 $_GET、$_POST 或 $_REQUEST 变量拼进 SQL 字符串的地方;调用 mysql_query()(已废弃)、mysqli_query() 且第二个参数含变量的;用 eval()、create_function() 动态构造查询的。
- 看到
mysqli_real_escape_string()不代表安全——它依赖连接字符集,若数据库设为gbk,仍可能宽字节绕过 - 用
PDO::quote()也不等于防住注入,它只是加引号+转义,不能替代预处理 - 真正可靠的只有预处理语句:
$stmt = $pdo->prepare("SELECT * FROM user WHERE id = ?")+$stmt->execute([$id]) - 检查
filter_input()是否只用于校验,没配合绑定使用——校验 ≠ 防注入
XSS 漏洞常在哪些输出位置暴露
几乎所有把用户可控数据直接塞进 HTML 的地方都危险,但最容易被忽略的是 JavaScript 上下文和属性值场景。
-
echo $_GET['name']放在<div></div>里?htmlspecialchars()是底线,但必须带ENT_QUOTES和明确指定'UTF-8' -
echo "var name = '{$_GET['name']}';"这种 JS 内联赋值?htmlspecialchars()失效,要用json_encode()包裹并加JSON_HEX_TAG | JSON_HEX_AMP -
<a href="<?php echo $_GET['url']; ?>">?属性值未闭合时,攻击者可提前退出属性并插入事件,必须用filter_var($url, FILTER_VALIDATE_URL)校验或白名单协议限制 - 富文本输出不能只靠
htmlspecialchars(),得用HTMLPurifier这类专用库,否则<img src=x onerror=alert(1)>照样执行
为什么 filter_input() 不是万能过滤方案
filter_input() 看起来很规范,但它只解决“输入阶段”的类型校验和基础清洗,不涉及上下文语义,更不保证后续使用安全。
立即学习“PHP免费学习笔记(深入)”;
- 用
FILTER_SANITIZE_STRING(PHP 8.1 已弃用)会删掉标签但保留属性,<a href="javascript:alert(1)">依然存活 -
FILTER_VALIDATE_EMAIL只校验格式,不防admin@example.com%00<script>...这类截断+XSS组合 - 它不处理变量重赋值:比如
$a = filter_input(INPUT_GET, 'id', FILTER_SANITIZE_NUMBER_INT); $b = $a . '_suffix';,后面拼进 SQL 或 JS 就又回到原始风险 - 真正该做的是:输入阶段用
filter_input()做初步校验,存储/传输/输出各环节按需再做对应上下文处理,不能“一滤永逸”
配置文件和敏感信息最容易被忽略的泄露路径
很多团队花大力气修 SQL 注入,却让 config.php 直接躺在 Web 可访问目录下,或者用 .env 文件但没配 Web 服务器禁止访问规则。
- 检查
config.php是否在DocumentRoot下,如果是,立刻移到上层目录,比如/var/www/config/并确保 Web 服务无法映射到该路径 - 确认
display_errors = Off且log_errors = On,否则数据库连接失败时可能把完整路径、账号密码打在错误页上 - 看
phpinfo()是否暴露——上线环境必须删除或禁用,它会泄露所有扩展、路径、环境变量 - 检查
.git目录是否被部署上去,里面可能残留.env或调试配置,Nginx/Apache 默认不屏蔽.git访问
真正的难点不在找到漏洞,而在于判断某个看似无害的变量传递链是否最终抵达了危险函数——比如从 $_COOKIE 到 unserialize(),中间隔了七八个函数调用,静态扫描很难覆盖全。这时候必须手动跟踪变量流向,看有没有任何一处跳出了预期上下文。



















