验证码验证需统一转小写比对、session键名加业务前缀、验证通过后立即unset清理,否则易因大小写差异、键名冲突或残留导致误判或重放攻击。

验证码字符串必须用 strtolower() 统一转小写再比对
用户输入的验证码常含大小写混输(比如把“O”打成“0”,或把“l”当成“1”),而生成时若用 rand(65, 90) 只取大写字母,rand(97, 122) 只取小写,混合后不统一处理就极易误判。直接用 == 比对会因大小写差异失败。
正确做法是:存储进 $_SESSION 前不做转换,但验证时双方都转小写——strtolower($_POST['captcha']) === strtolower($_SESSION['captcha_code'])。这样既保留原始生成逻辑,又规避输入歧义。
- 不要在生成阶段就
strtolower($code)后存入 Session,否则干扰字符、字体渲染差异等可能让图像和 Session 值视觉不一致,调试困难 - 避免用
strcasecmp(),它在 PHP 8.0+ 已标记为 deprecated,且语义不如显式strtolower()清晰 - 若验证码含数字(如
'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'),数字本身无大小写,所以只需对字母部分做归一化,而统一转小写是最简单可靠的兜底方案
$_SESSION 写入前必须调用 session_start(),且不能有输出
常见错误是:在 verify.php 开头漏掉 session_start(),或在它之前有空格、BOM、echo、HTML 标签,导致 header 已发送,Session 无法启动,$_SESSION 写入静默失败,后续验证永远为 null。
验证方法:在 verify.php 开头加 if (session_status() !== PHP_SESSION_ACTIVE) { die('session not started'); },快速定位问题。
-
session_start()必须在任何输出(包括空白符)之前调用,GD 图像输出前尤其敏感 - 如果使用 UTF-8 BOM 编码保存 PHP 文件,即使肉眼看不见开头字符,也会触发 headers already sent 错误
- 某些 IDE 自动生成文件头注释,若注释前有空行,也属于“提前输出”
验证码 Session 键名要唯一,避免被其他模块覆盖
多个功能共用 $_SESSION['code'] 是高频翻车点。PHPCMS v9 就因自定义 session_mysql 类与原生 Session 写入冲突,导致验证码存进去却读不出来。你用的框架、CMS 或公共类库很可能也在写 $_SESSION['code'] 或 $_SESSION['captcha']。
建议用带前缀的键名,例如 $_SESSION['form_login_captcha_code'],配合有效期时间戳一起存:$_SESSION['form_login_captcha_code'] = $code; $_SESSION['form_login_captcha_time'] = time();。
- 不要依赖全局短键名,哪怕只是临时测试——上线后极易被第三方代码覆盖
- 键名中加入业务上下文(如
login、register、resetpwd)可隔离不同表单的验证码生命周期 - PHP 的 Session 序列化默认用
php_serialize,支持嵌套数组,但键名冲突仍是第一风险源
验证通过后必须立即 unset(),不能只 $_SESSION['x'] = null
仅赋值为 null 或空字符串,isset($_SESSION['captcha_code']) 仍返回 true,导致同一验证码被重复使用多次。攻击者可抓包重放已通过的验证码 POST 请求,绕过校验。
正确清理方式只有两种:unset($_SESSION['captcha_code'], $_SESSION['captcha_time']),或更彻底的 $_SESSION = [];(慎用,会清空整个会话)。
- 务必在
if (captcha_ok) { ... }成立分支内执行unset(),不要放在 else 或函数末尾 - 如果验证码用于 AJAX 登录,且登录成功后跳转,注意跳转前已 unset;若跳转失败需重新生成,不能复用旧值
- GD 图像资源(
$image)用完必须imagedestroy($image),否则内存泄漏,但这是性能问题,不影响验证码逻辑正确性
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











