DedeCMS 后台验证码错误终极排查指南:从 GD 库到 sessions_xxx 升级残留全覆盖

DedeCMS 后台验证码不显示或永远输错,背后可能是 GD 缺失、data/sessions 目录权限错、cfg_domain_cookie 改动残留多个 sessions_xxx 目录、PHPSESSID cookie 跨域丢失。本文按图片不显示、提交错、版本兼容三类故障分别给出排查命令与修复代码,覆盖织梦 5.7 各 SP 版本与 PHP 7+ 环境。

张文保 更新 26 分钟阅读 1,883 阅读

DedeCMS 后台登录验证码错误这个问题,几乎每个搞过织梦运维的人都撞过一两回。最难的不是修,是先判断到底是哪一类故障——同样一句“验证码不对”背后可能是会话目录权限错了、可能是 GD 库压根没装、可能是升级遗留了一个名字带乱码后缀的会话目录、也可能纯粹是浏览器 Cookie 被 SameSite 限制。本文把 DedeCMS 5.7 / V5.7 SP1 / V5.7 SP2 / V5.7 UTF8 各版本下我亲自处理过的验证码故障案例汇总成一份排查清单,配套给到具体代码改动与命令。

DedeCMS 验证码工作原理速览

要修验证码,得先知道 DedeCMS 这套机制是怎么走的。整个链路有四步:

  1. 浏览器请求登录页 dede/login.php,页面 HTML 里有一个 <img src="../include/vdimgck.php"> 的标签。
  2. 浏览器请求 vdimgck.php,这个脚本随机生成 4 位验证码字符串,调用 GD 库画到 PNG 图里返回;同时把字符串写进 PHP session 的 $_SESSION['svali']
  3. 用户填完表单提交,login.php 拿表单里的 validate 与会话里的 svali 比较,相同就放行。
  4. 会话依赖 PHP session,session 文件存放路径由 data/common.inc.phpinclude/common.inc.php 里的 session_save_path 决定。

这条链条上任何一个环节断掉都会触发“验证码错误”或“不显示”。下面按断点位置分别讲。

故障一:验证码图片根本不显示

登录页加载完,输入框旁边只有一个红叉或裂图占位符,这是图片请求本身失败了。打开浏览器开发者工具的 Network 面板,找 vdimgck.php 那一行,看 HTTP 状态码:

500 Internal Server Error

大概率是 PHP GD 扩展没装或没启用。在服务器执行 php -m | grep -i gd,如果没有 gd 输出,就是缺扩展。修复手段按系统不同:

  • CentOS 7:yum install php-gd,然后 systemctl restart php-fpm(或 httpd)。
  • Ubuntu/Debian:apt install php7.4-gd(按你的 PHP 版本)。
  • 宝塔面板:软件商店 - PHP - 设置 - 安装扩展,勾选 gd 后重启 PHP。
  • Windows IIS:打开 php.ini,把 ;extension=gd 前面的分号去掉;老版本 Windows 上是 extension=php_gd2.dll。重启 IIS。

一个容易忽略的细节:如果你装了多个 PHP 版本(宝塔常见情况),网站绑定的 PHP 版本和命令行 php -m 看到的可能是两个不同的 ini 文件。修改前先确认网站绑定的具体版本。

403 Forbidden

vdimgck.php 文件存在但被拒绝访问。两个常见原因:

  1. nginx/apache 配置里把 include/ 目录整个 deny 了。多数织梦安全加固教程会建议“锁死 include 目录禁止外部访问”,但如果做到把 vdimgck.php 也锁了就会触发这个问题。修复方式是改 nginx 配置加白名单:location = /include/vdimgck.php { allow all; }
  2. SELinux 策略阻拦。CentOS 7 默认开启的 SELinux 会把网站目录标记为 httpd_sys_content_t,但如果你拷文件进来时打破了这个标签,httpd 进程访问就会被拒。修复:restorecon -Rv /var/www/html/include/

200 OK 但图片仍然不显示

