PHP作为广泛使用的服务端脚本语言,在开发Web应用时经常需要处理用户输入,若对输入内容没有做合理的校验,很容易引发各类代码注入问题,其中输入白名单校验是防护这类风险的重要手段。

常见的PHP代码注入场景
PHP中常见的代码注入主要分为几类,不同类型的注入触发逻辑不同,但本质都是非法输入被系统当作可执行代码处理。
- SQL注入:用户输入的内容直接拼接进SQL语句,导致攻击者可以修改SQL逻辑,非法查询、篡改或删除数据库数据。
- XSS注入:用户输入的恶意脚本内容被输出到页面,其他用户访问时脚本会被浏览器执行,窃取用户信息或篡改页面内容。
- 命令注入:用户输入的内容被直接传入系统命令执行函数,攻击者可以拼接额外命令,获取服务器权限或执行恶意操作。
- 代码注入:用户输入的内容被传入
eval、assert等可以执行PHP代码的函数,导致攻击者直接执行自定义PHP代码。
输入白名单校验的核心原则
输入白名单校验的核心逻辑是:预先定义所有合法输入的规则集合,只接受符合规则的输入,不符合规则的一律拒绝,而不是尝试过滤掉非法内容。这种方式比黑名单校验更可靠,因为黑名单很难覆盖所有潜在的非法输入形式。
白名单校验的规则需要根据具体的业务场景制定,比如某个字段只允许输入数字,就只允许0-9的字符;某个字段只允许选择预设的选项,就只允许选项对应的值,不允许其他内容。
PHP中输入白名单校验的实现方法
1. 基础字符串类型校验
对于只允许特定字符类型的输入,可以使用PHP内置的过滤函数或者正则匹配实现白名单校验。比如只允许输入字母和数字的用户名,可以通过以下方式校验:
<?php
/**
* 校验用户名是否符合白名单规则:只允许大小写字母和数字,长度6-20位
* @param string $username 用户输入的用户名
* @return bool 校验通过返回true,否则返回false
*/
function check_username_whitelist($username) {
// 正则匹配:只允许a-z、A-Z、0-9,长度6到20位
$pattern = '/^[a-zA-Z0-9]{6,20}$/';
if (preg_match($pattern, $username)) {
return true;
}
return false;
}
// 测试用例
$test_username1 = 'test123';
$test_username2 = 'test@123';
var_dump(check_username_whitelist($test_username1)); // 输出bool(true)
var_dump(check_username_whitelist($test_username2)); // 输出bool(false)
?>
2. 枚举类型输入校验
对于只能从预设选项中选择的输入,比如用户角色、订单状态等,直接判断输入值是否在预设的白名单数组中即可:
<?php
/**
* 校验用户角色是否符合白名单规则
* @param string $role 用户输入的角色值
* @return bool 校验通过返回true,否则返回false
*/
function check_role_whitelist($role) {
// 预设的合法角色白名单
$allowed_roles = ['admin', 'editor', 'viewer'];
if (in_array($role, $allowed_roles, true)) {
return true;
}
return false;
}
// 测试用例
$test_role1 = 'admin';
$test_role2 = 'super_admin';
var_dump(check_role_whitelist($test_role1)); // 输出bool(true)
var_dump(check_role_whitelist($test_role2)); // 输出bool(false)
?>
3. 数字范围校验
对于必须是数字且在特定范围的输入,比如年龄、分页页码等,可以先判断是否为数字,再判断是否在合法范围内:
<?php
/**
* 校验年龄是否符合白名单规则:只能是18-60之间的整数
* @param mixed $age 用户输入的年龄
* @return bool 校验通过返回true,否则返回false
*/
function check_age_whitelist($age) {
// 先判断是否为整数
if (!is_int($age) && !ctype_digit((string)$age)) {
return false;
}
$age_int = (int)$age;
// 判断是否在18到60的范围内
if ($age_int >= 18 && $age_int <= 60) {
return true;
}
return false;
}
// 测试用例
$test_age1 = 25;
$test_age2 = 15;
$test_age3 = '30';
var_dump(check_age_whitelist($test_age1)); // 输出bool(true)
var_dump(check_age_whitelist($test_age2)); // 输出bool(false)
var_dump(check_age_whitelist($test_age3)); // 输出bool(true)
?>
4. 结合过滤函数使用
PHP内置的filter_var函数也可以配合白名单逻辑使用,比如校验邮箱、URL等特定格式的输入时,先通过内置过滤器校验格式,再进一步判断是否符合业务白名单:
<?php
/**
* 校验邮箱是否符合白名单规则:格式正确且域名在允许的白名单中
* @param string $email 用户输入的邮箱
* @return bool 校验通过返回true,否则返回false
*/
function check_email_whitelist($email) {
// 先校验邮箱格式是否正确
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
return false;
}
// 提取邮箱域名
$domain = substr(strrchr($email, '@'), 1);
// 允许的邮箱域名白名单
$allowed_domains = ['ipipp.com', 'test.com'];
if (in_array($domain, $allowed_domains, true)) {
return true;
}
return false;
}
// 测试用例
$test_email1 = 'user@ipipp.com';
$test_email2 = 'user@gmail.com';
var_dump(check_email_whitelist($test_email1)); // 输出bool(true)
var_dump(check_email_whitelist($test_email2)); // 输出bool(false)
?>
输入白名单校验的注意事项
在实际使用输入白名单校验时,需要注意以下几点,才能确保校验机制有效发挥作用:
- 白名单规则要尽可能严格,只开放业务必须的输入范围,不要预留过大的宽松空间。
- 校验要在输入的最前端进行,也就是接收用户输入后立即校验,不要等到后续业务逻辑中才处理。
- 所有用户输入都要校验,不管是表单提交、URL参数还是接口传入的内容,不要信任任何外部输入。
- 白名单校验要和输出转义、参数化查询等其他防护措施配合使用,不能仅靠白名单解决所有注入问题。
- 如果输入不符合白名单规则,要直接拒绝并返回明确的错误提示,不要尝试自动修正输入内容,避免修正逻辑引入新的风险。
需要注意的是,输入白名单校验是防范代码注入的第一道防线,但不是唯一防线。比如SQL注入除了做输入校验,还要使用参数化查询;XSS注入除了输入校验,输出到页面时还要做HTML转义,多层防护才能最大程度降低安全风险。