CloudFlare TurnStile 安全防护 v1.09 [复制链接]

二级用户组

简介

全站访问门禁 + 登录/注册/找回密码人机校验,基于 Cloudflare Turnstile,独立于系统内置验证码。

功能

  • 独立开启 全站访问检查 / 登录访问检查 / 注册访问检查 / 找回密码访问检查。

  • 支持添加 IP白名单 / 豁免路径。

  • 默认保留 30天 验证记录,默认验证通过凭证有效期 24小时,均支持独立设置。

  • 统计 当日 / 7日 / 30 日通过与失败次数,统计 TOP10 失败次数 IP。

  • 支持设置同一 IP 验证次数上限。

    支持完整 IP、CIDR 与前缀。仅当访问者来自这些网段时才采信 X-Forwarded-For / X-Real-IP(Cloudflare 回源自动可信)。

    CloudFlare TurnStile 安全防护与系统内置验证码完全独立,均可开启互不影响。

文件结构

plugin/suncosmos_turnstile/
│
├── conf.json                                   # 插件元数据(name/brief/version/bbs_version/type/hooks_rank/overwrites_rank/dependencies)
├── install.php                                 # 安装:建日志表(IF NOT EXISTS)+ 初始化 KV 配置(幂等)
├── uninstall.php                               # 卸载:删表 + 删 KV + 清缓存/门禁 Cookie/语言编译缓存
├── upgrade.php                                 # 升级:幂等结构迁移(db_check_table_exists + SHOW COLUMNS + ALTER TABLE)
├── setting.php                                 # 后台设置页入口:权限校验 + POST 处理(4 个动作) + 4 个 Tab 分发
├── icon.png                                    # 后台插件列表图标
│
├── model/
│   └── SuncosmosTurnstileService.php           # 核心服务(配置/校验/门禁票据/IP/限频/豁免/日志/统计/输出辅助) 38 KB
│
├── route/
│   └── suncosmos_turnstile.php                 # 前台路由:gate 渲染门禁页 + verify 校验 token(强制 PRG,302 收尾)
│
├── static/
│   └── js/
│       ├── turnstile-gate.js                   # 门禁页脚本:显式渲染 + 提交兜底 + 防重复提交 + 脚本不可用兜底
│       └── turnstile-form.js                   # 场景表单脚本:渲染 + hidden 注入 + 提交后重置 + 位置统一(placeLikeLogin)
│
├── view/
│   └── htm/
│       ├── gate.htm                            # 前台风禁页(自包含页面,不依赖站点骨架)
│       ├── setting.htm                         # 后台·基础设置(开关/密钥/门禁/场景/日志/限频 + fail-open 告警)
│       ├── whitelist.htm                       # 后台·白名单(IP 白名单 + 门禁豁免路径)
│       ├── records.htm                         # 后台·记录(筛选/分页/单条删除/清空)
│       └── stats.htm                           # 后台·统计(今日·7日·30日 + 场景分布 + 失败 Top10 IP)
│
└── hook/                                       # 编译期注入片段(文件名含扩展名必须与源占位符一致)
    ├── index_inc_route_before.php              # 全站访问门禁拦截主逻辑(含插件自身路由硬豁免)
    ├── index_route_case_end.php                # 注册前台路由 case:suncosmos_turnstile
    ├── model_route_table_end.php               # 注册路由表:gate / verify 两个命名路由
    ├── model_inc_file.php                      # Service 注册(每行以逗号结尾)
    ├── model_cron_daily_end.php                # 每日任务:清理超保留天数的验证记录
    ├── user_login_post_start.php               # 登录 POST:Turnstile 校验(限频 → 校验)
    ├── user_create_post_start.php              # 注册 POST:Turnstile 校验
    ├── user_resetpw_post_start.php             # 找回密码两步 POST:Turnstile 校验(按 $action 区分)
    ├── user_login_submit_after.htm             # 登录表单注入组件(表单内、提交按钮之后)
    ├── user_create_submit_before.htm           # 注册表单注入组件(位置由 JS 统一搬到按钮之后)
    ├── user_resetpw_verify_code_after.htm      # 找回密码第 1 步注入组件(位置由 JS 统一搬到按钮之后)
    ├── user_create_card_title.htm              # 找回密码第 3 步注入组件(原占位符在 form 外 → JS 搬入表单)
    ├── admin_sidebar_end.htm                   # 后台侧边栏入口
    ├── lang_zh_cn_bbs.php                      # 简体中文语言键(102 个)
    ├── lang_zh_tw_bbs.php                      # 繁体中文语言键(102 个)
    └── lang_en_us_bbs.php                      # 英文语言键(102 个)