状态码正常但浏览器渲染不出来。点开 Response 面板看返回内容:

  • 如果是一段 PHP Warning 或 Notice 文本(比如“Cannot modify header information”),说明 PHP 在 imagepng() 之前已经输出了内容,破坏了 PNG 头。常见原因是某个被 require 的文件存了 BOM。用 dos2unix 或专门的 BOM 清理工具处理 data/common.inc.phpinclude/common.inc.php
  • 如果返回是空的,可能是 imagepng 输出失败,去看 PHP error_log,搜 vdimgck.php 关键词。

故障二:图片显示,但提交永远报“验证码错误”

这是最常见也最难判断的一种。验证码画出来了,输得也对,但 login.php 一比对就说错。本质都是“图片端写的 svali”和“登录端读到的 svali”不在同一个 session 里。原因可以拆成五种:

原因 A:session.save_path 目录没有写入权限

php.ini 里 session.save_path = "/tmp"(Linux 默认)或 "C:\Windows\Temp"(Windows 默认)。如果目录不存在或当前 PHP 进程对它没有写权限,session_start() 会失败,写入的 svali 拿不到。

排查命令:

php -r 'echo session_save_path();'
ls -ld $(php -r "echo session_save_path();")

看 owner 与 mode。CentOS 7 上 php-fpm 默认运行用户是 apache 或 nginx,必须保证目录至少有 drwx-wx-wx(1733 或 1777)权限。

原因 B:DedeCMS 自定义的 session 路径权限不对

这是织梦特有的一个坑。include/common.inc.php 里有这么一段:

$enkey = substr(md5(substr($cfg_domain_cookie,0,5)),0,10);
$sessSavePath = DEDEDATA."/sessions_{$enkey}";
if ( !is_dir($sessSavePath) ) mkdir($sessSavePath);
if(is_writeable($sessSavePath) && is_readable($sessSavePath))
{
    session_save_path($sessSavePath);
}

它会根据域名 cookie 名称的 md5 值生成一个像 sessions_8ab3842ff8 这种名字的目录,然后切换 session 路径。这套设计的初衷是让多个站点共享 PHP-FPM 也不会串 session,但在以下三种场景会反复出问题:

  1. data/ 目录权限是 755 或 700,PHP 进程没法在里面 mkdir,路径切换失败回退到默认 /tmp,但默认 /tmp 也可能被其它原因清空。
  2. 站点经历过迁移,data/ 目录从一台主机搬到另一台,新主机的 PHP 进程用户与旧机不同,sessions_xxx 目录的 owner 还是旧用户,新进程 is_writeable 返回 false,路径切换失败但与默认 /tmp 也不连通。
  3. 同一站点先后用过 cfg_domain_cookie 不同的设置(比如先空再填了主域名),之前生成的 sessions_xxx 目录留在那里没人理,新生成另一个名字。重复的目录会让维护者困惑哪个才是当前用的。

修复方式分两类:

临时修复(最快):chmod -R 755 data/chmod -R 777 data/sessions_*,登录看是否好转。如果一时找不到对应的 sessions_xxx 目录,就把 data/ 下所有 sessions 开头的目录全部 777 一次。

根治:把 include/common.inc.php 里 $sessSavePath = DEDEDATA."/sessions_{$enkey}" 改成 $sessSavePath = DEDEDATA."/sessions",永远走单一目录。改完之后建 data/sessions 目录并赋权。这种改法最大的好处是后续无论域名怎么调整,session 都不会因为目录名漂移而失效。

原因 C:升级遗留的 sessions_xxx 与新 sessions_yyy 互相覆盖

原文里提到过这个场景,再展开一下:DedeCMS 从 5.6 升到 5.7 SP2 这个跨版本路径上,common.inc.php 的 enkey 算法没变,但 cfg_domain_cookie 默认值有差异。结果就是升级后会用新的 sessions_yyy 目录,老的 sessions_xxx 还在那里。

问题出在 DedeCMS 的某些场景(比如某些插件、某些 hooks)会硬写 svali 到老目录。表现就是“图片画的验证码写到老目录,登录读 svali 时去新目录拿——空的,永远对不上”。

判断方式:登录失败后立刻去 data/ 目录看哪个 sessions 目录有最新文件。

find data/sessions* -type f -mmin -2 -ls

如果命中两个不同 sessions_xxx 目录都有最近 2 分钟内修改的文件,就是双写场景,必须把 common.inc.php 改成单一目录解决。

原因 D:cookie 域写错或 SameSite 拦了

