is_numeric会将'0x23'等合法十六进制字符串判定为true,这是其设计行为而非bug;它按c风格解析字面量,支持0x/0x前缀,但不校验业务所需的十进制格式,易被用于sql注入绕过。

is_numeric 不会把十六进制字符串判定为 false——恰恰相反,它会把形如 '0x23'、'0XABC' 这样的字符串判定为 true。
这是该函数最常被误用、也最容易引发安全问题的核心点。
is_numeric('0x23') 返回 true 是明确行为,不是 bug
is_numeric 的文档和实际实现都明确支持十六进制字面量(以 0x 或 0X 开头的字符串)作为“数字字符串”。它不校验是否符合业务所需的十进制整数/浮点格式,只做宽松语法识别。
-
is_numeric('0x1F')→true -
is_numeric('0Xdeadbeef')→true -
is_numeric('0x')→false(不完整,被拒绝) -
is_numeric('0xG1')→false(含非法字符)
这跟 intval($s, 0) 的解析逻辑一致:PHP 内部按 C 风格字面量规则解析,0x 开头即触发十六进制路径。
为什么你看到 is_numeric('0x...') 返回 false?
常见真实原因有以下几种:- 字符串被 URL 解码或过滤意外截断,例如传入的是
'0x23%00abc',%00会让is_numeric直接返回false(它对空字节敏感) - 字符串开头有不可见空白(如
"\t0x23"),is_numeric会跳过首个空白后继续判断,但若空白后紧跟0x,仍可能为true;而如果空白在中间(如'0 x23'),则返回false - 你实际测试的是带引号的字符串字面量,比如写成了
is_numeric(" '0x23' ")(含单引号),那整个字符串就不是合法十六进制字面量了 - 使用了错误的编码或传输方式导致
0x被转义、大小写混杂(如'0X'没问题,但'0X '尾部空格会导致false)
is_numeric 在 SQL 场景中为何危险?
它和拼接 SQL 一起用时,漏洞链很直接:- 用户提交
s=0x27204f5220313d31(即' OR 1=1的 hex) -
is_numeric($_GET['s'])返回true,放行 - 拼接到查询中:
INSERT INTO t(v) VALUES($s) - MySQL 把
0x27204f5220313d31当作二进制字面量自动转成字符串,等效于插入' OR 1=1
这种绕过不需要任何特殊技巧,就是 is_numeric 的设计本意被错用。
替代方案:按需选择严格校验
- 要十进制整数?用filter_var($s, FILTER_VALIDATE_INT)
- 要十进制浮点?用 filter_var($s, FILTER_VALIDATE_FLOAT)
- 要允许科学计数法但拒绝十六进制?正则:/^[+-]?[0-9]*\.?[0-9]+([eE][+-]?[0-9]+)?$/
- 要兼容空格前后?先 trim(),再校验
别依赖 is_numeric 做输入过滤,它只回答“这个字符串看起来像某种数字字面量吗”,不回答“它是否安全可用”。
真正容易被忽略的点是:十六进制识别是硬编码在 PHP 底层词法分析里的,无法关闭,也无法通过配置禁用。只要字符串满足 0x + 有效十六进制字符,就过得了 is_numeric。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











