feat: add host-sec-check

This commit is contained in:
2026-09-04 11:54:07 +08:00
parent ef22c6829e
commit b28acc66d8
9 changed files with 539 additions and 0 deletions
@@ -0,0 +1,48 @@
# 取证入口(无 sudo 时)
目标用户常见情况:`sudo` 要密码,但在 `docker` 组。Docker 组等价于 root。
## 读宿主机(绕过 preload)
容器用自己的 libc,**不会**加载宿主机 `/etc/ld.so.preload`。
```bash
docker run --rm -i --privileged --pid=host --net=host -v /:/host alpine:latest \
cat /host/etc/ld.so.preload
hexdump -C /host/etc/ld.so.preload # 在容器内
```
列隐藏进程:
```bash
docker run --rm --privileged --pid=host alpine:latest sh -c \
'for d in /proc/[0-9]*; do
pid=${d#/proc/}
cmd=$(tr "\0" " " < "$d/cmdline" 2>/dev/null) || continue
[ -n "$cmd" ] && echo "$pid $cmd"
done'
```
## 改宿主机(systemctl / chattr)
`chroot /host /usr/bin/chattr` 会加载宿主机 preload,可能 **stat 不到** 被藏文件。
用发行版自带 chattr 操作 `/host/...`:
```bash
docker run --rm -i --privileged --pid=host --net=host -v /:/host ubuntu:latest bash -s <<'SH'
chattr -i /host/etc/ld.so.preload
: > /host/etc/ld.so.preload
chattr +i /host/etc/ld.so.preload
lsattr /host/etc/ld.so.preload
SH
```
systemctl / reboot:
```bash
docker run --rm --privileged --pid=host --net=host alpine:latest \
nsenter -t 1 -m -u -i -n -- systemctl disable --now UNIT
```
不要用业务容器当跳板;用 `alpine`/`ubuntu` 临时 `--rm`。查到一半时 `docker ps` 里出现的随机名容器就是这个跳板。
@@ -0,0 +1,75 @@
# 检查项
每项:做了 / 跳过(原因) / 可疑。
## A. 快照
- [ ] 主机名、OS、内核、uptime
- [ ] CPU/负载、内存(含 hugepage、swap)、磁盘
- [ ] 网卡:公网 v4/v6、内网、Tailscale/VPN
- [ ] 监听端口 + 进程(root `ss -lntup`)
- [ ] 本机防火墙:nft/iptables/ufw;INPUT 策略
- [ ] 云安全组(做不到就写未查)
## B. 身份与入口
- [ ] sshd 配置:端口、root、密码、Pubkey
- [ ] 所有 `authorized_keys`(含 `/root`、`/home/*`、git 用户)
- [ ] `last` / `lastb` / ssh journal
- [ ] `/etc/passwd` 里 UID 0、系统外用户、登录 shell
- [ ] sudoers、NOPASSWD、docker/lxd/libvirt 组
- [ ] 对公网的面板:Portainer/Cockpit/宝塔/1Panel/k8s/Docker TCP
- [ ] 单元或 compose 里的明文 bootstrap token
## C. 持久化
- [ ] 用户与系统 crontab、`/etc/cron.*`
- [ ] systemd 服务与 timer(非发行版默认)
- [ ] `ld.so.preload` + 对应 `.so`(容器视角 hexdump)
- [ ] `lsattr` 发现的 immutable 异常文件
- [ ] rc.local、profile.d、bashrc 后门
- [ ] 第二套 container runtime
## D. 进程
- [ ] 容器视角 vs 宿主机 `ps` 差集
- [ ] maps 里的陌生 so
- [ ] 矿机/挖矿/proxy 客户端特征名与同体积副本
- [ ] hugepage 预留但无对应进程
- [ ] 高 CPU 且不在业务名单里的进程
## E. 容器 / k8s
- [ ] `docker ps -a` 与 inspect(privileged、sock、`/:/host`、`0.0.0.0`)
- [ ] 挂机赚钱镜像名单
- [ ] k8s/k3s:节点 Ready、该节点 Pod 列表
- [ ] API 不通时:kubelet `pods/*/etc-hosts`、crictl
- [ ] 工作负载是否能在 Git/编排仓库里对上
## F. 暴露
- [ ] `0.0.0.0` 与 `[::]` 监听清单
- [ ] **从外部**扫公网 IPv4(和 IPv6)
- [ ] rpcbind :111、DB、Redis、应用明文端口
- [ ] 管理 SSH 是否可收到 VPN
- [ ] 证书/域名入口是否可被 IP:端口绕过
## G. 补漏(容易漏)
- [ ] IPv6 公网地址同样在听
- [ ] 世界可写目录、异常 SUID
- [ ] 未装补丁 / `apt` 过期很久
- [ ] 出站长连接(C2)
- [ ] 内核模块 `lsmod` 异常
- [ ] 时间偏移(影响日志)
- [ ] 备份是否还能用(清理前不要删数据盘)
- [ ] 日志被清空或 journal vacuum
- [ ] Web 根目录 webshell(若有 nginx/caddy/apache)
- [ ] `.git` 或备份文件可下载
- [ ] 云 metadata 是否对容器开放
## H. 报告与处置确认
- [ ] 结论表
- [ ] 用户确认后再改
- [ ] 改完公网+域名双侧验证
@@ -0,0 +1,59 @@
# 处置(用户同意之后)
## 顺序
1. 证据已落盘(collect 输出、关键 ls/hexdump/docker inspect)
2. 停进程 / 停容器
3. 删或占位文件
4. 关公网端口
5. 最后才重启(卸已加载的隐藏库)
## 占位
对矿机路径(`/usr/bin/perfcc`、`/tmp/javavm64`、隐藏 docker 根名等):
```bash
chattr -i "$path" 2>/dev/null
rm -f "$path"
: > "$path"
chmod 000 "$path"
chattr +i "$path"
```
`/etc/ld.so.preload` 占空文件,权限 `644` + immutable,让动态链接器能读到「没有库」。
目录型隐藏根:停进程并卸挂载后删除,再在同名路径放 immutable 空文件,阻止再 `mkdir`。
改 immutable 时用容器自带 `chattr` 操作 `/host/...`,不要 `chroot` 到宿主机再跑 chattr。见 access.md。
## 容器
```bash
docker stop NAME && docker rm NAME
docker rmi IMAGE
```
不要 `docker system prune -a`。
## 端口
- 入口在本机 loopback → `127.0.0.1:PORT:CONTAINER`
- 入口走 Tailscale 主机名 → 必须绑 **当前 Tailscale IPv4**,不能只绑 127.0.0.1
- 可同时绑 loopback + Tailscale,禁止 `0.0.0.0` / `[::]`
改 compose 后 force-recreate,再从公网 IP 和业务域名两边探测。
## rpcbind
```bash
systemctl disable --now rpcbind.socket rpcbind.service
systemctl mask rpcbind.socket rpcbind.service
```
无对外 NFS 再做。确认 `:111` 消失。
## 重启
重启前确认业务是 `restart: always`,并告知会短暂中断。
重启后:`grep -l <so名> /proc/*/maps` 为 0;若已清 hugepage 则 `HugePages_Total` 为 0;域名 200。
@@ -0,0 +1,40 @@
# 持久化位置
按这个名单扫。出现「刚被排查时时间戳又刷新」= 仍有 watchdog。
## 定时
- `crontab -u root -l` 以及 `/var/spool/cron/crontabs/*`
- `/etc/cron.d/` `/etc/cron.hourly/` `/etc/cron.daily/` `/etc/cron.weekly/`
- systemd timers:`systemctl list-timers --all`
## 服务
- `/etc/systemd/system/*.service`(尤其非发行版默认的)
- `ExecStart` 指向 `/tmp`、`/dev/shm`、`/root/.config`、随机短名
- `Restart=always` 的陌生服务
## 动态链接器
- `/etc/ld.so.preload`:内容常是 `/lib/libXXX.so`(约十几字节)。宿主机 `cat` 可能显示空,用容器 hexdump
- 对应 `.so` 可能 `chattr +i`、setgid
- 常见名:`libgcwrap.so`、`libpprocps.so`、`libfsnldev.so`、`libprocesshider.so`
## 二进制与分身
- `/usr/bin` `/bin` `/usr/local/bin` `/tmp` 下刚改过的可执行文件
- 同字节大小的多份拷贝(矿机改名)
- 占位对抗:空文件 `chmod 000` + `chattr +i`(见 cleanup.md)
## 隐藏运行时
- 第二套 dockerd:`--data-root` 不在 `/var/lib/docker`,`--host unix://` 异常路径
- 混淆文件名(把 docker 子命令拼进无关单词)
- k3s 的 `containerd-shim` 被借去跑 **moby** namespace + 异常 `--address`
## 登录与启动
- `~/.bashrc` / `/etc/profile.d` 里 curl|bash
- `/etc/rc.local`
- SSH `authorized_keys` 多出来的 key
- 意外用户、UID 0 非 root