PHP session 的会话 ID 通过 PHPSESSID cookie 在浏览器与服务器间传递。cookie 域 path 写错或者 SameSite 限制都会让浏览器不再发送 PHPSESSID,下一次请求 server 就拿不到 session。

具体几种触发:

  • 站点从 http 切换到 https 但 cookie 还设了 secure=false,跨协议被丢。
  • 站点用了多个二级域名(比如登录页在 admin.example.com,验证码图请求在 www.example.com 的 vdimgck.php),如果没显式 setcookie 的 domain=.example.com,cookie 不跨子域。
  • Chrome 80+ 默认 SameSite=Lax,在某些跨站嵌入场景(iframe 后台管理)会丢 cookie。

排查方法:浏览器开发者工具 Application 面板,看 Cookies,确认 PHPSESSID 在登录页与 vdimgck.php 请求里都存在且值相同。

原因 E:浏览器禁用了 Cookie 或开了无痕

少数情况下用户自己关掉了 cookie。换一个浏览器或开普通窗口验证。

故障三:磁盘空间满导致 session 写入失败

这个原因大家不太想得到,但实战里出现频率不低。空间满时 PHP 创建 session 文件会失败,svali 就写不进去。

排查命令:

df -h
du -sh /tmp /var/www/html/data/sessions* 2>/dev/null

常见塞满 /tmp 的元凶:

  • old session 没被 GC,phpinfo 里 session.gc_probability 默认 1,session.gc_divisor 默认 1000,意味着 1/1000 的请求触发清理。低流量站点几年不清一次,几百万个 sess_xxx 文件堆在 /tmp。
  • php-fpm 的 access.log、error.log 在 /tmp 滚到几个 GB。
  • 系统 /tmp 被设为 tmpfs(内存盘)且尺寸只有几百 MB,写满立刻失败。

解决方式:定期 find /tmp -name 'sess_*' -mtime +1 -delete,或者把 session 改用 redis、数据库存。

故障四:插件、二开把 session 启动顺序搅乱

DedeCMS 二次开发时常遇到的坑:某个插件在 require_once common.inc.php 之前就 echo 了内容,导致后续 session_start() 报“session already sent”错误。如果你刚装了某个第三方插件之后才出现验证码问题,先停掉这个插件验证一下。

排查方法:临时把 include/common.inc.php 第一行加 error_reporting(E_ALL); ini_set('display_errors', 1);,登录失败时看屏幕上是否有 Warning。修完后记得改回去。

故障五:MySQL 连接慢导致 vdimgck.php 超时

不太常见但确实见过:vdimgck.php 这个脚本本身不依赖 MySQL,但它会 require common.inc.php,common.inc.php 会建立 MySQL 连接。如果 MySQL 连不上,整个脚本超时被 kill,浏览器看到的是断流图片。

排查:服务器手动跑 php /var/www/html/include/vdimgck.php > /tmp/test.png,看是否能正常出图,多大延时。

修复后仍未解决的兜底方案:直接关闭验证码

如果排查了一圈还是修不好(多见于客户托管主机不给 root 权限,php.ini 改不了),可以直接关闭后台验证码作为应急。原文里给的两段改法仍然可用,但必须配合下面的安全加固,不然你的后台就裸奔了。

关闭代码改动(DedeCMS 5.7 适用)

第一步,编辑 dede/login.php,找到这行:

if($validate=='' || $validate != $svali)

替换为:

if(false)

第二步,编辑 dede/templets/login.htm,把验证码相关的 HTML 标签整段移除:

<li><span>验证码:</span>
<input name="validate" type="text" id="vdcode" style="width:50px;text-transform:uppercase;" />
<img id="vdimgck" src="../include/vdimgck.php" alt="看不清?点击更换" align="absmiddle" style="cursor:pointer" onclick="this.src=this.src+'?'" />
</li>

关闭后必须做的安全加固

没了验证码后,机器人爆破成本降到零。后台目录会在几小时内被各种自动化工具扫到。必须立即做以下三件事:

  1. 把 dede 目录改名为不可猜的字符串。修改方式:服务器上 mv dede dede_xyz123,然后改 data/admin/allowurl.txt 与 data/config.cache.inc.php 里的“dede”为新名字。改完之后 dede.zhangsanlisi 这种地址访问。
  2. 给 dede 目录加 nginx basic auth 双重保护:
