DedeCMS 自定义表单必填项校验加固版:变量变量安全风险、HTML5 校验、蜂蜜罐与 reCAPTCHA 协同

DedeCMS 自定义表单默认不做必填校验,社区流传的 plus/diy.php 修补代码用了变量变量 $$field 配合 DedeCMS 全局注入有安全隐患。本文给出加固版服务端校验(直接读 $_POST + 字段名白名单)、HTML5 + minlength/pattern 现代化前端校验、蜂蜜罐隐藏字段、IP 频率限制、reCAPTCHA v3 协同的完整代码与 FAQ。

更新 23 分钟阅读 1,016 阅读

DedeCMS 的"自定义表单"功能让站长能在前台收集用户提交(咨询、报名、留言等),后台 → 核心 → 自定义表单管理可以可视化建表。但默认表单没有必填项校验——用户提交空白表单也能成功,结果后台收一堆空数据。社区流传的修补方法是改 /plus/diy.php 加服务端校验,或前端 jQuery 校验。两种方法 2026 年都有需要重新审视的地方:服务端的写法有 变量变量($$field)安全风险、前端的 jQuery 在现代浏览器已是冗余依赖。

这一篇把 DedeCMS 自定义表单必填项校验讲透:原社区代码的安全隐患、加固版的服务端校验、纯前端的现代浏览器原生校验(HTML5 required + JS)、二者协同设计、与 reCAPTCHA / 蜂蜜罐反爬虫的协同、提交后跳转优化、跨版本(V5.7 SP1/SP2 + DedeBIZ)兼容、迁移现代 CMS 的等价做法。

一、社区代码的安全隐患:变量变量 $$field

原社区流传的服务端校验代码核心:

$requireds = explode(',', $required);
foreach($requireds as $field) {
    if($$field == '') {                    // ← 变量变量
        showMsg('带*号的为必填内容', '-1');
        exit();
    }
}

$$field 是 PHP 的变量变量语法——如果 $field = "name",那么 $$field 等价于 $name。这个特性配合 DedeCMS 的全局变量注入机制(register_globals=on 或 DedeCMS 自家的全局注入),允许通过 $_POST['name'] 自动变成 $name。但这同时是个安全坑:

  1. 开 register_globals 的 PHP 5.x 老站:攻击者可以伪造任意变量名,覆盖应用内部变量。但 PHP 5.4 起 register_globals 已被移除,新站点无此风险。
  2. DedeCMS 全局注入:DedeCMS 自带 foreach($_POST as $k => $v) ${$k} = $v; 这种危险代码(在 common.inc.php 里)。攻击者构造 POST: required=cfg_db_pwd&cfg_db_pwd=hacked 能覆盖数据库密码全局变量。
  3. 变量名校验缺失:原代码没验证 $field 是不是合法的字段名——攻击者可以传 required=xxx;DROP TABLE 这种值,foreach 进去虽不直接 SQL 注入,但破坏程序流。

正确的写法不要用变量变量,直接读 $_POST

if (!empty($required)) {
    $requireds = is_array($required) ? $required : explode(',', $required);
    foreach ($requireds as $field) {
        // 严格校验字段名(仅字母数字下划线)
        if (!preg_match('/^[a-zA-Z0-9_]+$/', $field)) continue;

        $val = isset($_POST[$field]) ? trim($_POST[$field]) : '';
        if ($val === '') {
            showMsg('带*号的为必填内容,请正确填写:' . htmlspecialchars($field), '-1');
            exit();
        }
    }
}

这个写法相比社区版本:① 不用变量变量,避免依赖 DedeCMS 的全局注入;② 字段名加白名单正则,防止注入;③ 错误提示告知具体哪个字段没填,对用户更友好。

二、加固服务端校验:完整代码

放在 /plus/diy.php 找到 $dede_fields = empty($dede_fields) ? '' : trim($dede_fields); 行,下面加:

// === 自定义表单必填项与基础校验 ===
// 1. 必填项检查
if (!empty($required)) {
    $requireds = is_array($required) ? $required : explode(',', $required);
    foreach ($requireds as $field) {
        if (!preg_match('/^[a-zA-Z0-9_]+$/', $field)) continue;
        $val = isset($_POST[$field]) ? trim($_POST[$field]) : '';
        if ($val === '') {
            showMsg('带 * 号的为必填内容,请正确填写', '-1');
            exit();
        }
    }
}

