72f2e1a897
Signed-off-by: Fanjun Kong <kongfanjun@iscas.ac.cn>
276 lines
6.5 KiB
Markdown
276 lines
6.5 KiB
Markdown
# RPM 包安全特性大规模扫描系统
|
||
|
||
## 架构概览
|
||
|
||
```
|
||
[包列表获取] → [并行下载] → [ELF提取] → [安全扫描] → [结果入库] → [报告生成]
|
||
```
|
||
|
||
## 快速开始
|
||
|
||
### 1. 安装依赖
|
||
|
||
```bash
|
||
# RHEL/Fedora/OpenEuler
|
||
sudo dnf install -y parallel checksec sqlite
|
||
|
||
# 验证
|
||
checksec --version
|
||
parallel --version
|
||
```
|
||
|
||
### 2. 初始化
|
||
|
||
```bash
|
||
chmod +x *.sh
|
||
./01_init.sh
|
||
```
|
||
|
||
### 3. 执行扫描(无锁设计)
|
||
|
||
```bash
|
||
# 全量扫描(8 并发)
|
||
./02_scan.sh
|
||
|
||
# 自定义并发数
|
||
PARALLEL_JOBS=16 ./02_scan.sh
|
||
```
|
||
|
||
**输出文件**:
|
||
- `results/scanned.txt` - 已扫描包列表
|
||
- `results/success.txt` - 扫描成功的包
|
||
- `results/failed.txt` - 扫描失败的包
|
||
- `results/no_binary.txt` - 无二进制文件的包
|
||
- `results/*.json` - checksec 原始结果
|
||
|
||
### 4. 导入数据库
|
||
|
||
```bash
|
||
python3 04_import_results.py scan_workspace/results/scan_results.db scan_workspace/results
|
||
```
|
||
|
||
**为什么分两步?**
|
||
- 避免多进程并发写入 SQLite 导致的数据库锁定问题
|
||
- 扫描阶段只写文件,完全无锁
|
||
- 导入阶段串行处理,安全可靠
|
||
|
||
### 5. 生成报告
|
||
|
||
```bash
|
||
# 生成统计报告和 CSV
|
||
python3 03_report.py scan_workspace/results/scan_results.db --export-csv report.csv
|
||
```
|
||
|
||
## 核心设计
|
||
|
||
### 1. 预筛选策略
|
||
|
||
**问题**: `dnf repoquery` 默认包含 noarch 包,浪费资源
|
||
|
||
**解决方案**:
|
||
```bash
|
||
# 只查询 x86_64 架构(注意格式字符串末尾的 \n)
|
||
dnf repoquery --arch x86_64 --qf '%{name}-%{version}-%{release}.%{arch}\n'
|
||
|
||
# 进一步过滤:检查是否包含目标路径
|
||
dnf repoquery --arch x86_64 --list PACKAGE | grep -E '^/(usr/)?(bin|sbin|lib64)'
|
||
```
|
||
|
||
### 2. 软链接去重
|
||
|
||
**问题**: `/bin` → `/usr/bin` 导致重复扫描
|
||
|
||
**解决方案**:
|
||
```bash
|
||
# 使用 inode 去重
|
||
find . -type f -exec stat -c '%i %n' {} \; | sort -u -k1,1 | cut -d' ' -f2-
|
||
```
|
||
|
||
### 3. 并行优化
|
||
|
||
**三级并行**:
|
||
- **包级**: GNU Parallel 并行处理包 (`-j 8`)
|
||
- **下载级**: DNF 并行下载配置
|
||
- **扫描级**: checksec 批量调用
|
||
|
||
### 4. 无锁设计(重要)
|
||
|
||
**问题**: SQLite 不支持多进程并发写入
|
||
|
||
**解决方案**: 扫描与入库分离
|
||
```bash
|
||
# 扫描阶段:多进程并发写文件(无锁)
|
||
./02_scan.sh
|
||
→ results/scanned.txt
|
||
→ results/success.txt
|
||
→ results/failed.txt
|
||
→ results/*.json
|
||
|
||
# 导入阶段:单进程串行入库(安全)
|
||
python3 04_import_results.py scan_results.db results/
|
||
```
|
||
|
||
**关键改进**:
|
||
- ✅ 使用 `flock` 文件锁代替数据库查询
|
||
- ✅ 状态文件代替数据库状态查询
|
||
- ✅ JSON 文件存储中间结果
|
||
- ✅ 批量导入代替逐条写入
|
||
|
||
### 5. 增量扫描
|
||
|
||
文件记录扫描状态,跳过已扫描的包:
|
||
```bash
|
||
# 使用 flock 检查是否已扫描
|
||
if grep -qx "$pkg_name" "$status_file"; then
|
||
echo "跳过已扫描: $pkg_name"
|
||
exit 0
|
||
fi
|
||
```
|
||
|
||
### 6. 错误处理
|
||
|
||
- 下载超时: `timeout 300s`
|
||
- 解压失败: 记录状态到数据库
|
||
- checksec 超时: 单包超时 60s
|
||
- 重试机制: `parallel --retries 2`
|
||
|
||
## 数据库 Schema
|
||
|
||
```sql
|
||
packages (id, name, version, status, scan_time)
|
||
↓
|
||
binaries (id, package_id, file_path, inode)
|
||
↓
|
||
security_checks (id, binary_id, pie, nx, canary, fortify, relro)
|
||
```
|
||
|
||
## 配置文件
|
||
|
||
编辑 `config.sh`:
|
||
```bash
|
||
PARALLEL_JOBS=8 # 并发数
|
||
REPO_ARCH="x86_64" # 架构
|
||
DOWNLOAD_TIMEOUT=300 # 下载超时
|
||
CHECKSEC_TIMEOUT=60 # 扫描超时
|
||
```
|
||
|
||
## 输出文件
|
||
|
||
```
|
||
scan_workspace/
|
||
├── rpm_cache/ # RPM 缓存
|
||
├── extracted/ # 临时解压目录
|
||
├── results/
|
||
│ ├── scan_results.db # SQLite 数据库
|
||
│ ├── *.json # checksec 原始结果
|
||
│ └── report.csv # CSV 报告
|
||
├── scan.log # 运行日志
|
||
├── error.log # 错误日志
|
||
└── parallel.log # GNU Parallel 日志
|
||
```
|
||
|
||
## 性能估算
|
||
|
||
- 单包平均耗时: 10-30s (下载 + 解压 + 扫描)
|
||
- 8 并发处理 1000 个包: ~30-60 分钟
|
||
- 磁盘空间: 每包 10-100MB (临时)
|
||
|
||
## 故障排查
|
||
|
||
### 1. checksec 找不到
|
||
|
||
```bash
|
||
# 从源码安装
|
||
git clone https://github.com/slimm609/checksec.sh
|
||
sudo cp checksec /usr/local/bin/
|
||
```
|
||
|
||
### 2. 并行任务失败
|
||
|
||
查看日志:
|
||
```bash
|
||
tail -f scan_workspace/error.log
|
||
cat scan_workspace/parallel.log | grep -v "^0"
|
||
```
|
||
|
||
### 3. 数据库锁定(已解决)
|
||
|
||
**旧版本问题**:
|
||
```
|
||
Error: in prepare, database is locked (5)
|
||
```
|
||
|
||
**新版本解决方案**:
|
||
- ✅ 采用无锁设计,扫描阶段不写数据库
|
||
- ✅ 使用文件锁 `flock` 代替数据库锁
|
||
- ✅ 扫描完成后批量导入
|
||
|
||
如果仍然遇到数据库锁定:
|
||
```bash
|
||
# 删除锁文件
|
||
rm scan_workspace/results/scanned.txt.lock
|
||
|
||
# 或使用新的工作目录重新扫描
|
||
WORK_DIR="./scan_workspace_new" ./02_scan.sh
|
||
```
|
||
|
||
## 扩展功能
|
||
|
||
### 1. 分布式扫描
|
||
|
||
```bash
|
||
# 机器 A: 扫描前 500 个包
|
||
head -500 packages.list | parallel -j 8 ./scan_package.sh
|
||
|
||
# 机器 B: 扫描后 500 个包
|
||
tail -500 packages.list | parallel -j 8 ./scan_package.sh
|
||
|
||
# 合并结果文件(不需要合并数据库)
|
||
cat machine_a/results/*.json > all_results.json
|
||
cat machine_a/results/success.txt machine_b/results/success.txt > all_success.txt
|
||
|
||
# 导入到统一数据库
|
||
python3 04_import_results.py merged.db results/
|
||
```
|
||
|
||
**优势**:
|
||
- 每台机器独立工作,无数据库冲突
|
||
- 只需合并文本文件和 JSON
|
||
- 最后统一导入数据库
|
||
|
||
### 2. 定时增量扫描
|
||
|
||
```bash
|
||
# crontab
|
||
0 2 * * * cd /path/to/scan && ./01_init.sh && ./02_scan.sh && python3 04_import_results.py scan_results.db results/
|
||
```
|
||
|
||
### 3. Web Dashboard
|
||
|
||
使用 Grafana + SQLite 数据源可视化结果
|
||
|
||
## 潜在问题与解决方案
|
||
|
||
| 问题 | 影响 | 解决方案 |
|
||
|------|------|----------|
|
||
| noarch 包误扫描 | 浪费资源 | `--arch x86_64` 预筛选 |
|
||
| 软链接重复 | 结果重复 | inode 去重 |
|
||
| 内核模块误报 | PIE 检查不适用 | 排除 `*.ko` |
|
||
| 网络不稳定 | 下载失败 | 超时 + 重试 |
|
||
| 磁盘空间不足 | 解压失败 | 及时清理临时文件 |
|
||
| checksec 慢 | 扫描耗时长 | 批量调用 + 并行 |
|
||
|
||
## 最佳实践
|
||
|
||
1. **首次运行**: 先用小样本测试 (`head -10 packages.list`)
|
||
2. **并发调优**: 根据 CPU 核心数和网络带宽调整 `PARALLEL_JOBS`
|
||
3. **磁盘管理**: 定期清理 `rpm_cache/` 和 `extracted/`
|
||
4. **日志监控**: 实时查看 `tail -f scan.log`
|
||
5. **增量更新**: 定期运行获取新包
|
||
|
||
## 参考资料
|
||
|
||
- [checksec 文档](https://github.com/slimm609/checksec.sh)
|
||
- [GNU Parallel 教程](https://www.gnu.org/software/parallel/parallel_tutorial.html)
|
||
- [RPM 包管理](https://rpm.org/)
|