请不要在公开 Issue 中披露可直接利用的漏洞。优先通过 GitHub Security Advisory 私下报告,并提供:
- 受影响版本或提交;
- 部署方式和 dae 版本;
- 可复现步骤;
- 预期影响;
- 已知缓解措施。
kdae-panel 是具有高权限的系统管理服务。当前发布模型以 root 运行,但将操作限制为固定的 dae、systemctl 和 journalctl 参数,不提供终端或任意 Shell API。
dae 版本管理(KDAE_PANEL_ENABLE_DAE_INSTALL,默认开启)会引入一条从互联网取回内容并以 root 执行的路径,边界另见下方“dae 版本管理”。
主要防护包括:
- 管理员密码使用 Argon2id,随机盐值独立保存;
- 登录令牌只在创建时返回到 HttpOnly Cookie,数据库只保存 SHA-256 摘要;
- 会话使用绝对过期时间,修改密码会注销旧会话;
- 非只读请求要求会话专属 CSRF 令牌并校验 Origin;
- 登录失败按可信代理解析后的来源地址限速,限速状态有容量上限和自动清理;
- 密码哈希操作全局串行,忙时快速拒绝,避免并发 Argon2id 耗尽内存;
- API 请求体、命令输出和日志条数都有上限;
- systemd 服务采用能力白名单和文件系统保护;
- 配置及备份默认使用 0600 权限,数据目录使用 0700;首次安装写入的种子配置同样是 0600。
- 配置备份最多保留 50 份且总量不超过 256 MiB,防止管理历史耗尽磁盘。
- 子进程不继承
KDAE_PANEL_前缀的环境变量,初始化凭据不会传给 dae 等外部命令。
一键部署(scripts/get.sh)的信任边界:
- 两个信任根,同属 GitHub:
raw.githubusercontent.com上 main 分支的 get.sh 本身,与 GitHub Release 的发布资产。curl | bash等于同时信任两者;不接受的话,手动下载发布包核对后安装,或从源码构建。一键卸载只涉及第一个信任根——raw 上 main 分支的脚本本身,不下载任何 Release 资产,信任边界是部署的子集;安装时会在/usr/share/kdae-panel/落一份本地副本,卸载可以完全离线。 - 校验强制:脚本先解析 latest 实际指向的 tag,两个文件从同一固定版本下载,逐一比对
SHA256SUMS,不一致即中止,没有跳过开关。清单与发布包同发布者、同 Release,防传输损坏与不完整下载,防不住发布者本身——与 dae 的.dgst同一档。 - 独立的来源证明:发布流水线为每个归档生成 GitHub OIDC 构建来源证明,
gh attestation verify <归档> --repo tuoro/kdae-panel可验证归档确实产自本仓库的发布工作流。它不依赖与包同源的清单,防得住"资产被事后替换";残余信任是 GitHub 本身与系统 CA 信任库。 - 发布纪律:只有
vX.Y.Z形式的 tag 会成为正式 release;带连字符的 tag(v1.0.0-rc.1)自动标为预发布,不会被 latest 直链选中,一键部署的用户不会装到它。
该功能默认开启。面板会从上游下载 dae 可执行文件并以 root 替换、重启服务,因此边界如下:
- 来源固定:仓库写死为
daeuniverse/dae与olicesx/dae,接口只接受来源枚举,不接受任意仓库地址;版本号与构建编号经正则校验后才拼入 URL。 - 地址自行拼装:发布资产与
.dgst的下载地址由面板用写死的仓库名、已过正则的 tag 和自己构造的资产名拼出,不采信接口响应里的browser_download_url。这样一份被篡改的响应最多让校验和对不上(安装失败),而没法把下载悄悄指到同一个域下的别的仓库。 - 校验强制:官方发布的 sha256 取自随包的
.dgst,kdae 构建的取自 GitHub Actions 接口。校验和缺失或格式不符一律拒绝安装,没有任何跳过校验的开关。 - 第三方依赖:kdae 的构建产物直链需要认证,面板改经
nightly.link取得重定向。它只发重定向、不转发文件字节,且校验和经另一条连接独立取得,因此它无法篡改内容——它影响的是可用性,不是完整性。这是一个显式的第三方供应链依赖。 - 只信任本仓库自己的构建:解析 CI 构建时会重新核对
head_repository、事件类型与分支。GitHub 的分支过滤作用于head_branch,若上游给构建流程加上pull_request触发,来自 fork 的 PR 会带着攻击者的产物出现在列表里,而其校验和是 GitHub 如实计算的、能通过比对。 - 物料同源:首次安装所需的服务单元、种子配置与 geo 数据全部取自同一个发布包,因此被同一个 sha256 覆盖,不引入任何新的下载源或信任根。
- 不覆盖既有资产:升级时只替换
dae.service的ExecStart实际指向的文件。首次安装时已存在的配置与已存在的 geo 数据一律保留不动(用户可能自己维护着一份裁剪过的 geo);已存在的服务单元只有在内容与本次将写入的完全相同、或它启动的已经是面板要装的那个文件时,才视为就位,否则一律拒绝。目标位置若已经躺着一个 dae(systemd 里查不到,但文件在),安装前会先把它备份成回滚点,绝不无备份覆盖。 - 状态存疑即拒绝:判断"这台机器上有没有 dae"依赖
systemctl show。查询失败时不会当成"没有",而是直接拒绝首次安装——把查询失败当绿灯,会让一次 systemctl 抽风变成一次覆盖安装。 - 拒绝无法改写的单元:发布包里的单元用固定的默认路径,面板会把它改写成实际路径并核对改写确实生效;上游若变更默认路径导致改写落空,安装当场中止,而不是写出一个指向别处的单元。单元的其余指令按上游原样落盘——它与可执行文件同属一个已校验的发布包,信任边界与二进制本身相同。
- 确认取出的是可执行文件:可执行文件按条目名从包内挑选,首次安装与升级两条路径都在落盘前校验 ELF 魔数,避免上游改动打包方式后装上一个非可执行文件。
- 装完不自动启动:dae 是透明代理,配置不当地启动会切断管理员自身的连接。种子配置不声明任何网卡,启动由用户显式执行。
- 先验证后替换:新二进制先自证能运行、且接受当前配置,才会备份旧版本并原子替换;重启后需在观察窗口内持续保持 active,且 systemd 的重启计数不再增长(只看状态会漏掉两次采样之间跑完的崩溃-重启循环),否则自动回滚。
- 回滚点只在事务结算时改写:备份先写到暂存位,只有磁盘上确实留下了新版本才提升为正式回滚点;安装失败并退回原样时暂存位被丢弃,既有回滚点原封不动。否则一次失败的安装会顺手删掉用户真正想回去的那一版。暂存位在下次状态查询时若还在,说明上一次安装被中途打断,界面会据此提示核对版本。
- 沙箱代价:发行单元默认允许写
/usr/bin与/etc/systemd/system,让首次安装和版本切换开箱即用。前者在 root 的PATH上,后者能定义以 root 运行的服务,因此面板缺陷可能升级为任意代码执行;这是项目为降低首次使用门槛明确接受的默认权限代价。不需要版本管理的部署可以关闭开关并收紧写目录。首次安装在动手前仍会逐个探测目标目录并把缺项说清楚。 - 残余信任:完整性最终建立在系统 CA 信任库之上。面板的出站流量可能正被它自己管理的 dae 接管,若代理链上存在 TLS 拦截(企业环境中并不罕见),校验和与内容会来自同一个中间人。校验和与资产经不同请求取得只提高门槛,不消除这一前提。
该功能在认证后始终可用,与 dae 版本管理相互独立。更新只写 dae 的数据目录(通常就是本来就可写的配置目录),既不碰可执行文件也不碰 /etc/systemd/system;旧版 KDAE_PANEL_ENABLE_GEO_UPDATE 只为启动参数兼容保留。
-
内置来源仍为固定枚举:
loyalsoldier与v2fly的下载地址由面板用写死的仓库名、已过正则的 tag 和自己构造的资产名拼出,不采信接口响应里的 URL。 -
自定义来源受独立出站策略约束:每组必须提供两个数据直链和两个 SHA-256 校验直链,只接受公网 HTTPS。保存时拒绝 userinfo、内网字面地址与片段;首跳和每次重定向重新解析 DNS,连接前再用
Dialer.Control检查实际地址,限制跳数并清除 Authorization/Cookie。自定义客户端不携带 GitHub Token;来源文件以0600保存,因为查询串可能包含管理员配置的临时凭据。 -
校验强制:两个文件各自比对随发布的
.sha256sum,缺失或格式不符一律拒绝,没有跳过开关。注意它与数据同发布者、同位置,防的是传输损坏与被篡改的接口响应,防不住发布者本身——这一点和官方发布的.dgst是同一档。 -
两个来源的规则集不是同一套,切换会静默改变路由行为:
来源 仓库 与 dae 发布包 loyalsoldierLoyalsoldier/v2ray-rules-dat不同。重新编排的规则集,分类更细,同名分类所含域名也不同 v2flyv2fly/geoip+v2fly/domain-list-community相同。dae 的 CI 正是从这两个仓库取 geo 打进发布包的( geosite.dat即dlc.dat改名)切换来源后
geosite:开头的路由规则所匹配的域名集合会改变,而 dae 照样 validate 通过、照样运行。界面在切换时(而非每次更新时)显式警告这一点,账本也记下每次用的是哪个来源。 -
v2fly不是新的信任根,loyalsoldier是:dae 官方 CI 用不带任何校验的curl从 v2fly 拉取 geo 再打进发布包,因此 v2fly 早已是隐式信任根,.dgst只能保证"与构建机当天下载到的字节一致"。选v2fly时面板反而比官方流水线更严(多做了一次 sha256 比对);选loyalsoldier才是真正新增一个信任根。 -
就地更新实际生效的那一份:dae 查找 geo 的顺序是
DAE_LOCATION_ASSET→ 配置目录 →/root/.local/share/dae→/usr/local/share/dae→/usr/share/dae。面板更新第一个命中的那份文件所在的目录,都不存在时才写配置目录。若无脑写配置目录,会给用dae-installer维护/usr/local/share/dae的人生成一份优先级更高的副本,从此他们跑上游更新脚本毫无效果且没有任何提示;界面会把被遮蔽的副本明确列出来。 -
可回滚:
dae validate完全不检查 geo,而一份 dae 不接受的 geo 会让它启动失败——此时流量不再被透明代理接管,属于静默的 fail-open。因此更新的顺序是:暂存 → 旧文件改名留作回滚点 → 原子替换 →dae reload→ 成功则删掉回滚点,失败则把旧文件放回去并再 reload 一次。两个文件同进同退,不会出现一新一旧的组合。 -
只 reload 不 restart:reload 会重建控制平面并重新编译路由规则,从而重读 geo,不必重挂 eBPF。新连接不受影响,但 dae 的切换预算约为 10 秒,进行中的长连接可能被中断——界面按此如实描述,不宣称"完全无感"。
- 默认监听
0.0.0.0:2023,便于可信局域网直接访问;主机防火墙不应把该端口暴露到公网; - 首次初始化必须通过服务启动日志中的一次性链接兑换短时 HttpOnly 授权 Cookie;
- 跨不可信网络访问必须使用 HTTPS 反向代理或 SSH 隧道;
- HTTPS 部署必须设置
KDAE_PANEL_SECURE_COOKIE=true; - 反向代理必须保留原始 Host;
- 不要让非管理员写入
/etc/kdae-panel、/var/lib/kdae-panel、dae 配置或面板二进制; - 定期更新面板、dae、Go 工具链、Node 构建依赖和操作系统;
- 备份可能包含订阅地址和节点凭据,分享前必须脱敏。
- 首次初始化采用“首个成功请求创建管理员”的模式。公开网络监听前必须先在本机完成初始化。
- 面板不能防止已拥有 root 权限的本机攻击者读取数据库、替换二进制或直接修改 dae。
sysdump归档和 dae 校验错误可能包含系统信息,只有已登录管理员可以读取;临时归档在响应读取完成后立即清理。- 连接活动只向已登录管理员开放,从 journald 与 procfs 提取地址、域名、MAC、进程和出站元数据;这些数据本身具有隐私性,不应把面板端口暴露到公网。面板不读取或暴露 dae 内部 eBPF Map,也不提供无法从公开接口证明的逐条存活状态。