配置流程

  1. 后台「插件管理」安装并启用 suncosmos_turnstile

  2. 填写站点密钥(Site Key)与密钥(Secret Key)并勾选启用插件

  3. 选择性开启全站访问、登录、注册找回密码场景验证

  4. 最后点击保存配置

兼容版本

XiunoX v1.1

其它说明

本插件已在生产环境进行测试,但未与其它 插件 / 模板 进行测试,无法确定此插件是否与其它插件存在冲突;同时本插件未测试全部 CDN 服务商,无法确定是否与某类型 CDN 冲突;如发生以上情况可强制卸载本插件,并向我反馈此问题,非常感谢!

常见问题

Q1 如果插件出现BUG,如果强制关闭插件?

  1. 删除数据库中的 表名_plugin中的 suncosmos_turnstile 该行,或将installed 与 enable 字段改为 0

  2. 删除 plugin 文件夹下的 suncosmos_turnstile 目录

  3. 删除 tmp 文件夹下的缓存文件

  4. Ctrl+F5 强制刷新网站首页即可恢复正常访问

Q2 如果申请 CloudFlare TurnStile?

首先在 CloudFlare 首页搜索 Turnstile,点击出现的搜索结果。

image.png

并点击右侧的手动添加小部件。

image.png

依次填写 “小组件名称” 与 “主机名”,小组件名称可随意填写,主机名填写你的网站域名(可填多个),如果没有在 CloudFlare 托管此域名,或为二级域名,输入完成后直接选择 将 “网站域名” 添加为自定义主机名,填写完成后点击创建就会显示出站点密钥和密钥,将两个值一次填写到插件中就可以使用了。

image.webp
image.webp

插件图片预览

image.webp

login.png

password.png

register.png


插件分享
插件名称:
CloudFlare TurnStile 安全防护
兼容版本:
XIUNOX 1.1
插件自检报告:
审计完成: 未发现问题 。以下为报告。

# suncosmos_turnstile 代码规范与安全审计报告
项 值 插件版本 `1.0.9` (`conf.json` ) / db`bbs_plugin.version = 1.0.9` 状态 `installed=1` 、`enable=1` 、编译缓存已清(下次访问自动重编译) 适配核心 XiunoX`1.1.10` (`bbs_version = 1.1` ) 审计范围 `plugin/suncosmos_turnstile/` 全部 22 个源文件 + 核心调用链(`_include()` /`CsrfService` /`CacheHelper` /`url()` /`paginação` /`db_cond_to_sqladd` /`http_location` /`ErrorHandler` / Tabler 子集 CSS) 审计方法 静态审计(硬规则 + 扫描器分级对照)+ 编译器原规则校验 + 语法/键名交叉校验 + 运行时日志取证 + 数据库状态核对 结论 未发现规范或安全缺陷 (0 fatal / 0 error / 0 warning)

## 一、逐项审计结果
### 1. 结构与元数据 ✅
检查 结果 `conf.json` 必填字段 `name` /`brief` /`version` (三位制`1.0.9` )/`bbs_version` (两位制`1.1` )/`type=plugin` 齐全 废弃字段 无`id` /`installed` /`enable` ✅ `hooks_rank` 16 个键与`hook/` 16 个文件 一一对应 (含扩展名)✅ `overwrites_rank` 为 object(`{}` )✅ 目录命名 `suncosmos_turnstile` (两段式),前缀未占用`xn_` /`xnx_` ✅ 安装/卸载脚本 `install.php` 幂等(`CREATE TABLE IF NOT EXISTS` );`uninstall.php` 标准拼写 + 删表 +`kv_delete` + 清 Cookie/语言缓存 ✅ 结构变更 走`upgrade.php` (`db_check_table_exists` +`SHOW COLUMNS` +`ALTER TABLE` 幂等)✅ 表字符集 / 前缀 `utf8mb4` ;`db_*` 传表名不含前缀(原生 SQL 手拼`$db->tablepre` )✅

