[open_login]开放登录热
插件质量报告
插件:
open_loginHook 功能范围 循环 使用频率 文件读写 修改系统表 读写系统表 读写自己的表 site.closed_allow 站点关闭访问豁免检查 否 2 - - - - admin.tabs 后台管理页标签栏 否 1 - - - - 颜色说明:黄色表示有数据读写,红色表示 Hook 在系统循环中执行,浅蓝色表示有文件写入。“列表跳过”表示回调在列表路径会提前返回,“回调内”表示操作位于回调自身或其帮助函数的循环体内。“文件批处理”表示逐个处理用户一次提交的文件,属于必要操作,不标记修正。只有操作实际处于需要优化的循环路径时,才会标记“修正❗️”。
数据字典
plugin_open_login_codes字段 类型 可空 默认值 约束 id id 否 - 主键、自增 code key 否 - 唯一 user_id uint 否 - - callback_url string 否 '' - created_at uint 否 - - expires_at uint 否 - - used_at uint 否 0 - 开放登录 - 一个用于向其它网站授权你的论坛登录的插件
接入流程
将用户引导至授权页,用户在本站登录并确认授权后,会跳转到回调地址,并在 URL 中附带 code 参数
回调方服务端请求兑换地址,将 [code] 替换为实际授权码,换取本站用户 ID
成功响应:{"ok":1,"user_id":123};失败响应:{"ok":0,"message":"..."}
授权码一次性有效,兑换成功后不可重复使用;未兑换的授权码不会过期
Base64URL 与普通 Base64 的区别
两者都是把回调地址转成一段文本,便于放进 URL;解码后都能还原成原始回调地址
普通 Base64 结果里可能出现 +、/、=;纯英文 URL 不一定含有 /,但中文等非 ASCII 字符更容易编出 /
Base64URL 会把 + 换成 -,把 / 换成 _,并去掉末尾的 = 填充,因此更适合直接放在路径里
伪静态授权页请使用 Base64URL;未开启伪静态时,直接把完整回调地址做 URL 编码即可,不需要 Base64
PHP 示例:$encoded = rtrim(strtr(base64_encode($callback), '+\/', '-_'), '=');
原始回调地址https://example.com/回调?from=论坛
普通 Base64(不要用于路径)aHR0cHM6Ly9leGFtcGxlLmNvbS/lm57osIM/ZnJvbT3orrrlnZs=其中 / 会被当成 URL 路径分隔符,例如上例里的 ...comS/lm57...
Base64URL(伪静态路径请用这个)aHR0cHM6Ly9leGFtcGxlLmNvbS_lm57osIM_ZnJvbT3orrrlnZs不含 +、/、末尾 =,可安全放在 /auth/... 路径中
伪静态已开启
授权页:/auth/[callback],其中 [callback] 为回调地址的 Base64URL 编码(不是普通 Base64)
兑换地址:/auth2/[code]
示例:/auth/aHR0cHM6Ly9leGFtcGxlLmNvbS_lm57osIM_ZnJvbT3orrrlnZs
伪静态未开启
授权页:/index.php?a=auth&callback=%5Bcallback%5D,其中 [callback] 为完整回调地址(做 URL 编码,不需要 Base64)
兑换地址:/index.php?a=auth2&code=%5Bcode%5D
示例:/index.php?a=auth&callback=https%3A%2F%2Fexample.com%2F%E5%9B%9E%E8%B0%83%3Ffrom%3D%E8%AE%BA%E5%9D%9B- bbs1org2026-08-12
我已下载 1.0.6 完整源码核查。这里的
$db->exec(...)是 PDO::exec 执行固定 SQL,不是 PHP 的exec()系统命令函数;参数中也没有用户输入,所以这一处不存在命令执行。该唯一索引其实还是重复的:建表时code ... UNIQUE已创建唯一约束,可以直接删除open_login_ensure_code_unique_index(),但这是代码简化,不是 RCE 修复。不过插件当前确实有更严重的协议安全问题,建议先暂停使用:
- 任意网站都能传入 callback,没有后台注册客户端或回调白名单,授权页确认后会直接跳转任意 http/https 地址,属于开放重定向/钓鱼入口;
/auth2/[code]不验证客户端身份,任何拿到 code 的人都能兑换 UID;- 文档和代码明确让未兑换授权码永久不过期,泄漏后的利用窗口无限;
- 仅返回
user_id,没有 state、PKCE、client_id、redirect URI 绑定或签名身份令牌,不能安全证明兑换方就是最初发起方; - 回调允许明文 HTTP,code 可能在传输、日志和 Referer 中泄漏。
最低修复方案应是:后台注册应用并完整匹配 HTTPS redirect URI;授权码只存哈希、5 分钟过期,并绑定 client_id + redirect_uri + S256 PKCE;授权请求必须有 state;兑换端验证 client_id、redirect_uri、code_verifier,使用原子条件更新消费 code。若目标是标准第三方登录,建议直接采用 Authorization Code + PKCE/OIDC,而不是继续扩展当前 UID 兑换协议。
因此结论:没有命令执行,但当前版本不应作为安全的开放登录协议上线。
替换了exec。旧表可以停用再启用插件迁移
这个code只能说泄露不泄露或者被其它人兑换了都没什么关系,因为它只提供兑换用户id,是给网站确认你提供的不是伪造的凭据用的
修复了回调url在本来就有get参数时会错误传参的问题
🔍 插件审查报告(对照《AI 开发规则》与开发者文档)
🔒 安全问题
回调地址无主机白名单:plugin.php:95-118 仅校验 http/https 与 host 存在,未限制目标主机,273 行 go() 会把用户重定向到任意(含私网/环回)主机并附带授权码,存在开放重定向风险。建议增加可选主机白名单配置,至少拦截内网地址。授权码为 128 位随机值,无 SQL 注入、XSS,兑换信息未泄露敏感数据。
⚡ 性能问题
未发现明显问题。无红区钩子 DB 读,无循环逐条查询。
🐛 功能/规范缺陷
- 授权码并发兑换竞态(中):plugin.php:224-234 事务内 SELECT used_at 后 UPDATE ... WHERE used_at=0,但返回值无条件用 SELECT 出的 user_id;MySQL 下并发两请求都能读到 used_at=0 并各自返回 user_id。应按 UPDATE 影响行数判断是否兑换成功。
- 未兑换码不清理(低):plugin.php:209 expires_at 固定写 0,309 行 cleanup 仅删 used_at>0 的记录,未兑换码永不过期导致表无限增长。建议给授权码设有效期并一并清理。
- 兑换接口用 GET 改状态(低):plugin.php:285-296 auth2 用 GET 消费授权码(UPDATE used_at),预加载/重试/日志泄露可能提前消费。建议改 POST(require_post)。
✅ 修复优先级
① 回调主机白名单(中);② 并发兑换竞态(中);③ 授权码有效期与清理、POST 兑换(低)。
🔍 插件审查报告(AI 自动审查,对照《AI 开发规则》与开发者文档;本插件未被 8/18 楼上 350 的报告覆盖,属独立首次审查)
插件:开放登录(open_login)v1.0.8
结论:已完成命名规范、生命周期(install/uninstall 幂等)、数据库跨库兼容、红区 Hook 零 DB 读、Hook 真实性(含核心与跨插件依赖核对)、SQL 注入风险、CSRF/权限校验、CSS 变量规范等项审查,未发现安全、性能或功能性问题。
旧格式迁移:已将插件代码转存到开发日志,主题正文改为基础介绍。