#!/usr/bin/env bash # 只采集,不修改。用法: collect.sh [user@]host # 输出写到 stdout;完整日志建议重定向到文件。 set -euo pipefail HOST="${1:?usage: collect.sh user@host}" SSH=(ssh -o BatchMode=yes -o ConnectTimeout=15 "$HOST") echo "===== host-sec-check collect $(date -u +%Y-%m-%dT%H:%M:%SZ) host=$HOST =====" remote() { "${SSH[@]}" bash -s <<'EOS' set +e echo "----- id / os / uptime -----" id; hostname; uname -a . /etc/os-release 2>/dev/null; echo "$PRETTY_NAME" uptime echo "----- mem / hugepage / disk -----" free -h grep -E "MemAvailable|HugePages_|Hugetlb|SwapTotal" /proc/meminfo df -h echo "----- addresses -----" ip -4 addr; ip -6 addr show scope global echo "----- listen (may miss pid without root) -----" ss -lntup 2>/dev/null || netstat -lntup 2>/dev/null echo "----- docker -----" docker ps -a --format "table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}" 2>/dev/null || echo "no docker" echo "----- preload (host cat; may be hidden) -----" ls -la /etc/ld.so.preload 2>/dev/null echo "preload_host=[$(cat /etc/ld.so.preload 2>/dev/null)]" echo "----- cron files -----" ls -la /etc/cron.d /etc/cron.hourly /etc/cron.daily 2>/dev/null echo "----- systemd extras -----" ls /etc/systemd/system/*.service 2>/dev/null echo "----- users uid0 / human -----" awk -F: '$3==0 || ($3>=1000 && $3<65534) {print}' /etc/passwd echo "----- sshd -----" grep -E "^(Port|PermitRootLogin|PasswordAuthentication|PubkeyAuthentication)" /etc/ssh/sshd_config /etc/ssh/sshd_config.d/* 2>/dev/null sshd -t 2>&1 && echo "sshd_syntax=ok" || echo "sshd_syntax=failed" sshd -T 2>/dev/null | grep -iE "^(port|permitrootlogin|passwordauthentication|kbdinteractiveauthentication|pubkeyauthentication|permitempty passwords|maxauthtries|x11forwarding|allowtcpforwarding) " systemctl is-active ssh.service sshd.service ssh.socket 2>/dev/null systemctl is-enabled ssh.service sshd.service ssh.socket 2>/dev/null echo "----- last -----" last -a | head -15 echo "----- failed logins -----" lastb -a 2>/dev/null | head -15 || true echo "----- firewall -----" ufw status verbose 2>/dev/null || true firewall-cmd --state 2>/dev/null || true nft list ruleset 2>/dev/null | head -160 || true iptables -S 2>/dev/null | head -160 || true echo "----- fail2ban -----" fail2ban-client status 2>/dev/null || echo "fail2ban unavailable" echo "----- timers -----" systemctl list-timers --all --no-pager 2>/dev/null | head -120 echo "----- privilege groups -----" getent group sudo wheel docker lxd libvirt 2>/dev/null || true echo "----- outbound connections (numeric) -----" ss -tpn 2>/dev/null | head -160 || true echo "----- docker security summary -----" docker ps -a --format '{{.Names}} ports={{.Ports}}' 2>/dev/null || true docker inspect --format '{{.Name}} privileged={{.HostConfig.Privileged}} pid={{.HostConfig.PidMode}} network={{.HostConfig.NetworkMode}} binds={{len .Mounts}}' $(docker ps -aq 2>/dev/null) 2>/dev/null || true EOS } remote # docker 组:从容器读 preload 真值 if "${SSH[@]}" "groups | grep -qw docker" 2>/dev/null; then echo "----- preload via alpine (bypass host libc) -----" "${SSH[@]}" "docker run --rm --privileged -v /:/host alpine:latest hexdump -C /host/etc/ld.so.preload" 2>/dev/null \ || echo "alpine hexdump failed" fi echo "===== collect done =====" echo "下一步: 按 SKILL.md 解读;不要把本输出里的密钥贴进聊天。"