location ^~ /dede_xyz123/ {
    auth_basic "admin only";
    auth_basic_user_file /etc/nginx/.htpasswd;
}
  1. 用宝塔或 fail2ban 配置 IP 白名单,只允许办公 IP 访问后台。

三件做齐之后才算把“关闭验证码”的安全代价还回去了。

data/safe/inc_safe_config.php 的局部关闭法

除了硬改 login.php,还有一种更柔和的方法是改 data/safe/inc_safe_config.php。这个文件里有:

$safe_gdopen = '1,2,3,5,6';

每个数字对应一个开启验证码的位置:1 是后台登录、2 是会员登录、3 是会员注册、5 是发文章、6 是评论。把 1 去掉就只关后台登录验证码,其他位置仍然有验证码。

这个改法相当于把后台“验证码安全设置”界面里的某些开关程序化设置,可以在 PHP 直接修改文件而不依赖后台能进。但前提是“后台进不去”这个故障已经修了——其实如果能进后台就直接在“验证码安全设置”勾掉了。

data 目录搬家引起的特殊场景

原文提到的“data 目录转移引起的验证码错误”是 5.7 版本里一个独立的故障类型。织梦 5.7 SP2 版本之后官方推荐把 data 目录从 web 目录里挪出来(比如挪到 /home/wwwdata),通过 include/common.inc.php 里 define('DEDEDATA', xxx) 来指向新位置。

挪了之后 vdimgck.php 与 login.php 这两个脚本读取 session 的路径会因为下面这段代码出错:

$sessSavePath = DEDEDATA."/sessions_{$enkey}";

如果 DEDEDATA 已经是绝对路径就没问题,但如果当时定义成相对路径 ../data,PHP CLI 与 PHP-FPM 的 cwd 不一样,就会出现 session 路径解析到不存在的位置。

修复方式:把 DEDEDATA 定义改成绝对路径,例如 define('DEDEDATA', '/home/wwwdata/example')

PHP 版本兼容相关的子故障

DedeCMS 5.7 原生兼容 PHP 5.2-5.6,到 PHP 7 之后有几处不兼容:

  • session_register() 在 PHP 7 被删除,部分老版本登录脚本仍调用,触发 fatal error。需要全文搜索 session_register 替换成 $_SESSION['xxx'] = ...
  • mysql_* 系列函数被废弃,dede 默认还在用,需要替换成 mysqli 或者用 PDO 适配层。
  • preg_replace 的 /e 修饰符在 PHP 7 取消,改成 preg_replace_callback。

这三处不解决,PHP 7+ 环境下登录直接崩,自然报“验证码错误”(其实是后续逻辑没跑到)。社区有 dedeCMS-php7 的兼容补丁,搜索这个关键词找带 git 仓库的版本而不是百度网盘的版本,避免被植入后门。

常用的快速验证手段

遇到“验证码错误”第一步先做这个十分钟内能跑完的快速验证:

  1. 浏览器打开 站点根/include/vdimgck.php,看是否能直接看到验证码图片。看不到就是图片生成端坏了,按故障一处理。
  2. 右键图片,看看图片源,按下 F12 看 Network 里这个请求的 Set-Cookie 是不是含 PHPSESSID。没有就是 cookie 设置出问题。
  3. 刷新登录页,再看 Network,确认登录页的 Cookie 请求头里 PHPSESSID 与上一步 Set-Cookie 的值一致。不一致就是 cookie 没存住。
  4. SSH 进服务器执行 ls -lt /tmp/sess_* | head(或对应 sessions_xxx 目录),看刚才请求是否产生新 session 文件。没产生就是 session.save_path 不可写。
  5. 手动 cat sess_xxx 看里面是否有 svali 字段。

这五步走完,故障定位精度可以到具体哪个环节。

常见问题解答

验证码图片能显示但永远输错,是什么原因?

大概率是 svali 写入的 session 与登录提交读到的 session 不是同一个。先检查 data/sessions_xxx 目录权限是否对 PHP 进程可写,再确认 cookie 里 PHPSESSID 在两次请求间没变。第三个排查点是看 data 目录有没有同时存在多个 sessions_xxx 目录、是否双写。

