Skip to content

fix(plugin): 收敛临时邮箱 Provider 插件安装链的 RCE 面 - #158

Closed
guyue211 wants to merge 1 commit into
ZeroPointSix:mainfrom
guyue211:fix/temp-mail-plugin-install-rce
Closed

guyue211 wants to merge 1 commit into
ZeroPointSix:mainfrom
guyue211:fix/temp-mail-plugin-install-rce

Conversation

@guyue211

Copy link
Copy Markdown

Pull request type / PR 类型

  • Bugfix / Bug 修复
  • Feature / 新功能
  • Code style update (formatting, renaming) / 代码风格更新(格式化、重命名)
  • Refactoring (no functional changes, no api changes) / 重构(无功能更改、无 API 更改)
  • Build related changes / 构建相关更改
  • Documentation content changes / 文档内容更改
  • Other (please describe) / 其他(请描述):

What is the current behavior? / 当前行为是什么?

POST /api/plugins/install 把请求体中的 url 直接透传给服务端下载器,配合
POST /api/system/reload-plugins 的 exec_module 构成远程代码执行链。仅需后台登录态
(@login_required,无管理员角色校验),即可让服务端从任意地址下载 Python 文件并在
容器内以 root 执行任意命令。

四处缺口叠加:

  1. controllers/plugins.py:install_plugin(name, url=url),下载源完全由客户端决定
  2. temp_mail_plugin_manager.py:完整性校验条件是 if url is None and entry is not None,
    自定义 URL 时 entry 恒为 None,sha256 分支整段失效
  3. 同上:target = plugin_dir / f"{plugin_name}.py",name 无白名单,可路径穿越写入
  4. temp_mail_provider_factory.py:spec.loader.exec_module(mod) 无条件加载插件目录内
    任意 .py,模块顶层代码在执行期即生效,无静态审计、无签名、无沙箱

另:requests.get(url) 未限制协议与目标地址,可访问回环、内网与云元数据端点。

Issue Number / Issue 编号: N/A(安全问题,未开公开 issue)

What is the new behavior? / 新行为是什么?

修复原则是保留能力、收敛边界——自定义来源安装是既有特性(CLI --from、文档中的
自定义安装),不删除,改为分层设防:

  • 接口层:HTTP 接口拒绝请求体 url,返回 PLUGIN_CUSTOM_URL_DISABLED;安装源只能是
    registry 登记条目。自定义来源安装收敛到命令行 install-provider --from,需在服务器上显式执行
  • 下载层:_check_download_url() 仅允许 http/https;自定义来源额外拒绝回环、私网、
    链路本地、保留地址与云元数据主机(先解析 DNS 再逐地址判定)
  • 名称层:_validate_plugin_name() 白名单 ^[A-Za-z0-9_][A-Za-z0-9_.-]{0,63}$ 且拒绝 ..;
    _resolve_plugin_target() 复核落点父目录必须等于插件目录
  • 内容层:_audit_plugin_source() 用 ast 解析后拒绝 subprocess/ctypes/pty/
    multiprocessing/socket/pickle/marshal 导入与 eval/exec/compile/__import__/
    system/popen 等调用;要求继承 TempMailProviderBase 或使用 @register_provider
  • 完整性:官方源声明 sha256 必须匹配;未声明时改用源码审计,消除「源里没写 sha256
    即完全不校验」的口子
  • 加载层:load_plugins() 对插件目录内符号链接标记 failed,不加载

验证 / Verification

在真实实例(Debian 12 / Python 3.11 / uid=0)上修复前确认可执行,修复后同一实例:

POST /api/plugins/install {"name":"pwn9","url":"http://<any>/x.py"}
  → 400 PLUGIN_CUSTOM_URL_DISABLED,插件目录保持为空
POST /api/system/reload-plugins → {"loaded":[],"failed":[]}

纵深防线:

subprocess RCE            → BLOCKED PLUGIN_SOURCE_REJECTED
__import__().system()     → BLOCKED PLUGIN_SOURCE_REJECTED
合法 provider              → ACCEPTED
http://127.0.0.1/x.py     → BLOCKED PLUGIN_URL_BLOCKED
http://169.254.169.254/   → BLOCKED PLUGIN_URL_BLOCKED
file:///etc/passwd        → BLOCKED PLUGIN_URL_INVALID
https://example.com/ok.py → ALLOWED

测试:

  • 插件相关测试 101 passed
  • 新增 17 项加固用例,覆盖名称穿越、协议白名单、内网拦截、源码审计、符号链接
  • 全仓 886 passed, 3 skipped;tests/test_pool_cf_real_e2e.py 4 项失败为既有问题
    (依赖外部 CF Worker,改动前后表现一致,与本次无关)

Breaking change / 破坏性变更

POST /api/plugins/install 不再接受 url 字段。若部署侧依赖接口做自定义安装,需改用:

python -m outlook_web.services.temp_mail_plugin_cli install-provider <name> --from <url>

后续建议 / Follow-ups(未包含在本 PR)

  1. /api/plugins/* 与 /api/system/reload-plugins 增加管理员角色校验,降低单账号泄露影响面
  2. 官方插件源引入 Ed25519 离线签名,替代纯 sha256
  3. 应用进程改非 root 运行,插件目录与 Web 根目录只读挂载
  4. 如需长期支持第三方插件,考虑独立进程或受限解释器加载

相关文档

  • docs/BUG/2026-10-25-临时邮箱Provider插件安装RCE安全缺陷BUG.md(完整根因、攻击链、验证记录)
  • CHANGELOG.md(未发布段)
  • 临时邮箱Provider插件接入说明.md(安装链新约束与源码审计规则)

POST /api/plugins/install 的自定义 url 可让服务端从任意地址下载 .py,
配合 POST /api/system/reload-plugins 的 exec_module 在进程内以 root 执行代码。
四处缺口叠加成链:

1. 控制器把请求体 url 直接透传给下载器,下载源完全由客户端决定
2. 完整性校验条件是 `if url is None and entry is not None`,自定义 URL 时恒假
3. target = plugin_dir / f"{name}.py",name 无白名单,可穿越写入
4. exec_module 无条件加载插件目录任意 .py,无审计、无签名、无沙箱

修复遵循保留能力、收敛边界:自定义来源安装是既有特性,不做删除。

- 接口层:HTTP 拒绝 url,安装源只能是 registry 登记的官方源;自定义来源
  收敛到 CLI(install-provider --from),需在服务器上显式执行
- 下载层:仅允许 http/https;自定义来源拦截回环、私网、链路本地、保留地址
  与云元数据主机(先解析 DNS 再逐地址判定)
- 名称层:白名单 ^[A-Za-z0-9_][A-Za-z0-9_.-]{0,63}$ 且拒绝 ..,落点父目录复核
- 内容层:基于 ast 的源码审计,拒绝 subprocess/ctypes/socket 等导入与
  eval/exec/compile/__import__/system 等调用;要求继承 TempMailProviderBase
- 完整性:官方源声明 sha256 必须匹配,未声明时改用源码审计,
  消除「源里没写 sha256 即完全不校验」的口子
- 加载层:插件目录内符号链接不再加载

测试:插件相关 101 项通过,新增 17 项加固用例覆盖各道防线。

Refs: docs/BUG/2026-10-25-临时邮箱Provider插件安装RCE安全缺陷BUG.md
@guyue211 guyue211 closed this Nov 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant