所有外部输入必须经存在性判断、类型校验、内容过滤三道防护:用 filter_input() 替代裸访问 $_GET/$_POST,输出 HTML 前用 htmlspecialchars() 转义,JSON/XML 等非标准编码需手动解析并校验,敏感字段禁止任何过滤。

$_GET 和 $_POST 不能直接读,必须加防护层
直接写 $_GET['id'] 或 $_POST['email'] 是危险且易崩溃的写法。PHP 不会自动帮你判断参数是否存在、是否为空、是否被篡改,出错时只抛 Notice: Undefined index,而 XSS、SQL 注入、整型溢出等问题就藏在这些“看起来没问题”的取值里。
核心原则:所有外部输入都视为不可信,必须经过存在性判断 + 类型校验 + 内容过滤三道关卡。
- 用
isset()或空合并运算符??判断键是否存在(避免 Notice) - 用
filter_input()替代裸数组访问,它内置类型转换与验证逻辑,比如filter_input(INPUT_GET, 'page', FILTER_VALIDATE_INT)返回false而不是字符串"abc" - 对输出到 HTML 的内容,必须用
htmlspecialchars($value, ENT_QUOTES, 'UTF-8')转义,不能依赖过滤函数“顺便”防 XSS - 不要用
$_REQUEST——它受php.ini中request_order控制(默认"GP"),GET 参数可能意外覆盖 POST 值,权限校验字段尤其危险
GET 数据要特别防乱码和 URL 注入
$_GET 来自 URL,天然暴露、编码不可控、长度受限,浏览器地址栏粘贴或分享链接时极易带非法字符或未编码中文。Apache + mod_php 环境下常见 $_GET['q'] 是乱码,本质是客户端未做 urlencode(),而 PHP 没做解码或校验。
正确做法不是“修复乱码”,而是绕过它:
立即学习“PHP免费学习笔记(深入)”;
- 前端提交前确保 URL 参数已
encodeURIComponent()(JS)或urlencode()(PHP 生成链接时) - PHP 端不依赖
mb_convert_encoding()猜测编码,直接用filter_input(INPUT_GET, 'q', FILTER_SANITIZE_FULL_SPECIAL_CHARS)清理后再转义输出 - 分页、排序等参数哪怕只是数字,也必须校验:
$page = filter_input(INPUT_GET, 'page', FILTER_VALIDATE_INT) ?: 1;,拒绝?page=1%00<script></script>这类混合 payload - 绝对禁止把
$_GET值拼进 SQL 或直接echo,哪怕它看起来只是?id=123
POST 数据要区分编码类型,别漏掉 raw body
$_POST 只解析 application/x-www-form-urlencoded 和 multipart/form-data 编码的数据。如果前端发的是 application/json、text/xml 或自定义格式,$_POST 就是空的——这不是 bug,是设计如此。
此时必须手动读原始请求体:
- 用
file_get_contents('php://input')获取 raw body(注意:不能用于multipart/form-data,会冲突) - JSON 请求需先
json_decode(file_get_contents('php://input'), true),再对结果做filter_var_array()批量校验 - XML 请求用
simplexml_load_string()解析后,字段仍需单独过滤,不能认为“解析过了就安全” - 表单含文件上传时,
$_FILES是独立数组,$_POST不包含文件字段;且必须检查$_FILES['avatar']['error'] === UPLOAD_ERR_OK,否则可能拿到空文件或临时路径错误
密码、token、敏感字段必须原样取值,禁止过滤
密码字段绝不能走 filter_input() 或 htmlspecialchars(),任何“清理”都可能破坏哈希或加密逻辑。同理,CSRF token、JWT payload、加密密钥也不能被截断、转义或强制类型转换。
正确姿势只有两个动作:
- 存在性判断:
$password = $_POST['password'] ?? '';或isset($_POST['password']) ? $_POST['password'] : ''; - 后续立即交给
password_hash($password, PASSWORD_ARGON2ID)处理,中间不做任何字符串操作 - 若需校验密码强度(如至少 8 位+大小写字母+数字),应在 hash 前用正则或
password_get_info()辅助判断,而不是靠过滤函数“修正”用户输入 - 对
$_POST['csrf_token'],只比对是否等于 session 中存储的值,不 trim、不 htmlspecialchars、不 intval
最常被忽略的一点:filter_input() 的返回值可能是 false、null 或字符串,但很多开发者直接拿它拼 SQL 或传给 PDO —— 必须显式判断失败情况并中断流程,否则 false 变成空字符串或 0,悄悄绕过校验。



