升级到 PHP 7.4 后验证码全坏了怎么办?

DedeCMS 5.7 原生不兼容 PHP 7+,常见崩点是 session_register、mysql_* 函数、preg_replace 的 /e 修饰符。临时方案是降回 PHP 5.6,长期方案是打 dedeCMS-php7 兼容补丁,或者考虑迁移到 Typecho、WordPress 等仍在维护的 CMS。

关闭验证码后被爆破怎么办?

关闭验证码必须配合后台目录改名、nginx basic auth、IP 白名单这三件事。三件做齐才算把验证码挡掉的爆破阻力还回来。如果做不到这些,建议优先修验证码本身而不是关掉。

为什么 sessions_8ab3842ff8 这种带后缀的目录会出现多个?

DedeCMS 用 cfg_domain_cookie 的 md5 值作为目录后缀,如果你站点曾改过 cfg_domain_cookie 设置(包括从空字符串变成有值),新旧两个值会生成两个不同后缀的目录。新目录正在被使用,老目录留在那里没人清。彻底解决方法是把 include/common.inc.php 改成 sessions_save_path 走单一 data/sessions 目录。

验证码刷新后看到的图片字符与提交时的不同,是不是浏览器缓存问题?

不是。点击刷新验证码会向 vdimgck.php 发新请求,重新生成 svali 写到 session。每次刷新后页面上看到的字符就是当前会话里最新的 svali。如果你看到旧字符,是浏览器对图片做了 cache 没真正请求 vdimgck.php,可以在 src 后面加随机参数 ?t=随机数 强制刷新。原文模板里那个 onclick="this.src=this.src+'?'" 就是干这个的。

已经把 data/sessions 目录权限改成 777 了还是不行?

检查三个细节:第一,是否 data 上层目录权限不对(比如 data 是 755 但 data/sessions 是 777,PHP 也得有 data 的 x 权限才能 cd 进去);第二,SELinux 是否启用且未对 sessions 目录做 restorecon;第三,是否有多个 sessions 目录而你只改了其中一个。Linux 命令 find data -type d -name 'sessions*' -exec ls -ld {} \; 一次性看清。

修改 common.inc.php 后是否需要重启 PHP-FPM?

修改 PHP 文件不需要重启 PHP-FPM,因为 PHP-FPM 每次请求都会重新读文件(除非开了 OPcache)。如果开了 OPcache 且 opcache.validate_timestamps=0,那确实需要重启 PHP-FPM 或者 reload 让缓存失效。

多站点共用一个 PHP-FPM,会不会互相串验证码?

原版 DedeCMS 用 enkey 后缀的设计就是为了避免这种串。enkey 基于 cfg_domain_cookie 的 md5,只要每个站点设置了不同的 cfg_domain_cookie,就会落到不同的 sessions_xxx 目录。但如果你按本文建议改成单一 data/sessions 目录,就要单独考虑跨站隔离——简单做法是各站点的 data 目录本来就分开放,所以单一 sessions 也不会冲突。

验证码图片出现但全是黑色或乱码字符?

GD 库装了但没装 freetype,无法渲染中文字体。验证码模板使用了 imagettftext,缺 freetype 时退化为 imagestring 用 ASCII 像素字体,遇到中文位置画黑块。修复:CentOS 上装 php-gd 同时确保附带 freetype,或者把 vdimgck.php 改成只画英文与数字。

验证码错误的故障是否会影响前台会员登录?

同一个故障原因(session 不可写、GD 缺、目录权限)会同时影响后台与前台。但织梦前台默认会员登录的验证码是另一段代码(用的 member/snsbutton.php 与 include/vdimgck.php 同源但调用上下文不同),症状可能略有差异。修了后台之后顺便去会员登录页测一遍。

分享到
标签
版权声明

本文标题:《DedeCMS 后台验证码错误终极排查指南:从 GD 库到 sessions_xxx 升级残留全覆盖》

本文链接:https://zhangwenbao.com/a-dedecms-background-verification-solution-error-code.html

版权声明:本文原创,转载请注明出处和链接。许可协议: CC BY-NC-SA 4.0

继续阅读
发表评论
分享到微信 或在下方手动填写
支持 Ctrl + Enter 提交