// 2. 邮箱字段格式校验
if (isset($_POST['email']) && $_POST['email'] !== '') {
    if (!filter_var($_POST['email'], FILTER_VALIDATE_EMAIL)) {
        showMsg('邮箱格式不正确', '-1');
        exit();
    }
}

// 3. 手机号简单校验(中国大陆)
if (isset($_POST['mobile']) && $_POST['mobile'] !== '') {
    if (!preg_match('/^1[3-9]\d{9}$/', $_POST['mobile'])) {
        showMsg('手机号格式不正确', '-1');
        exit();
    }
}

// 4. 蜂蜜罐反爬虫(hidden 字段,正常用户不填,爬虫填了就拦)
if (isset($_POST['url']) && $_POST['url'] !== '') {
    // 静默拦截,不告诉爬虫具体原因
    showMsg('提交失败', '/');
    exit();
}

// 5. 提交频率限制(同 IP 30 秒内只能提交 1 次)
$cacheKey = 'diy_submit_' . md5($_SERVER['REMOTE_ADDR']);
$cacheFile = DEDEDATA . '/cache/' . $cacheKey;
if (file_exists($cacheFile) && (time() - filemtime($cacheFile)) < 30) {
    showMsg('提交太频繁,请 30 秒后重试', '-1');
    exit();
}
file_put_contents($cacheFile, time());

这套加固版做了 5 件事:① 必填校验;② 邮箱格式校验;③ 手机号校验;④ 蜂蜜罐反爬;⑤ 提交频率限制。比社区版本只做必填的安全防御提升一个量级。

三、纯前端校验:现代浏览器 + HTML5

2026 年的浏览器已经原生支持表单校验,不需要 jQuery。直接用 HTML5 required 属性:

<form action="/plus/diy.php" method="post" enctype="multipart/form-data">
    <label>姓名 *
        <input type="text" name="name" required minlength="2" maxlength="20">
    </label>

    <label>邮箱 *
        <input type="email" name="email" required>
    </label>

    <label>手机号 *
        <input type="tel" name="mobile" required pattern="^1[3-9]\d{9}$">
    </label>

    <label>留言内容 *
        <textarea name="message" required minlength="10" maxlength="500"></textarea>
    </label>

    <!-- 蜂蜜罐:CSS 隐藏,正常用户看不到,爬虫填了就被服务端拦 -->
    <label style="position:absolute;left:-9999px">Website
        <input type="text" name="url" tabindex="-1" autocomplete="off">
    </label>

    <input type="hidden" name="required" value="name,email,mobile,message">
    <button type="submit">提交</button>
</form>

HTML5 表单校验的优势:

  • 无 JS 依赖——浏览器原生支持,不用引 jQuery;
  • 多语言友好——浏览器按用户语言显示错误提示(中文用户看到中文);
  • 可访问性好——屏幕阅读器能读 required 标签,无障碍体验更佳;
  • 禁用 JS 也能提交——服务端兜底,防 JS 关闭场景。

3.1 自定义错误提示

HTML5 默认错误提示是浏览器自带的("请填写此字段" 等),可以自定义:

<input type="email" name="email" required
       oninvalid="this.setCustomValidity('请填写有效的邮箱地址')"
       oninput="this.setCustomValidity('')">

oninvalid 设错误文案,oninput 在用户重新输入时清空错误。这两个事件让自定义提示与原生校验机制结合。

四、服务端 + 前端协同设计

正确的做法是两端都校验

场景前端服务端
用户体验实时反馈,不需要等服务器提交后才知道错
禁用 JS / 直接 POST失效仍能挡住
性能减少无效请求到服务器无差别
安全不可信(用户能改 DOM 绕过)可信,最后防线

结论:前端用 HTML5 增强用户体验,服务端校验是不能省的最终防线。任何只做前端不做服务端校验的表单都不安全。

五、与 reCAPTCHA / 蜂蜜罐的协同

5.1 蜂蜜罐(Honeypot)

原理:在表单里加一个对正常用户隐藏的字段(CSS position: absolute; left: -9999px),机器人爬虫填表时会无脑填所有字段,正常用户因为看不到所以不填。服务端检查这个字段:

  • = 正常用户,放行;
  • 非空 = 爬虫,静默拦截(不告诉它具体哪个字段有问题,让它继续浪费配额)。

