329 lines
17 KiB
Markdown
329 lines
17 KiB
Markdown
<p align="center"><a href="SECURITY.md">English</a> | <strong>简体中文</strong></p>
|
||
|
||
# 安全
|
||
|
||
RustNet 处理不受信任的网络数据,因此纵深防御至关重要。本文档描述了已实现的安全措施。
|
||
|
||
## 目录
|
||
|
||
- [Landlock 沙箱(Linux)](#landlock-sandboxing-linux)
|
||
- [Seatbelt 沙箱(macOS)](#seatbelt-sandboxing-macos)
|
||
- [FreeBSD 沙箱](#freebsd-sandboxing)
|
||
- [权限剥离与 Job Object 沙箱(Windows)](#privilege-drop-and-job-object-sandboxing-windows)
|
||
- [权限需求](#privilege-requirements)
|
||
- [只读操作](#read-only-operation)
|
||
- [不主动对外通信](#no-external-communication)
|
||
- [日志文件隐私](#log-file-privacy)
|
||
- [eBPF 安全](#ebpf-security)
|
||
- [威胁模型](#threat-model)
|
||
- [供应链安全](#supply-chain-security)
|
||
- [审计与合规](#audit-and-compliance)
|
||
- [报告安全问题](#reporting-security-issues)
|
||
|
||
## Landlock 沙箱(Linux)<a id="landlock-sandboxing-linux"></a>
|
||
|
||
在 Linux 5.13+ 上,RustNet 使用 [Landlock](https://landlock.io/) 在初始化后限制自身的 Linux capabilities。这样即使包解析存在漏洞被利用,也能限制损害范围。
|
||
|
||
### 受限制的内容
|
||
|
||
| 限制项 | 内核版本 | 描述 |
|
||
|--------|----------|------|
|
||
| 文件系统 | 5.13+ | 仅 `/proc` 可读(用于进程识别) |
|
||
| 网络 | 6.4+ | 禁止 TCP bind/connect(RustNet 为被动模式) |
|
||
| Linux capabilities | 任意 | pcap socket 打开后丢弃 `CAP_NET_RAW` |
|
||
| Linux capabilities | 任意 | eBPF 程序加载后丢弃 `CAP_BPF`、`CAP_PERFMON`、`CAP_SYS_ADMIN` |
|
||
| root uid | 任意 | 以 root 启动时(如 `sudo rustnet`),初始化完成后降权到调用用户(`SUDO_UID`/`SUDO_GID`)或 `nobody` |
|
||
| 特权 | 3.5+ | `PR_SET_NO_NEW_PRIVS` 由 RustNet 自身设置——始终生效,即使使用 `--no-sandbox`——防止通过 setuid 二进制文件提升特权 |
|
||
|
||
### 工作原理
|
||
|
||
1. **初始化阶段**:RustNet 加载 eBPF 程序、打开包捕获句柄、创建日志文件
|
||
2. **特权锁定**:设置 `PR_SET_NO_NEW_PRIVS`(即使禁用沙箱也会应用)
|
||
3. **Linux capabilities 剥离**:移除 `CAP_NET_RAW`、`CAP_BPF`、`CAP_PERFMON` 和 `CAP_SYS_ADMIN`
|
||
4. **root uid 降权**:以 root 运行时,通过 `setresuid`/`setresgid` 切换到调用 sudo 的用户(或 `nobody`)。已打开的捕获 socket、eBPF 程序和日志/导出文件继续有效。在没有 Landlock 的内核上,这是主要的隔离手段
|
||
5. **Landlock**:限制文件系统和网络访问
|
||
|
||
### 安全收益
|
||
|
||
如果攻击者利用 DPI/包解析中的漏洞:
|
||
- 无法读取任意文件(凭据、配置等)
|
||
- 无法写入文件系统(除配置的日志路径外)
|
||
- 无法建立出站 TCP 连接(阻止数据外泄)
|
||
- 无法绑定 TCP 端口(阻止反向 shell)
|
||
- 无法创建新的 raw socket(Linux capabilities 已剥离)
|
||
- 无法通过 setuid 二进制文件提升特权(`PR_SET_NO_NEW_PRIVS`,即使使用 `--no-sandbox` 也会设置)
|
||
- 不以 root 运行:使用 `sudo rustnet` 时进程会切换为调用用户,即使在没有 Landlock 的内核上,攻击者也无法获得 root
|
||
|
||
### CLI 选项
|
||
|
||
```
|
||
--no-sandbox 禁用 Landlock 沙箱、Linux capabilities 剥离和 root uid 降权
|
||
(仍会设置 PR_SET_NO_NEW_PRIVS)
|
||
--sandbox-strict 要求完整沙箱强制生效,否则退出
|
||
--no-uid-drop 初始化后保持 root 运行,
|
||
不降权到 SUDO_UID/SUDO_GID(或 nobody)
|
||
```
|
||
|
||
root uid 降权的权衡:降权后,procfs 回退路径的进程归属只能检查目标用户拥有的进程,
|
||
`/var/log/pods` 下的 Kubernetes 日志目录也可能不可读。eBPF 快速路径(默认)不受影响。
|
||
如果依赖纯 procfs 归属(如未启用 eBPF 的构建)且需要归属其他用户的进程,请使用
|
||
`--no-uid-drop`。
|
||
|
||
### 优雅降级
|
||
|
||
- **Kernel < 5.13**:跳过沙箱,记录警告
|
||
- **Kernel 5.13-6.3**:仅文件系统限制
|
||
- **Kernel 6.4+**:完整的文件系统 + 网络限制
|
||
- **Docker**:Landlock 可能受限;应用正常运行
|
||
|
||
## Seatbelt 沙箱(macOS)<a id="seatbelt-sandboxing-macos"></a>
|
||
|
||
在 macOS 10.5+ 上,RustNet 使用 [Seatbelt](https://theapplewiki.com/wiki/Dev:Seatbelt)(`sandbox_init_with_parameters`)在初始化后限制自身能力。这样即使包解析存在漏洞被利用,也能限制损害范围。
|
||
|
||
### 受限制的内容
|
||
|
||
| 限制项 | 描述 |
|
||
|--------|------|
|
||
| 出站网络 | TCP/UDP 出站被阻止;Unix socket(Mach IPC)允许 |
|
||
| 文件系统读取 | 禁止读取用户主目录(`/Users`、`/var/root`);GeoIP 路径显式允许 |
|
||
| 文件系统写入 | 禁止写入所有用户主目录(`/Users`、`/var/root`) |
|
||
| 文件系统写入 | 仅配置的日志、JSON log、PCAP 和 PCAPNG 导出路径可写 |
|
||
| 进程执行 | 除 `/usr/sbin/lsof` 外,禁止执行所有二进制文件 |
|
||
| root uid | 以 root 启动时(如 `sudo rustnet`),初始化完成后降权到调用用户(`SUDO_UID`/`SUDO_GID`)或 `nobody` |
|
||
|
||
### 工作原理
|
||
|
||
1. **初始化阶段**:RustNet 打开包捕获句柄(BPF/PKTAP)并创建日志文件
|
||
2. **预创建**:PCAP sidecar 文件(`.connections.jsonl`)和 PCAPNG 输出文件在沙箱应用前创建,因此其路径已经是有效的允许目标;同时移交给降权目标用户,确保降权后仍可写入
|
||
3. **root uid 降权**:以 root 运行时,通过 `setgid`/`setuid` 切换到调用 sudo 的用户(或 `nobody`)。已打开的捕获和日志/导出文件描述符继续有效
|
||
4. **沙箱应用**:调用 `sandbox_init_with_parameters`;已打开的文件描述符保持不变,仅限制未来的操作
|
||
|
||
### 配置文件策略
|
||
|
||
RustNet 使用 **默认允许** 的 SBPL 配置文件配合针对性拒绝。拒绝默认的配置文件需要显式将所有系统库、Mach 端口、区域设置数据、字体和其他 OS 内部组件加入白名单——脆弱且容易出错。默认允许配合针对性拒绝覆盖了主要威胁(凭据窃取、数据外泄、shell 逃逸),同时避免操作风险。具体的拒绝规则阻止对用户主目录下的文件读/写、出站网络连接,以及除 `/usr/sbin/lsof` 外所有二进制文件的执行。
|
||
|
||
### 输出文件支持
|
||
|
||
`--json-log`、`--pcap-export` 和 `--pcapng-export` 路径通过运行时参数(`JSON_LOG_PATH`、`PCAP_PATH`、`PCAP_JSONL_PATH`、`PCAPNG_PATH`)传递给 SBPL 配置文件。配置文件为每个路径授予显式的 `allow file-write*` 规则,该规则通过 SBPL 的特异性优先于更宽泛的 `/Users` 拒绝规则。未使用的参数默认为 `/dev/null`。
|
||
|
||
三个标志在沙箱内均可正常工作。
|
||
|
||
### 安全收益
|
||
|
||
如果攻击者利用 DPI/包解析中的漏洞:
|
||
- 无法读取 `/Users` 下的 SSH 密钥、AWS 凭据、浏览器配置文件或其他凭据文件
|
||
- 无法写入 `/Users` 下的 SSH 密钥、AWS 凭据、浏览器配置文件或其他凭据文件
|
||
- 无法建立出站 TCP/UDP 连接(阻止数据外泄)
|
||
- 无法打开新的 raw network socket
|
||
- 无法执行二进制文件(不能通过 `/bin/sh`、`/usr/bin/curl` 等逃逸 shell)
|
||
- 不以 root 运行:使用 `sudo rustnet` 时进程会切换为调用用户
|
||
|
||
### CLI 选项
|
||
|
||
```
|
||
--no-sandbox 禁用 Seatbelt 沙箱和 root uid 降权
|
||
--sandbox-strict 要求完整沙箱强制生效,否则退出
|
||
--no-uid-drop 初始化后保持 root 运行,
|
||
不降权到 SUDO_UID/SUDO_GID(或 nobody)
|
||
```
|
||
|
||
root uid 降权的权衡:默认的 PKTAP 归属路径不受影响(进程元数据随已打开的捕获
|
||
fd 带内到达),但 lsof 回退路径(PKTAP 不可用时启用,例如显式指定 `--interface`)
|
||
降权后只能看到目标用户的进程。如果依赖 lsof 归属其他用户的进程,请使用
|
||
`--no-uid-drop`。
|
||
|
||
### 为什么默认使用 BestEffort
|
||
|
||
`sandbox_init_with_parameters` 是 macOS 的私有(未公开)API。自 macOS 10.5 以来一直保持稳定,Chromium、Firefox 和 Safari 都使用它进行进程沙箱,但理论上可能在没有通知的情况下发生变化。BestEffort 在 API 行为异常时优雅降级,而不是阻止应用运行。使用 `--sandbox-strict` 可要求沙箱生效,否则中止。
|
||
|
||
### 剪贴板行为
|
||
|
||
与 Linux Landlock 不同,在 Seatbelt 下剪贴板复制(`c` 键)正常工作。macOS 剪贴板使用 NSPasteboard,通过 Mach IPC 在 Unix domain socket 上通信——SBPL 配置文件显式允许 `(network-outbound (remote unix-socket))`。
|
||
|
||
在 Linux 上,剪贴板需要访问 Wayland socket(`/run/user/UID/wayland-0`)或 X11 socket(`/tmp/.X11-unix/`)。Landlock 的拒绝默认模型会阻止这些,因为它们不在写路径的允许列表中,因此当 Landlock 激活时剪贴板不可用。
|
||
|
||
## FreeBSD 沙箱<a id="freebsd-sandboxing"></a>
|
||
|
||
FreeBSD 当前未启用沙箱。计划使用 `cap_enter()` 配合 `libcasper` 实现完整的 Capsicum 沙箱,用于特权进程查找——详见 [ROADMAP.md](ROADMAP.md)。
|
||
|
||
### root uid 降权
|
||
|
||
在 Capsicum 落地之前,FreeBSD 上的主要隔离手段是 root 降权:以 root 启动时(如 `sudo rustnet`),BPF 捕获设备打开后,进程通过 `setresuid`/`setresgid` 降权到调用用户(`SUDO_UID`/`SUDO_GID`)或 `nobody`。已打开的捕获和日志/导出文件描述符继续有效,预创建的导出文件会移交给目标用户。注意 `doas` 不设置 `SUDO_UID`,因此 doas 用户会回退到 `nobody`。
|
||
|
||
权衡:进程归属使用 `sockstat`,非 root 用户只能看到目标用户的 socket。如需归属其他用户的进程,请使用 `--no-uid-drop`。
|
||
|
||
```
|
||
--no-uid-drop 初始化后保持 root 运行,
|
||
不降权到 SUDO_UID/SUDO_GID(或 nobody)
|
||
```
|
||
|
||
## 权限剥离与 Job Object 沙箱(Windows)<a id="privilege-drop-and-job-object-sandboxing-windows"></a>
|
||
|
||
在 Windows 上,RustNet 在初始化后从进程令牌中移除危险特权,并应用 Job Object 阻止子进程创建。
|
||
|
||
### 受限制的内容
|
||
|
||
| 限制项 | 描述 |
|
||
|--------|------|
|
||
| 特权移除 | 永久移除 SeDebugPrivilege、SeTakeOwnershipPrivilege、SeBackupPrivilege、SeRestorePrivilege 等危险特权 |
|
||
| 子进程 | Job Object 阻止创建子进程(反向 shell、基于 exec 的数据外泄) |
|
||
|
||
### 工作原理
|
||
|
||
1. **初始化阶段**:RustNet 打开 Npcap 句柄并创建日志文件
|
||
2. **特权移除**:`AdjustTokenPrivileges` 配合 `SE_PRIVILEGE_REMOVED` 永久从进程令牌中剥离危险特权
|
||
3. **Job Object**:应用 `JOB_OBJECT_LIMIT_ACTIVE_PROCESS = 1` 的 Job Object,阻止任何子进程创建
|
||
|
||
### 安全收益
|
||
|
||
如果攻击者利用 DPI/包解析中的漏洞:
|
||
- 无法调试其他进程(SeDebugPrivilege 已移除)
|
||
- 无法取得任意文件的所有权(SeTakeOwnershipPrivilege 已移除)
|
||
- 无法通过 ACL 绕过读取文件(SeBackupPrivilege 已移除)
|
||
- 无法生成子进程(cmd.exe、powershell.exe、curl.exe —— 被 Job Object 阻止)
|
||
- 无法加载内核驱动(SeLoadDriverPrivilege 已移除)
|
||
|
||
### 局限性
|
||
|
||
Windows 沙箱弱于 Linux/macOS/FreeBSD:
|
||
- 无文件系统限制 —— Windows 缺少与 Landlock 或 Seatbelt 等效的进程级文件系统沙箱
|
||
- 无网络限制 —— 阻止出站会中断 Npcap 包捕获
|
||
- 特权移除仅影响提升进程已拥有的特权
|
||
|
||
### CLI 选项
|
||
|
||
```
|
||
--no-sandbox 禁用特权移除和 Job Object
|
||
--sandbox-strict 要求完整沙箱强制生效,否则退出
|
||
```
|
||
|
||
## 权限需求<a id="privilege-requirements"></a>
|
||
|
||
RustNet 需要特权访问来捕获网络数据包:
|
||
|
||
| 平台 | 需求 |
|
||
|------|------|
|
||
| Linux | `CAP_NET_RAW` 这项 Linux capability 或 root |
|
||
| macOS | Root 或 BPF 组成员(`access_bpf` 组) |
|
||
| Windows | Administrator(用于 Npcap) |
|
||
| FreeBSD | Root 或 BPF 设备访问 |
|
||
|
||
### 为什么需要特权
|
||
|
||
- **Raw socket 访问** —— 在低层拦截网络流量(只读、非混杂模式)
|
||
- **BPF 设备访问** —— 将包过滤器加载到内核
|
||
- **eBPF 程序** —— 可选的内核探针,用于增强进程追踪(仅限 Linux)
|
||
|
||
### 推荐:基于 Linux capabilities 的执行(Linux)
|
||
|
||
与其以 root 运行,不如仅授予所需的 Linux capabilities:
|
||
|
||
```bash
|
||
# 现代 Linux(5.8+):包捕获 + eBPF
|
||
sudo setcap 'cap_net_raw,cap_bpf,cap_perfmon+eip' $(which rustnet)
|
||
|
||
# 仅包捕获(eBPF 会回退到 procfs)
|
||
sudo setcap cap_net_raw+eip $(which rustnet)
|
||
```
|
||
|
||
旧版 pre-5.8 内核需要宽泛的 `CAP_SYS_ADMIN` 才能执行 eBPF 操作。RustNet
|
||
的安装包不会自动授予该 capability;除非你明确接受该风险,否则请只授予
|
||
`CAP_NET_RAW` 并使用 procfs 回退。沙箱应用后,`CAP_NET_RAW` 和 eBPF
|
||
加载相关 capabilities 会被丢弃——进程仅保留所需的最小特权。
|
||
|
||
## 只读操作<a id="read-only-operation"></a>
|
||
|
||
RustNet 仅监控流量,不会:
|
||
- 修改数据包
|
||
- 阻断连接
|
||
- 注入流量
|
||
- 更改路由表
|
||
- 更改防火墙规则
|
||
|
||
包捕获以非混杂、只读模式打开。
|
||
|
||
## 不主动对外通信<a id="no-external-communication"></a>
|
||
|
||
RustNet 完全在本地运行:
|
||
- 无遥测或分析
|
||
- 无网络请求(除监控的流量外)
|
||
- 无云服务或远程 API
|
||
- 所有数据保留在你的系统上
|
||
|
||
## 日志文件隐私<a id="log-file-privacy"></a>
|
||
|
||
日志文件可能包含敏感信息:
|
||
- IP 地址和端口
|
||
- 主机名和 SNI 数据
|
||
- 进程名和 PID
|
||
- DNS 查询和响应
|
||
|
||
**最佳实践:**
|
||
- 默认禁用日志记录(不使用 `--log-level` 标志)
|
||
- 保护日志目录权限
|
||
- 实施日志轮转和保留策略
|
||
- 分享前检查日志中的敏感数据
|
||
|
||
## eBPF 安全<a id="ebpf-security"></a>
|
||
|
||
使用 eBPF 进行增强型进程检测时(Linux 默认):
|
||
|
||
- 现代内核需要额外的 Linux capabilities(`CAP_BPF`、`CAP_PERFMON`)
|
||
- eBPF 程序在加载前由内核验证
|
||
- 仅限只读操作(不修改数据包)
|
||
- 如果 eBPF 失败,自动回退到 procfs
|
||
|
||
## 威胁模型<a id="threat-model"></a>
|
||
|
||
**RustNet 防护的内容:**
|
||
- 未经授权的用户无法在没有适当权限的情况下捕获数据包
|
||
- 基于 Linux capabilities 的权限限制了被入侵后的影响范围
|
||
- Landlock(Linux)和 Seatbelt(macOS)沙箱限制潜在的漏洞利用
|
||
|
||
**RustNet 不防护的内容:**
|
||
- 拥有包捕获权限的用户可以看到所有未加密的流量
|
||
- Root/Administrator 用户可以直接修改 RustNet 或捕获数据包
|
||
- 对机器的物理访问可以捕获数据包
|
||
- 网络级攻击(RustNet 是监控工具,不是安全设备)
|
||
|
||
### 以 Root 身份运行时的沙箱
|
||
|
||
Landlock(Linux)和 Seatbelt(macOS)即使在 RustNet 以 root(UID 0)运行时也会强制执行限制。沙箱一旦应用就无法从进程内部撤销——在 Linux 上,RustNet 会在应用任何限制之前直接设置 `PR_SET_NO_NEW_PRIVS`(Landlock 也需要并会同样设置它),该设置对每个进程不可逆,并且即使使用 `--no-sandbox` 也会应用。
|
||
|
||
然而,沙箱**不能**防护供应链攻击。被入侵的二进制文件可以直接不应用沙箱。Root 也可以:
|
||
- 传递 `--no-sandbox` 完全跳过沙箱(`PR_SET_NO_NEW_PRIVS` 除外)
|
||
- 卸载 Landlock LSM 内核模块
|
||
- 在 macOS 上禁用 SIP(控制沙箱强制执行)
|
||
- 使用 `ptrace` 修改运行中的进程
|
||
|
||
因此,强烈建议使用细粒度 Linux capabilities(`setcap cap_net_raw=eip`)运行,而不是以 root 运行。
|
||
|
||
## 供应链安全<a id="supply-chain-security"></a>
|
||
|
||
RustNet 采取以下措施防护供应链攻击:
|
||
|
||
- **依赖锁文件**:`Cargo.lock` 已提交到仓库,固定所有传递依赖版本并记录源校验和。这防止静默版本升级。
|
||
- **安全审计**:`cargo deny check` 在每次 push 和 pull request 时于 CI 中运行,对照 RustSec Advisory Database 检查依赖,并强制执行许可证、来源和通配符版本策略(`deny.toml`)。一个每日定时工作流会针对已提交的 `Cargo.lock` 重新检查安全公告,因此新发布的公告无需 push 即可被发现。
|
||
- **CI action 固定**:所有 GitHub Actions 均通过 commit SHA(而非标签)固定,防止对上游 action 的标签重写攻击。
|
||
- **保守的依赖策略**:新依赖需要说明理由,并审查其维护状态和安全记录(参见 [CONTRIBUTING.zh-CN.md](CONTRIBUTING.zh-CN.md))。
|
||
- **构建时完整性**:Windows Npcap SDK 下载在 `build.rs` 中对照硬编码的 SHA256 校验和进行验证。
|
||
- **代码签名**:macOS 发布版本使用 Apple Developer 证书签名并进行公证。
|
||
- **校验和验证**:所有打包工作流(Homebrew、Chocolatey、AUR)在发布前计算并双重验证 SHA256 校验和。
|
||
|
||
### 局限性
|
||
|
||
- `cargo install rustnet` 从 crates.io 获取最新兼容版本,并**不**使用 `Cargo.lock`。从源码构建的用户应验证源 tarball 校验和。
|
||
- 构建脚本(`build.rs`)和 proc-macros 在编译时执行任意代码。虽然所有当前依赖都是久经考验的 crate,但这是 Rust 构建模型的固有风险。
|
||
|
||
## 审计与合规<a id="audit-and-compliance"></a>
|
||
|
||
对于生产环境:
|
||
- 审计记录谁以包捕获权限运行 RustNet
|
||
- 网络监控策略和数据保护法规合规
|
||
- 对特权网络访问进行用户访问审查
|
||
- 通过配置管理系统实现自动化 Linux capabilities 管理
|
||
|
||
## 报告安全问题<a id="reporting-security-issues"></a>
|
||
|
||
请通过 GitHub Issues 报告安全漏洞,或直接与维护者联系。
|