### 2. Hook 规范 ✅
检查 结果 首行标记 11 个`.php` hook 均`<?php exit;` ;5 个`.htm` hook 仅`<?php` (无误用`exit` )✅ `return;` `hook/` 目录内 无 ✅ `// hook 文件名` 注释 无 (统一`// ts:` 前缀,避免编译器多趟误匹配)✅ 变量污染 hook 内局部变量统一`$_suncosmos_ts_` 前缀 ✅ Service 加载路径 4 个脚本统一「`class_exists` 守卫 +`_include()` 」;4 个 hook 用`class_exists(...) || include_once` (短路), 不存在`include_once` 源文件的路径错配 ✅

### 3. 安全面 ✅
检查 结果 输出转义 全量`esc_html()` /`esc_attr()` ;无`esc_textarea()` 、无裸`htmlspecialchars()` ✅ CSRF 所有 POST 表单含`CsrfService::input()` ;核心中央`CsrfService::check()` 在路由之前执行(后台另有`admin/index.inc.php` 中央校验)✅ 权限 `setting.php` 开头`$gid != 1 && $gid != 2 AND message(-1, '无权限')` ✅ SQL 条件数组 /`db_sql_find_prepared()` 参数化(含`LIKE` );字段名白名单由核心保证 ✅ 门禁票据 `xn_encrypt()` 签名(站点 authkey)+ 版本盐 + 过期 + 可选 IP 摘要绑定 ✅ 门禁豁免 三层判定(路由层`PAGE_ROUTES` + 路径层`..` /前缀 + 文件层真实静态资源),且插件自身路由 硬豁免 防死循环 ✅ 开放重定向 `safe_dest()` (拒绝`//` 、`\` 、`..` 、CR/LF、指回插件)+`http_location()` 同源校验,双保险 ✅ 错误回显 `safe_error_code()` 白名单(10 个码),非法值回显为空 ✅ XSS 面 无`innerHTML =` /`outerHTML =` /`document.write` /`insertAdjacentHTML` /`eval(` ;`<script>` 体内无 PHP 标签 ✅ fail-open 收敛 仅`not_configured` /`verify_unavailable` 放行,且票据只给`min(FAIL_OPEN_TTL, gate_pass_ttl)` ;确定性失败仍拦 ✅ 限频 门禁端点与 login/register/resetpw 三个表单场景 均按 IP + 场景限频 (默认 300 次/300 秒,可配置、可`0` 关闭);限频命中不写日志 ✅

### 4. 前端规范 ✅
检查 结果 框架 Bootstrap 5.3 + Tabler Icons;无 jQuery / Alpine / idiomorph;无 htmx 2.x 旧事件名 ✅ 后台交互 原生`fetch` +`XN.toast` /`XN.confirm` ,不依赖`xiuno-modern.js` ✅ 收藏态 表单提交禁用按钮 + 前置`spinner-border` (DOM API 插入,不用`innerHTML` )✅ 卡片 / 标题 `x-card + card` ;组标题`card-title small text-muted` ;无`card-header` /`border-*` ✅ 按钮 全部`rounded-pill` ;无`w-100` /`flex-fill` ✅ Tab 导航 每个子 Tab 前有 Tabler 图标 ✅ 图标可用性 全插件 15 个类名逐一比对站点 Tabler 子集 (约 767 类)→ 全部存在 ✅ 静态资源 `static/*.js` 内无`<?php` ;引用走`$conf['view_url']` +`filemtime()` 版本号 ✅ 语言包 三语言各 102 键,按编译器原正则逐行校验 0 行非法 ✅

### 5. 本轮重点复核:视图内联脚本「去 PHP 化」重构 ✅
上一轮为解决编辑器误报,把 3 个后台 Tab 的内联文案改为 DOM 承载。本轮重点验证其 正确性与安全性 :

检查 方法 结果 JS 使用的`t('key')` 与 DOM 提供的`data-key` 是否一一对应 逐文件交叉比对 `setting` :`fail` /`confirm-clear` ;`whitelist` :`fail` ;`records` :`fail` /`confirm-delete` /`confirm-clear` —— 全部有对应`data-*` ✅ 多行「恢复默认」文本 隐藏`<textarea>` +`esc_html()` +`.value` 换行原样保留,与重构前运行时等价 ✅ 属性注入 `esc_attr()` (转义`"``'``<``>``&` ) 无法突破属性边界 ✅ DOM 注入 JS 仅用`getAttribute()` +`XN.toast/confirm` (文本 API) 无 HTML 解析路径,无 DOM-XSS ✅ 全局状态 未引入`window.__*` ,未新增全局变量 符合「状态放 DOM」✅ `<script>` 内 PHP 标签 定向扫描 三个视图 已清零 ;`gate.htm` 的 PHP 仅位于`<script src="...">` 属性 内(标准 HTML 属性输出,非 JS 主体)✅ IDE 诊断 `GetDiagnostics` `setting.htm` /`whitelist.htm` /`records.htm` 均为空 ✅

### 6. 运行时取证 ✅
证据 结果 `log/202609/error.php` 共 12 条, 最后一条为`17:55:34` (C-1 修复前的最后一次失败点击);此后插件升级、C-2~C-5 修复、视图重构 均未产生任何新条目 ✅ `log/202609/plugin_crash_error.php` 不存在(C-2 删除后未再生成)→ 无崩溃 ✅ 插件自动禁用计数 无新增`Plugin crash count` 记录 ✅ db `/ 插件状态 `installed=1 enable=1 version=1.0.9` ✅(升级链路已跑通) `tmp/` 插件编译产物为空,将由下次访问以 当前源码 重编译(不会运行旧字节码)✅

## 二、语法与命令级校验
1
2
3
4
5
6
7
8
9
10
11
12

`php -l → 全部完整 PHP/HTM 文件通过(hook 片段型文件为上下文依赖片段,编译器已声明跳过语法预检)
conf.json → json_decode: No error
esc_textarea | 裸 htmlspecialchars | jQuery | $.ajax | $.fn
Alpine | x-data | x-show | idiomorph | innerHTML = | outerHTML =
document.write | insertAdjacentHTML | eval( | window.__
htmx:configRequest | TRUNCATE TABLE → 无结果
hook/ 内 return; | // hook 注释 → 无结果
plugin/*/static/ 内含 <?php → 无结果
conf.json 内 "id" | "installed" | "enable" → 无结果
hook 文件数 16 vs hooks_rank 16 → 一致
语言键 zh_cn / zh_tw / en_us → 102 / 102 / 102,非法行 0
图标类名(15 个)vs Tabler 子集 CSS → 缺失 0`

## 三、观察项(不构成缺陷,无需修复)
# 说明 O-1 `log/` 与`log/202609/` 目录权限为`777` (全站性,非插件引入);建议运维收敛为`755` (属主`www` ) O-2 `error.php` 仍保留 3 条 修复前 的 fatal 历史记录(17:31/17:44/17:55),仅留痕,不影响运行 O-3 限频为读-改-写(非原子),高并发下少计数(偏松);对限频语义可接受 O-4 `recent_fail_open_count()` 每次渲染基础设置页做一次 24h 范围`db_count` ,走`idx_create_time` ;后台页面可接受 O-5 `scene='other'` 在后台筛选下拉不可选,但现有调用点全传字面量,该值实际不可达;未知场景已有「回退原始键 +`esc_html` 」兜底

## 四、未覆盖项(需人工/浏览器验证)
- 门禁与三场景的真实交互链路 :当前`enabled/gate_enabled` 未开、日志表 0 行,无法验证「门禁页渲染 → Turnstile 组件 → POST 校验 → 签发票据 → 放行」与「登录/注册/找回密码注入组件 → 校验 → htmx 提交」。纯逻辑已由 20 项 CLI 断言覆盖, 浏览器交互仍属未测面 。
- 如需补测:需先启用门禁并填入正式密钥,且管理员登录需你在场配合(可用浏览器子代理执行)。
总评 :本轮零发现。插件在结构、Hook 规范、转义/CSRF、门禁与限频安全模型、前端规范、语言包完整性、运行时稳定性七个维度均达标;上一轮的严重缺陷(升级链路`Cannot redeclare class` fatal)与 C-2~C-5 已获生产验证收敛。
插件类型:
安全防护
suncosmos_turnstile.zip 59.2KB
最新回复
  • xiunox 一级用户组

    设计思路很清晰:门禁票据与场景校验分离、IP 可信链(仅 CIDR/前缀命中才采信 XFF)、fail-open 告警,这几点考虑得比较到位。

    两点建议:

    1. 日志表建议对 ip、create_time 建索引,否则 30 天数据量下 cron 清理和 Top10 统计容易全表扫描;
    2. Secret 配置错误或 Turnstile 接口超时时,全站门禁存在锁站风险,建议后台路由也一并硬豁免(你已豁免插件自身路由,可扩大范围)。

    另外想确认:站点开启页面缓存后,表单注入的 hidden token 是否会命中旧缓存导致校验失败?期待补充。【 AI 自动回复,仅供参考】

    23天前

请先登录后再回复 登录