蜂蜜罐对 80%+ 的低端爬虫有效,且对正常用户零打扰——不需要点验证码。

5.2 Google reCAPTCHA v3

对剩余的高端爬虫(能识别蜂蜜罐的),加 reCAPTCHA v3。它在背后给每个用户打分(0-1,1 是真人,0 是爬虫),不需要用户点击:

<!-- 前端 -->
<script src="https://www.google.com/recaptcha/api.js?render=YOUR_SITE_KEY"></script>
<script>
grecaptcha.ready(function() {
    grecaptcha.execute('YOUR_SITE_KEY', {action: 'submit'}).then(function(token) {
        document.querySelector('input[name="recaptcha_token"]').value = token;
    });
});
</script>
<input type="hidden" name="recaptcha_token">
// 服务端校验
$token = $_POST['recaptcha_token'] ?? '';
$secret = 'YOUR_SECRET_KEY';
$resp = file_get_contents("https://www.google.com/recaptcha/api/siteverify?secret={$secret}&response={$token}");
$data = json_decode($resp, true);
if (!$data['success'] || $data['score'] < 0.5) {
    showMsg('请稍后重试', '-1');
    exit();
}

注意:reCAPTCHA 需要 Google 服务可访问——国内用户可能因网络问题加载失败,建议用国产替代(极验、网易易盾、阿里云人机验证)。

六、提交成功后的跳转优化

原代码用 showMsg() 显示提示后跳转,体验不佳。优化:

// 提交成功后跳转到独立感谢页(更专业)
header('Location: /thank-you.html?from=' . urlencode($_POST['name'] ?? ''));
exit();

感谢页可以放:① 提交成功提示;② 客户经理联系方式;③ 引导用户去看其他相关产品/文章;④ 触发 Google Analytics 转化事件(电商漏斗追踪)。

6.1 GA / 百度统计的转化追踪

在感谢页 thank-you.html 里加:

<script>
gtag('event', 'form_submit', {
    'event_category': 'Lead',
    'event_label': '咨询表单',
    'value': 100
});
</script>

这样 Google Analytics 能跟踪每次表单提交的转化漏斗,结合广告投放的 CPA / ROAS 计算。

七、跨版本兼容(V5.7 / SP1 / SP2 / DedeBIZ)

/plus/diy.php 在 DedeCMS 各版本里逻辑大致一致,但行号略有差异:

版本$dede_fields trim 行建议插入位置
V5.7~ 第 38 行第 39 行后
V5.7 SP1~ 第 40 行第 41 行后
V5.7 SP2~ 第 42 行第 43 行后
DedeBIZ V6已合并必填校验无需手改

升级 DedeCMS 时这个改动会被覆盖——记得升级前备份 /plus/diy.php

八、与防垃圾留言的纵深防御

必填校验只挡空表单,挡不了"机器人填假数据"。完整的防垃圾留言要做:

  1. 必填项 + 格式校验(本文)
  2. 蜂蜜罐(隐藏字段)
  3. 提交频率限制(同 IP 30 秒)
  4. reCAPTCHA / 极验(人机判断)
  5. 关键词黑名单(拦"代开发票"、"刷流量"等垃圾文本)
  6. fail2ban(动态拉黑高频提交 IP)
  7. WAF(Cloudflare / 阿里云)

每多一层就少一个数量级的垃圾留言。中小站点至少做前 4 层。

九、迁移到现代 CMS 的等价做法

DedeCMS 自定义表单WordPressHexo / 静态站
plus/diy.phpContact Form 7 / Gravity Forms / WPFormsFormspree / Netlify Forms / Vercel Edge
必填校验插件原生支持HTML5 + 服务端 webhook
反垃圾AkismetFormspree honeypot 内置
邮件通知插件原生Formspree 邮件转发

WordPress 上这些功能都是装个插件即可,不用改源码。Hexo 等静态站点用第三方 SaaS 服务(Formspree 免费 50 次/月)。

十、表单提交日志的留存

除了写入 dede_diyforms 数据库表,建议同时记录到日志:

