验证码必须“用完即焚”,即无论校验成功或失败,只要进入验证流程,就必须立即执行unset($_SESSION['code'])销毁会话中的验证码值,避免被重复利用。

验证码生成后被多次校验怎么办
PHP验证码一旦生成就该“用完即焚”,否则攻击者可反复提交同一组字符通过验证。常见表现是:用户第一次输错后,第二次再输同样内容反而通过了。
根本原因是验证逻辑里没销毁 $_SESSION['code']。无论比对成功还是失败,只要进入验证流程,就必须立刻清除会话中的验证码值。
- 在比对完成后加
unset($_SESSION['code']),不要只在成功分支里写 - 如果用了自定义键名(如
$_SESSION['vcode']),确保 unset 的键名完全一致 - 避免在验证前做 redirect —— 重定向可能让 session 写入延迟或失效,建议验证和销毁在同一请求内完成
字母验证码区分大小写导致总不匹配
用户看到的是大写 ABCD,但后台存的是小写 abcd,或者反之,结果永远校验失败。这不是 bug,是默认行为未对齐。
ThinkPHP、PHPCMS 等框架默认不区分大小写,但手写逻辑常忽略这点。关键不是“要不要区分”,而是前后端处理方式必须统一。
立即学习“PHP免费学习笔记(深入)”;
- 生成时统一转大写:
strtoupper($code),存入 session 前就规范格式 - 接收用户输入后也立即标准化:
strtoupper($_POST['captcha']) - 避免用
==直接比较,改用===配合标准化,防止类型隐式转换干扰 - 如果用 GD 绘图输出字母,注意字体文件是否支持大小写渲染 —— 某些精简字体只含大写字母,小写会显示为空白
验证码图片返回 500 或空白,但无报错提示
最典型的现象:浏览器 Network 面板里 captcha.php 请求状态是 500 或返回空内容,右键“查看图片”显示损坏图标。问题往往不在 GD 函数本身,而在它前面的输出污染。
GD 图像输出要求响应体**必须是纯二进制图像数据**,任何前置空格、BOM、echo、警告(notice)都会破坏结构。
- 用编辑器(如 VS Code、Notepad++)确认
captcha.php文件保存为 UTF-8 **无 BOM** 格式 - 文件开头第一行必须是
<?php,前面不能有任何空行、空格或 UTF-8 BOM 字节 - 在
session_start()后、图像生成前加ob_clean(),强制清空输出缓冲 - 临时注释掉
header()行,直接运行脚本 —— 如果出现 PHP Warning 或 Notice,说明错误被 header 隐藏了
为什么换浏览器/清缓存后验证码突然能用了
这通常暴露的是 Session 隔离问题:不是验证码逻辑坏了,而是服务端没能把验证码值正确写进当前用户的 Session。
常见根因是 session.save_path 权限不对,或 session ID 在跨请求时丢失(比如前端没带 cookie,或 nginx 配置了 proxy_cookie_path 错误)。
- 检查
session.save_path目录是否存在、是否可写(Linux 下常用/var/lib/php/sessions) - 在验证码生成脚本开头加
var_dump(session_status() === PHP_SESSION_ACTIVE);,确认 session 已激活 - 在生成和验证两个脚本里都打印
session_id(),看是否一致 —— 不一致说明 session 没延续 - 如果用了反向代理,确认
Set-Cookie响应头未被过滤,且Domain属性匹配当前域名
实际修复时,最常被跳过的一步是:验证逻辑执行后没立刻销毁 session 中的验证码。这个动作看似微小,却直接决定验证码能否防住重放攻击。其他问题大多有迹可循,唯独这一条,不加日志几乎无法感知。



