// 记录提交日志(追加到 logs/form_submit.log)
$logEntry = sprintf(
    "[%s] [%s] %s\n",
    date('Y-m-d H:i:s'),
    $_SERVER['REMOTE_ADDR'],
    json_encode([
        'name'    => $_POST['name'] ?? '',
        'email'   => $_POST['email'] ?? '',
        'mobile'  => $_POST['mobile'] ?? '',
        'message' => mb_substr($_POST['message'] ?? '', 0, 200, 'UTF-8'),
        'ua'      => substr($_SERVER['HTTP_USER_AGENT'] ?? '', 0, 100),
    ], JSON_UNESCAPED_UNICODE)
);
file_put_contents(DEDEDATA . '/logs/form_submit.log', $logEntry, FILE_APPEND | LOCK_EX);

日志的好处:① 数据库被攻击时还有副本;② 配 fail2ban 直接读日志识别垃圾 IP;③ 后期数据分析(哪些时段提交多、什么 UA 提交多)。

常见问题解答

改完 plus/diy.php 没生效?

① 确认文件保存了;② DedeCMS 后台 → 系统 → 系统设置 → 更新缓存(虽然 plus 不走模板缓存,但保险起见全清);③ 浏览器开 F12 看提交时是否真请求到 plus/diy.php(路径对不对);④ 看 PHP error_log 有没有报错。

required 隐藏字段的字段名要按什么命名?

按你后台自定义表单建立时的"数据字段名"。比如建表单时"姓名"对应数据字段名 name,"邮箱"对应 email,那 required 就写 name,email。字段名严格区分大小写。

表单提交后页面跳转到 plus/diy.php 但页面空白?

多半是 PHP 报错且 display_errors=Off。先打开 display_errors 看报错——大概率是某个 require 路径错或 include 失败。生产环境调试完务必关回 display_errors,避免暴露路径信息给攻击者。

能不能给不同表单设不同的必填项规则?

能。在 /plus/diy.php 里读 $_POST['diyid'](自定义表单 ID),按 ID 走不同规则:if ($diyid == 1) { ... } elseif ($diyid == 2) { ... }。或者把规则配置写到数据库的扩展字段,让管理员后台编辑。

用 reCAPTCHA 后 SEO 受影响吗?

不直接影响。Google 不会因为页面用了自家 reCAPTCHA 给 SEO 加分(也不扣分)。但 reCAPTCHA 加载约 200KB JS,对 LCP 有约 50-100ms 影响。生产建议仅在表单页加 reCAPTCHA,不要全站加。

蜂蜜罐字段叫什么名字最有效?

选爬虫"看到一定会填"的字段名:urlwebsitecompanyfax 等。爬虫的填表逻辑是按字段名匹配自带字典,常见字段名几乎必填。但要避免和真实表单里的同名字段冲突。

提交频率限制用 IP 还是 cookie?

都用更稳。IP 限制对 NAT 后的多用户共享 IP 不友好(误伤);cookie 限制可被清除绕过。生产建议组合:① IP 限频较宽(30 秒 1 次);② cookie 配合标识"已提交过"(24 小时内不能重复);③ 用 Redis 存计数器,性能更好。

能拦截使用 cURL 等工具直接提交吗?

用 cURL 等工具能绕过前端校验直接 POST。要拦:① 服务端必填校验(绕不过);② 蜂蜜罐 + reCAPTCHA + IP 限频组合;③ 检查 User-Agent 是否包含 cURL/Wget/Python 等爬虫特征字符串。但攻击者改 UA 容易,最终防御还是看服务端的逻辑校验。

表单提交日志要不要清理?

要。logs/form_submit.log 长期不清会越来越大,建议 logrotate:每天滚动 + 保留 30 天 + 超期归档到云存储(OSS / S3)。日志体量大时(每天几万次提交)走结构化日志(JSON Lines)+ 上 ELK 或 Loki,比 grep 文本快得多。

能在表单页加防止 F12 调试的代码吗?

不建议。这种"反调试"对正常开发者是骚扰,对攻击者完全无效(任何反调试都能在浏览器层面绕过)。要保护表单不被滥用,靠服务端校验 + 速率限制 + WAF + Honeypot 组合,不要靠"防 F12"这种心理防御。

分享到
标签
版权声明

本文标题:《DedeCMS 自定义表单必填项校验加固版:变量变量安全风险、HTML5 校验、蜂蜜罐与 reCAPTCHA 协同》

本文链接:https://zhangwenbao.com/dedecms-custom-form-settings-required-items-2.html

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

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