安全扫描工具部署与使用文档
环境信息
| 项目 | 内容 |
|---|---|
| 操作系统 | CentOS 7 (阿里云 ECS) |
| 服务器配置 | 2 核 8GB 内存 |
一、Nikto
| 项目 | 内容 |
|---|---|
| 版本 | 2.6.0 (LW 2.5) |
| 部署方式 | Git 克隆 + Perl 依赖 |
| 安装路径 | /opt/nikto/ |
| 命令路径 | /usr/local/bin/nikto |
| 用途 | Web 服务器漏洞扫描(安全头检测、配置审计、敏感文件发现) |
安装命令:
sudo yum install -y perl-JSON perl-Time-Piece perl-Time-HiRes \
perl-XML-Writer perl-Net-SSLeay perl-IO-Socket-SSL
cd /opt
sudo git clone https://github.com/sullo/nikto.git
sudo ln -s /opt/nikto/program/nikto.pl /usr/local/bin/nikto
nikto -Version
使用示例:
nikto -h http://target.com -maxtime 600s -timeout 10
nikto -h http://target.com -o report.html -Format htm
二、afrog
| 项目 | 内容 |
|---|---|
| 版本 | 3.5.3 |
| PoC 版本 | 0.5.29 |
| PoC 数量 | 1648 个 |
| 部署方式 | 预编译二进制 + PoC 仓库 |
| 安装路径 | /opt/afrog/ |
| 命令路径 | /usr/local/bin/afrog |
| PoC 路径 | ~/.config/afrog/pocs/ |
| 用途 | PoC 漏洞验证(SQL 注入、RCE、文件上传、未授权访问等) |
安装命令:
cd /opt
AFROG_VERSION="3.5.3"
wget https://github.com/zan8in/afrog/releases/download/v${AFROG_VERSION}/afrog_${AFROG_VERSION}_linux_amd64.zip
unzip afrog_${AFROG_VERSION}_linux_amd64.zip -d afrog
chmod +x afrog/afrog
sudo ln -s /opt/afrog/afrog /usr/local/bin/afrog
cd /tmp
git clone https://github.com/zan8in/afrog-pocs.git
mkdir -p ~/.config/afrog/pocs
cp -r /tmp/afrog-pocs/CVE /tmp/afrog-pocs/CNOV /tmp/afrog-pocs/default-pwd \
/tmp/afrog-pocs/disclosure /tmp/afrog-pocs/fingerprinting \
/tmp/afrog-pocs/unauthorized /tmp/afrog-pocs/version \
/tmp/afrog-pocs/vulnerability \
~/.config/afrog/pocs/
afrog -version
find ~/.config/afrog/pocs/ -name "*.yaml" | wc -l
使用示例:
afrog -t http://target.com -curated off -S critical,high,medium
afrog -t http://target.com -curated off -o report.html -S critical,high,medium
注意事项:
- 需加
-curated off参数才能加载本地 PoC。 afrog二进制可能被 ClamAV 误报,ClamAV 扫描时需排除afrog目录。
三、ClamAV
| 项目 | 内容 |
|---|---|
| 版本 | 0.103.11 |
| 病毒库版本 | 28008 |
| 病毒特征数 | 3,627,426 |
| 部署方式 | yum 安装 + cvdupdate 更新病毒库 |
| 命令路径 | /usr/bin/clamscan |
| 病毒库路径 | /var/lib/clamav/ |
| 用途 | 病毒/木马/恶意文件扫描 |
安装命令:
sudo yum install -y clamav clamav-update clamav-server clamav-server-systemd
pip3 install cvdupdate
sudo cvd update
sudo cp ~/.cvdupdate/database/*.cvd /var/lib/clamav/
sudo chown clamupdate:clamupdate /var/lib/clamav/*.cvd
clamscan --version
使用示例:
clamscan -r --infected /home /var/www /tmp
clamscan -r --infected --log=/tmp/clamav.log /home /var/www /tmp
注意事项:
- CentOS 7 yum 仓库版本被官方 CDN 标记为过期,使用
cvdupdate绕过。 - 病毒库更新命令:
sudo cvd update。
四、统一扫描脚本
| 项目 | 内容 |
|---|---|
| 脚本路径 | /opt/scripts/security-scan.sh |
| 目标文件 | /opt/scripts/targets.txt |
| 报告目录 | /var/log/security-scans/ |
| 版本 | v4.2 |
| 集成工具 | Nikto + afrog + ClamAV + SSH 远程检查 |
4.1 功能说明
脚本支持 SSH 远程免安装扫描,通过密钥或密码认证登录目标服务器,执行以下检查:
| 检查模块 | 内容 |
|---|---|
| 系统信息 | OS、内核、资源使用、内核安全配置 |
| SSH 安全 | root 登录策略、授权密钥检查、后门检测 |
| 网络风险 | 危险端口暴露、SSH 暴力破解检测、外网连接 |
| 定时任务 | 可疑 crontab 检测(curl/wget/base64/eval) |
| Web 指纹 | 服务器版本识别、CVE 匹配、安全响应头、SSL 证书 |
| 恶意文件 | 高风险文件收集、哈希比对、ClamAV 远程扫描 |
4.2 目标列表配置
文件路径: /opt/scripts/targets.txt
# ============================================
# 安全扫描目标列表
# 格式: 每行一个目标,# 开头为注释
# ============================================
# ---- 密钥登录 ----
# root@192.168.0.1 # 默认密钥 + 默认端口(22)
# root@192.168.0.2 -i /root/.ssh/id_rsa # 指定密钥
# root@192.168.0.3 -p 50022 # 指定端口
# ---- 密码登录 ----
# root:password@192.168.0.4 # 密码 + 默认端口
# root:password@192.168.0.5:50022 # 密码 + 指定端口
格式说明:
| 格式 | 说明 | 示例 |
|---|---|---|
user@host |
密钥登录,默认端口 22 | root@192.168.0.1 |
user@host -i key |
密钥登录,指定私钥 | root@192.168.0.1 -i /root/.ssh/id_rsa |
user@host -p port |
密钥登录,指定端口 | root@192.168.0.1 -p 50022 |
user:pass@host |
密码登录,默认端口 22 | root:mypass@192.168.0.1 |
user:pass@host:port |
密码登录,指定端口 | root:mypass@192.168.0.1:50022 |
4.3 使用方法
# 查看帮助
./security-scan.sh --help
# 扫描
./security-scan.sh -l targets.txt
# 带全局选项
./security-scan.sh -l targets.txt \
-i /root/.ssh/id_rsa \
-p 22 \
-o /var/log/security-scans
# 跳过部分检查模块
./security-scan.sh -l targets.txt --skip-malware --skip-web
4.4 命令选项
| 选项 | 说明 | 默认值 |
|---|---|---|
-l, --list FILE |
目标列表文件(必需) | - |
-i, --ssh-key FILE |
全局默认 SSH 私钥 | - |
-p, --ssh-port PORT |
全局默认 SSH 端口 | 22 |
-o, --output DIR |
报告输出目录 | /var/log/security-scans |
--skip-malware |
跳过恶意文件扫描 | - |
--skip-sysinfo |
跳过系统信息收集 | - |
--skip-network |
跳过网络风险检查 | - |
--skip-web |
跳过 Web 指纹扫描 | - |
--skip-cron |
跳过定时任务检查 | - |
--max-file-size MB |
拉取文件最大尺寸 | 50 |
--max-file-count N |
每台最多拉取文件数 | 200 |
--help |
显示帮助 | - |
4.5 报告结构
/var/log/security-scans/20260602_164750/
├── summary.txt
├── root@192_168_1_100_22/
│ ├── sysinfo.txt
│ ├── ssh-security.txt
│ ├── network.txt
│ ├── crontab.txt
│ ├── web.txt
│ ├── filelist.txt
│ └── pulled-files/
4.6 扫描报告示例
========================================
安全扫描汇总报告 v4.2
时间: 2026-06-02 16:47:56
目标: 1 成功: 1 失败: 0
总耗时: 6秒
========================================
### [1/1] root@106.13.3.111:50022 [password] ###
[!] 危险端口: 1
[!] SSH暴力破解: 10次
[!] Web CVE: 8
========================================
报告: /var/log/security-scans/20260602_164750
========================================
五、扫描脚本完整代码
#!/bin/bash
# Security Scan Suite v4.2 (远程免安装扫描版 - 支持密钥/密码认证)
# ==================== 颜色定义 ====================
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
RED='\033[0;31m'
BOLD='\033[1m'
NC='\033[0m'
# ==================== 工具检查 ====================
check_tool() {
if ! command -v "$1" &>/dev/null; then
echo -e "${RED}[✗] 未找到: $1${NC}"
return 1
fi
return 0
}
echo -e "${CYAN}[*] 检查本地依赖工具...${NC}"
MISSING=0
for tool in curl nmap openssl md5sum sha256sum ssh scp; do
check_tool "$tool" || MISSING=1
done
[ "$MISSING" -eq 1 ] && { echo -e "${RED}[✗] 缺少必要工具${NC}"; exit 1; }
SSHPASS_AVAILABLE=0
command -v sshpass &>/dev/null && SSHPASS_AVAILABLE=1 && echo -e "${GREEN}[✓] sshpass: 可用${NC}" || echo -e "${YELLOW}[!] sshpass 未安装${NC}"
CLAMAV_LOCAL=0
command -v clamscan &>/dev/null && CLAMAV_LOCAL=1 && echo -e "${GREEN}[✓] 本地 ClamAV: 可用${NC}" || echo -e "${YELLOW}[!] ClamAV 未安装${NC}"
echo -e "${GREEN}[✓] 核心工具检查通过${NC}"
# ==================== CVE 知识库 ====================
declare -a CVE_DB=(
"nginx|1.27.4|CVE-2024-7347|HIGH|HTTP/3 QUIC 内存越界读取"
"nginx|1.27.3|CVE-2026-25836|HIGH|缓存投毒漏洞"
"nginx|1.25.4|CVE-2024-24989|HIGH|mp4_module 整数溢出"
"nginx|1.25.4|CVE-2024-24990|HIGH|mp4_module 内存越界读取"
"nginx|1.25.3|CVE-2023-44487|HIGH|HTTP/2 Rapid Reset DoS"
"nginx|1.23.5|CVE-2022-41741|HIGH|mp4_module 内存越界写入"
"nginx|1.23.5|CVE-2022-41742|HIGH|mp4_module 内存越界读取"
"nginx|1.17.8|CVE-2021-23017|HIGH|DNS 解析器越界写入"
"nginx|1.13.8|CVE-2017-7529|HIGH|整数溢出越界读取"
"Apache|2.4.63|CVE-2025-26603|HIGH|mod_proxy 跨源本地读取"
"Apache|2.4.62|CVE-2024-47252|HIGH|mod_ssl 无限循环 DoS"
"Apache|2.4.61|CVE-2024-38476|HIGH|路径遍历访问控制绕过"
"Apache|2.4.58|CVE-2023-45853|CRITICAL|minizip 整数溢出"
"Apache|2.4.52|CVE-2021-44790|CRITICAL|mod_lua RCE"
"Apache|2.4.51|CVE-2021-42013|CRITICAL|路径遍历绕过"
"Apache|2.4.49|CVE-2021-41773|CRITICAL|路径遍历任意文件读取"
"Apache|2.4.39|CVE-2019-0211|CRITICAL|特权提升"
"OpenSSL|3.4.1|CVE-2025-26603|HIGH|跨源资源本地读取"
"OpenSSL|3.4.0|CVE-2024-12797|HIGH|X.509 验证绕过"
"OpenSSL|3.3.0|CVE-2024-6119|HIGH|X.509 名称检查 DoS"
"OpenSSL|3.0.12|CVE-2023-6237|HIGH|RSA 解密 DoS"
"OpenSSL|1.1.1l|CVE-2021-3711|CRITICAL|SM2 缓冲区溢出"
"OpenSSL|1.1.1j|CVE-2021-3450|HIGH|X.509 验证绕过"
"Microsoft-IIS|10.0|CVE-2022-21907|CRITICAL|HTTP 协议栈 RCE"
"Microsoft-IIS|7.5|CVE-2017-7269|CRITICAL|WebDAV 缓冲区溢出"
)
version_compare() {
local v1="$1" v2="$2"
[ "$v1" = "$v2" ] && return 0
local IFS='.'
read -ra V1P <<< "$v1"
read -ra V2P <<< "$v2"
local mx=${#V1P[@]}
[ ${#V2P[@]} -gt $mx ] && mx=${#V2P[@]}
for ((i=0; i<mx; i++)); do
local a=${V1P[$i]:-0} b=${V2P[$i]:-0}
(( a > b )) && return 1
(( a < b )) && return 2
done
return 0
}
# ==================== 帮助信息 ====================
show_help() {
cat <<'EOF'
用法: ./security-scan.sh [选项] -l targets.txt
目标列表格式 (每行一台):
user@host 密钥登录
user@host -i key_file 密钥 (指定密钥)
user@host -p port 密钥 (指定端口)
user:password@host 密码登录
user:password@host:port 密码 (指定端口)
选项:
-l, --list FILE 目标列表 (必需)
-i, --ssh-key FILE 全局默认私钥
-p, --ssh-port PORT 全局默认 SSH 端口 (默认 22)
-o, --output DIR 报告输出目录
--skip-malware 跳过恶意文件扫描
--skip-sysinfo 跳过系统信息
--skip-network 跳过网络风险
--skip-web 跳过 Web 指纹
--skip-cron 跳过定时任务
--max-file-size MB 拉取文件最大尺寸 (默认 50)
--max-file-count N 每台最多拉取文件 (默认 200)
--help 显示帮助
EOF
exit 0
}
# ==================== 参数解析 ====================
TARGET_FILE=""
SSH_KEY=""
SSH_PORT=22
OUTPUT_DIR="/var/log/security-scans"
SKIP_MALWARE=0
SKIP_SYSINFO=0
SKIP_NETWORK=0
SKIP_WEB=0
SKIP_CRON=0
MAX_FILE_SIZE=50
MAX_FILE_COUNT=200
while [ $# -gt 0 ]; do
case "$1" in
-l|--list) TARGET_FILE="$2"; shift 2 ;;
-i|--ssh-key) SSH_KEY="$2"; shift 2 ;;
-p|--ssh-port) SSH_PORT="$2"; shift 2 ;;
-o|--output) OUTPUT_DIR="$2"; shift 2 ;;
--skip-malware) SKIP_MALWARE=1; shift ;;
--skip-sysinfo) SKIP_SYSINFO=1; shift ;;
--skip-network) SKIP_NETWORK=1; shift ;;
--skip-web) SKIP_WEB=1; shift ;;
--skip-cron) SKIP_CRON=1; shift ;;
--max-file-size) MAX_FILE_SIZE="$2"; shift 2 ;;
--max-file-count) MAX_FILE_COUNT="$2"; shift 2 ;;
--help) show_help ;;
*) echo -e "${RED}[✗] 未知选项: $1${NC}"; exit 1 ;;
esac
done
if [ -z "$TARGET_FILE" ] || [ ! -f "$TARGET_FILE" ]; then
echo -e "${RED}[✗] 请通过 -l 指定目标列表${NC}"
exit 1
fi
# ==================== 解析目标列表 ====================
declare -a T_USER=()
declare -a T_PASS=()
declare -a T_HOST=()
declare -a T_PORT=()
declare -a T_KEY=()
declare -a T_AUTH=()
LINE_NUM=0
while IFS= read -r rawline; do
LINE_NUM=$((LINE_NUM + 1))
local_line=$(echo "$rawline" | sed 's/#.*//' | xargs)
[ -z "$local_line" ] && continue
if echo "$local_line" | grep -qP '^[^:]+:[^@]+@'; then
AUTH_PART=$(echo "$local_line" | grep -oP '^[^:]+:[^@]+')
p_user=$(echo "$AUTH_PART" | cut -d: -f1)
p_pass=$(echo "$AUTH_PART" | cut -d: -f2)
HOST_PART=$(echo "$local_line" | sed 's/^[^@]*@//')
if echo "$HOST_PART" | grep -qP ':\d+$'; then
p_host=$(echo "$HOST_PART" | grep -oP '^[^:]+')
p_port=$(echo "$HOST_PART" | grep -oP ':\K\d+$')
else
p_host="$HOST_PART"
p_port="$SSH_PORT"
fi
p_key=""
p_auth="password"
if [ "$SSHPASS_AVAILABLE" -eq 0 ]; then
echo -e "${RED}[✗] 第${LINE_NUM}行需要密码但 sshpass 未安装${NC}"
continue
fi
else
local_uh=$(echo "$local_line" | grep -oP '^\S+')
p_user=$(echo "$local_uh" | cut -d@ -f1)
p_host=$(echo "$local_uh" | cut -d@ -f2)
p_pass=""
p_port="$SSH_PORT"
p_key="$SSH_KEY"
p_auth="key"
local_k=$(echo "$local_line" | grep -oP '\-i \K\S+')
[ -n "$local_k" ] && p_key="$local_k"
local_p=$(echo "$local_line" | grep -oP '\-p \K\d+')
[ -n "$local_p" ] && p_port="$local_p"
fi
if [ -z "$p_user" ] || [ -z "$p_host" ]; then
echo -e "${RED}[✗] 第${LINE_NUM}行格式错误${NC}"
continue
fi
T_USER+=("$p_user")
T_PASS+=("$p_pass")
T_HOST+=("$p_host")
T_PORT+=("$p_port")
T_KEY+=("$p_key")
T_AUTH+=("$p_auth")
done < "$TARGET_FILE"
TOTAL=${#T_HOST[@]}
if [ "$TOTAL" -eq 0 ]; then
echo -e "${RED}[✗] 无有效目标${NC}"
exit 1
fi
# ==================== 初始化 ====================
DATE=$(date +%Y%m%d_%H%M%S)
SCAN_DIR="${OUTPUT_DIR}/${DATE}"
mkdir -p "$SCAN_DIR"
SUCCESS=0
FAIL=0
SCRIPT_START=$(date +%s)
echo ""
echo -e "${BOLD}==================================================${NC}"
echo -e "${BOLD} Security Scan Suite v4.2${NC}"
echo -e " 目标: ${TOTAL} 时间: $(date '+%Y-%m-%d %H:%M:%S')"
echo -e " 报告: ${SCAN_DIR}"
echo -e "${BOLD}==================================================${NC}"
for ((i=0; i<TOTAL; i++)); do
al="密钥"
[ "${T_AUTH[$i]}" = "password" ] && al="密码"
echo -e " $((i+1)). ${T_USER[$i]}@${T_HOST[$i]}:${T_PORT[$i]} [${al}]"
done
echo -e "${BOLD}==================================================${NC}"
# ==================== 远程执行封装 ====================
remote_exec() {
local ru="$1" rp="$2" rh="$3" rpt="$4" rk="$5" ra="$6" rc="$7" rt="${8:-30}"
if [ "$ra" = "password" ]; then
export SSHPASS="$rp"
timeout "$rt" sshpass -e ssh \
-o StrictHostKeyChecking=no \
-o ConnectTimeout=10 \
-o PubkeyAuthentication=no \
-p "$rpt" \
"${ru}@${rh}" "$rc" 2>/dev/null
else
local ko=""
[ -n "$rk" ] && ko="-i $rk"
timeout "$rt" ssh \
-o StrictHostKeyChecking=no \
-o ConnectTimeout=10 \
-o BatchMode=yes \
-p "$rpt" $ko \
"${ru}@${rh}" "$rc" 2>/dev/null
fi
}
run_remote() {
local ru="$1" rp="$2" rh="$3" rpt="$4" rk="$5" ra="$6" rt="${7:-30}"
if [ "$ra" = "password" ]; then
export SSHPASS="$rp"
timeout "$rt" sshpass -e ssh \
-o StrictHostKeyChecking=no \
-o ConnectTimeout=10 \
-o PubkeyAuthentication=no \
-p "$rpt" \
"${ru}@${rh}" bash -s 2>/dev/null
else
local ko=""
[ -n "$rk" ] && ko="-i $rk"
timeout "$rt" ssh \
-o StrictHostKeyChecking=no \
-o ConnectTimeout=10 \
-o BatchMode=yes \
-p "$rpt" $ko \
"${ru}@${rh}" bash -s 2>/dev/null
fi
}
remote_scp() {
local ru="$1" rp="$2" rh="$3" rpt="$4" rk="$5" ra="$6" rf="$7" lf="$8"
if [ "$ra" = "password" ]; then
export SSHPASS="$rp"
timeout 15 sshpass -e scp \
-o StrictHostKeyChecking=no \
-o PubkeyAuthentication=no \
-P "$rpt" \
"${ru}@${rh}:${rf}" "$lf" 2>/dev/null
else
local ko=""
[ -n "$rk" ] && ko="-i $rk"
timeout 15 scp \
-o StrictHostKeyChecking=no \
-o BatchMode=yes \
-P "$rpt" $ko \
"${ru}@${rh}:${rf}" "$lf" 2>/dev/null
fi
}
# ==================== 系统信息收集 ====================
collect_sysinfo() {
local u="$1" p="$2" h="$3" pt="$4" k="$5" a="$6" td="$7"
echo -e " ${CYAN}[系统信息]${NC}"
run_remote "$u" "$p" "$h" "$pt" "$k" "$a" 30 << 'RCEOF' > "${td}/sysinfo.txt"
echo "============ 系统信息 ============="
echo "时间: $(date '+%Y-%m-%d %H:%M:%S')"
echo ""
echo "--- 操作系统 ---"
echo "OS: $(cat /etc/os-release 2>/dev/null | grep PRETTY_NAME | cut -d'"' -f2)"
echo "Kernel: $(uname -r)"
echo "Uptime: $(uptime -p 2>/dev/null || uptime)"
echo ""
echo "--- 系统资源 ---"
echo "CPU: $(nproc 2>/dev/null || echo N/A) 核"
echo "Load: $(cat /proc/loadavg 2>/dev/null | cut -d' ' -f1-3)"
echo "Memory: $(free -h 2>/dev/null | awk '/Mem/ {print $3"/"$2}')"
echo "Disk: $(df -h / 2>/dev/null | awk 'NR==2 {print $3"/"$2" ("$5" used)"}')"
echo ""
echo "--- 内核安全 ---"
echo "ASLR: $(cat /proc/sys/kernel/randomize_va_space 2>/dev/null)"
echo "SELinux: $(getenforce 2>/dev/null || echo N/A)"
echo "Firewalld: $(systemctl is-active firewalld 2>/dev/null || echo N/A)"
echo "Iptables rules: $(iptables -L -n 2>/dev/null | grep -cE '^[A-Z]' || echo 0)"
echo ""
echo "--- 长时间运行进程 ---"
ps -eo pid,user,comm,etime --sort=-etime 2>/dev/null | head -11
echo ""
echo "--- root 进程 ---"
ps -eo user,pid,comm 2>/dev/null | grep '^ *root' | head -20
RCEOF
echo -e " ${GREEN}[✓] ${td}/sysinfo.txt${NC}"
}
# ==================== SSH 安全检查 ====================
check_ssh_security() {
local u="$1" p="$2" h="$3" pt="$4" k="$5" a="$6" td="$7"
echo -e " ${CYAN}[SSH 安全]${NC}"
run_remote "$u" "$p" "$h" "$pt" "$k" "$a" 10 << 'RCEOF' > "${td}/ssh-security.txt"
echo "============ SSH 安全检查 ============="
echo ""
echo "--- SSH 版本 ---"
ssh -V 2>&1
echo ""
echo "--- SSH 配置 ---"
if [ -f /etc/ssh/sshd_config ]; then
echo "PermitRootLogin: $(grep -E '^PermitRootLogin' /etc/ssh/sshd_config 2>/dev/null | tail -1 || echo '未设置(默认)')"
echo "PasswordAuthentication: $(grep -E '^PasswordAuthentication' /etc/ssh/sshd_config 2>/dev/null | tail -1 || echo '未设置(默认)')"
echo "PubkeyAuthentication: $(grep -E '^PubkeyAuthentication' /etc/ssh/sshd_config 2>/dev/null | tail -1 || echo '未设置(默认)')"
echo "MaxAuthTries: $(grep -E '^MaxAuthTries' /etc/ssh/sshd_config 2>/dev/null | tail -1 || echo '未设置(默认)')"
else
echo "sshd_config 不存在"
fi
echo ""
echo "--- 授权密钥 ---"
for dir in /root /home/*; do
[ -d "$dir/.ssh" ] || continue
echo "[用户: $(basename $dir)]"
if [ -f "$dir/.ssh/authorized_keys" ]; then
KC=$(grep -c '^ssh-' "$dir/.ssh/authorized_keys" 2>/dev/null || echo 0)
echo " 密钥数: $KC"
AB=$(grep '^command=' "$dir/.ssh/authorized_keys" 2>/dev/null | wc -l)
[ "$AB" -gt 0 ] && echo " [!] command= 配置: $AB 个"
else
echo " authorized_keys: 不存在"
fi
done
echo ""
echo "--- SSH 相关文件 ---"
find /etc/ssh/ /tmp /var/tmp -name 'authorized_keys*' -o -name 'id_rsa*' 2>/dev/null | head -20
RCEOF
local RISK=0
grep -q "PermitRootLogin.*yes" "${td}/ssh-security.txt" 2>/dev/null && { echo -e " ${RED}[!] SSH 允许 root 密码登录${NC}"; RISK=1; }
grep -q "command=" "${td}/ssh-security.txt" 2>/dev/null && { echo -e " ${RED}[!] 检测到 authorized_keys command= (可能后门)${NC}"; RISK=1; }
[ "$RISK" -eq 0 ] && echo -e " ${GREEN}[✓] SSH 配置无明显风险${NC}"
echo -e " ${GREEN}[✓] ${td}/ssh-security.txt${NC}"
}
# ==================== 网络风险检查 ====================
check_network_risk() {
local u="$1" p="$2" h="$3" pt="$4" k="$5" a="$6" td="$7"
echo -e " ${CYAN}[网络风险]${NC}"
run_remote "$u" "$p" "$h" "$pt" "$k" "$a" 15 << 'RCEOF' > "${td}/network.txt"
echo "============ 网络风险检查 ============="
echo ""
echo "--- 监听端口 ---"
ss -tlnp 2>/dev/null || netstat -tlnp 2>/dev/null
echo ""
echo "--- 外网连接 ---"
ss -tnp state established 2>/dev/null | awk 'NR>1 {print $5,$6}' | grep -v '127.0.0.1\|::1' | sort -u | head -20
echo ""
echo "--- SSH 暴力破解检测 ---"
if [ -f /var/log/secure ]; then
grep 'Failed password' /var/log/secure 2>/dev/null | tail -10
elif [ -f /var/log/auth.log ]; then
grep 'Failed password' /var/log/auth.log 2>/dev/null | tail -10
else
journalctl -u sshd --since '24 hours ago' 2>/dev/null | grep -i 'failed\|invalid' | tail -10
fi
echo ""
echo "--- 最近登录 ---"
last -10 2>/dev/null
RCEOF
local RC=0
local DL
DL=$(grep -cE "0\.0\.0\.0:(3306|6379|27017|11211|9200|5432)" "${td}/network.txt" 2>/dev/null)
DL=${DL:-0}
local FC
FC=$(grep -c "Failed password" "${td}/network.txt" 2>/dev/null)
FC=${FC:-0}
if [ "$DL" -gt 0 ]; then
echo -e " ${RED}[!] ${DL} 个危险端口暴露在 0.0.0.0${NC}"
RC=$((RC + DL))
fi
if [ "$FC" -gt 5 ]; then
echo -e " ${RED}[!] SSH 暴力破解: ${FC} 次${NC}"
RC=$((RC + 1))
fi
if [ "$RC" -eq 0 ]; then
echo -e " ${GREEN}[✓] 未发现明显网络风险${NC}"
fi
echo -e " ${GREEN}[✓] ${td}/network.txt${NC}"
}
# ==================== 定时任务检查 ====================
check_crontab_risk() {
local u="$1" p="$2" h="$3" pt="$4" k="$5" a="$6" td="$7"
echo -e " ${CYAN}[定时任务]${NC}"
run_remote "$u" "$p" "$h" "$pt" "$k" "$a" 15 << 'RCEOF' > "${td}/crontab.txt"
echo "============ 定时任务检查 ============="
echo ""
echo "--- 用户 Crontab ---"
for usr in $(cut -d: -f1 /etc/passwd 2>/dev/null); do
CR=$(crontab -l -u "$usr" 2>/dev/null)
if [ -n "$CR" ] && ! echo "$CR" | grep -q '^no crontab'; then
echo "[User: $usr]"
echo "$CR"
echo ""
fi
done
echo "--- /etc/crontab ---"
cat /etc/crontab 2>/dev/null
echo ""
echo "--- /etc/cron.d/ ---"
for f in /etc/cron.d/*; do
[ -f "$f" ] && echo "[File: $f]" && cat "$f" 2>/dev/null && echo ""
done
RCEOF
local SP
SP=$(grep -ciE "curl.*\||wget.*\||python.*-c|perl.*-e|base64|eval|/dev/tcp" "${td}/crontab.txt" 2>/dev/null)
SP=${SP:-0}
if [ "$SP" -gt 0 ] 2>/dev/null; then
echo -e " ${RED}[!] 发现 ${SP} 条可疑定时任务${NC}"
grep -iE "curl.*\||wget.*\||python.*-c|perl.*-e|base64|eval|/dev/tcp" "${td}/crontab.txt" 2>/dev/null | head -5 | while IFS= read -r ln; do
echo -e " ${RED}${ln}${NC}"
done
else
echo -e " ${GREEN}[✓] 未发现可疑定时任务${NC}"
fi
echo -e " ${GREEN}[✓] ${td}/crontab.txt${NC}"
}
# ==================== 恶意文件检测 ====================
scan_malware() {
local u="$1" p="$2" h="$3" pt="$4" k="$5" a="$6" td="$7"
echo -e " ${CYAN}[恶意文件检测]${NC}"
echo -e " [1/3] 远程收集高风险文件..."
local FILE_LIST
FILE_LIST=$(run_remote "$u" "$p" "$h" "$pt" "$k" "$a" 60 << RCEOF
find /home /var/www /tmp /opt /usr/local/bin /usr/bin -maxdepth 4 -type f -perm -4000 2>/dev/null | head -${MAX_FILE_COUNT}
find /home /var/www /tmp /opt -maxdepth 4 -type f \( -name '*.sh' -o -name '*.py' -o -name '*.php' -o -name '*.jsp' -o -perm -111 \) -mtime -7 2>/dev/null | head -${MAX_FILE_COUNT}
RCEOF
)
if [ -z "$FILE_LIST" ]; then
echo -e " ${GREEN}[✓] 未发现高风险文件${NC}"
return
fi
local FILE_COUNT
FILE_COUNT=$(echo "$FILE_LIST" | sort -u | wc -l)
echo -e " 发现 ${FILE_COUNT} 个高风险文件"
echo "$FILE_LIST" | sort -u > "${td}/filelist.txt"
echo -e " [2/3] 远程文件哈希比对..."
run_remote "$u" "$p" "$h" "$pt" "$k" "$a" 120 << 'RCEOF' >> "${td}/filelist.txt"
while IFS= read -r f; do
[ -f "$f" ] || continue
SIZE=$(stat -c%s "$f" 2>/dev/null || echo 0)
[ "$SIZE" -gt 104857600 ] 2>/dev/null && continue
MD5=$(md5sum "$f" 2>/dev/null | awk '{print $1}')
SHA=$(sha256sum "$f" 2>/dev/null | awk '{print $1}')
echo "$MD5 | $SHA | $SIZE | $f"
done
RCEOF
local HASH_COUNT
HASH_COUNT=$(grep -c '|' "${td}/filelist.txt" 2>/dev/null)
HASH_COUNT=${HASH_COUNT:-0}
echo -e " 已收集 ${HASH_COUNT} 个文件哈希"
local KNOWN_MALICIOUS=(
"44d88612fea8a8f36de82e1278abb02f"
"d9040bfe5862414908f75754a45f6269"
"680bfb450a5b0589ad72f11e7263b100"
)
local SM=0
local mh
for mh in "${KNOWN_MALICIOUS[@]}"; do
if grep -q "$mh" "${td}/filelist.txt" 2>/dev/null; then
SM=$((SM + 1))
echo -e " ${RED}[!] 命中恶意哈希: ${mh}${NC}"
fi
done
[ "$SM" -eq 0 ] && echo -e " ${GREEN}[✓] 哈希比对通过${NC}"
if [ "$CLAMAV_LOCAL" -eq 1 ]; then
echo -e " [3/3] 拉取可疑文件到本地 ClamAV..."
local PULL_DIR="${td}/pulled-files"
mkdir -p "$PULL_DIR"
local PULL_LIST
PULL_LIST=$(run_remote "$u" "$p" "$h" "$pt" "$k" "$a" 60 << RCEOF
find /home /var/www /tmp /opt -maxdepth 4 -type f -perm -4000 2>/dev/null
find /home /var/www /tmp /opt -maxdepth 4 -type f \( -name '*.sh' -o -name '*.py' -o -name '*.php' -o -name '*.jsp' -o -name 'shell*' -o -name '*.phtml' \) -mtime -3 2>/dev/null
RCEOF
)
if [ -n "$PULL_LIST" ]; then
local PULL_COUNT
PULL_COUNT=$(echo "$PULL_LIST" | sort -u | wc -l)
echo -e " 拉取 ${PULL_COUNT} 个可疑文件..."
echo "$PULL_LIST" | sort -u | head -"${MAX_FILE_COUNT}" | while IFS= read -r rf; do
[ -z "$rf" ] && continue
local sp
sp=$(echo "$rf" | tr '/' '_')
remote_scp "$u" "$p" "$h" "$pt" "$k" "$a" "$rf" "${PULL_DIR}/${sp}"
done
echo -e " 本地 ClamAV 扫描中..."
local CLAM_RESULT
CLAM_RESULT=$(clamscan -r --infected "$PULL_DIR" 2>&1 | grep -E "Infected|Scanned|FOUND")
echo "$CLAM_RESULT" >> "${td}/filelist.txt"
local INFECTED
INFECTED=$(echo "$CLAM_RESULT" | grep "Infected files:" | awk '{print $NF}')
INFECTED=${INFECTED:-0}
if [ "$INFECTED" -gt 0 ] 2>/dev/null; then
echo -e " ${RED}[!] ClamAV 发现 ${INFECTED} 个感染文件!${NC}"
echo "$CLAM_RESULT" | grep "FOUND" | head -10 | while IFS= read -r ln; do
echo -e " ${RED}${ln}${NC}"
done
else
echo -e " ${GREEN}[✓] ClamAV 扫描通过${NC}"
fi
else
echo -e " [3/3] 无需拉取"
fi
else
echo -e " [3/3] ClamAV 不可用,跳过"
fi
}
# ==================== Web 指纹扫描 ====================
scan_web_fingerprint() {
local u="$1" p="$2" h="$3" pt="$4" k="$5" a="$6" td="$7"
echo -e " ${CYAN}[Web 指纹]${NC}"
local URL=""
local HTTP_CODE HTTPS_CODE
HTTP_CODE=$(curl -sk --max-time 5 -o /dev/null -w "%{http_code}" "http://${h}" 2>/dev/null)
HTTPS_CODE=$(curl -sk --max-time 5 -o /dev/null -w "%{http_code}" "https://${h}" 2>/dev/null)
if [ "$HTTPS_CODE" != "000" ] && [ -n "$HTTPS_CODE" ]; then
URL="https://${h}"
elif [ "$HTTP_CODE" != "000" ] && [ -n "$HTTP_CODE" ]; then
URL="http://${h}"
fi
if [ -z "$URL" ]; then
echo -e " ${YELLOW}[!] 未发现 Web 服务${NC}"
return
fi
local HEADERS SERVER_POWERED
HEADERS=$(curl -skI --max-time 10 "$URL" 2>/dev/null)
local SERVER_HEADER
SERVER_HEADER=$(echo "$HEADERS" | grep -i "^Server:" | head -1 | sed 's/^[Ss]erver: *//;s/\r//')
local POWERED_BY
POWERED_BY=$(echo "$HEADERS" | grep -i "^X-Powered-By:" | head -1 | sed 's/^[Xx]-[Pp]owered-[Bb]y: *//;s/\r//')
echo -e " URL: ${URL}"
[ -n "$SERVER_HEADER" ] && echo -e " Server: ${BOLD}${SERVER_HEADER}${NC}"
[ -n "$POWERED_BY" ] && echo -e " 动态: ${POWERED_BY}"
local CVE_MATCHED_LIST=""
local CVE_HITS=0
local cve_entry
local cve_sw cve_ver cve_id cve_sev cve_desc
local matched_ver
for cve_entry in "${CVE_DB[@]}"; do
IFS='|' read -r cve_sw cve_ver cve_id cve_sev cve_desc <<< "$cve_entry"
if echo "$CVE_MATCHED_LIST" | grep -qw "$cve_id"; then
continue
fi
matched_ver=""
if [[ "$cve_sw" == "nginx" ]] && [[ "$SERVER_HEADER" == *nginx* ]]; then
matched_ver=$(echo "$SERVER_HEADER" | grep -oP "nginx/\K[0-9.]+" | head -1)
elif [[ "$cve_sw" == "Apache" ]] && [[ "$SERVER_HEADER" == *Apache* ]]; then
matched_ver=$(echo "$SERVER_HEADER" | grep -oP "Apache/\K[0-9.]+" | head -1)
elif [[ "$cve_sw" == "Microsoft-IIS"* ]] && [[ "$SERVER_HEADER" == *IIS* ]]; then
matched_ver=$(echo "$SERVER_HEADER" | grep -oP "IIS/\K[0-9.]+" | head -1)
fi
if [ -n "$matched_ver" ]; then
version_compare "$matched_ver" "$cve_ver"
if [ $? -eq 2 ]; then
echo -e " ${RED}[!] ${cve_id} (${cve_sev}) — ${cve_desc}${NC}"
CVE_HITS=$((CVE_HITS + 1))
CVE_MATCHED_LIST="${CVE_MATCHED_LIST} ${cve_id}"
fi
fi
done
if [ "$CVE_HITS" -eq 0 ]; then
echo -e " ${GREEN}[✓] 未匹配到已知 CVE${NC}"
else
echo -e " ${BOLD} 共匹配 ${CVE_HITS} 个 CVE${NC}"
fi
local MISSING_HEADERS=0
local HEADER_NAME
for HEADER_NAME in "Strict-Transport-Security" "X-Content-Type-Options" "X-Frame-Options" "Content-Security-Policy"; do
echo "$HEADERS" | grep -qi "^${HEADER_NAME}:" || MISSING_HEADERS=$((MISSING_HEADERS + 1))
done
if [ "$MISSING_HEADERS" -gt 0 ]; then
echo -e " ${YELLOW}[!] 缺失 ${MISSING_HEADERS} 个安全响应头${NC}"
fi
local SSL_HOST
SSL_HOST=$(echo "$URL" | grep -oP '(?<=https://)[^:/]+')
if [ -n "$SSL_HOST" ]; then
local CERT_OUTPUT
CERT_OUTPUT=$(echo | timeout 5 openssl s_client -servername "$SSL_HOST" -connect "${SSL_HOST}:443" 2>/dev/null)
if echo "$CERT_OUTPUT" | grep -q "BEGIN CERTIFICATE"; then
local CERT_ISSUER CERT_SUBJECT
CERT_ISSUER=$(echo "$CERT_OUTPUT" | openssl x509 -noout -issuer 2>/dev/null | sed 's/issuer=//')
CERT_SUBJECT=$(echo "$CERT_OUTPUT" | openssl x509 -noout -subject 2>/dev/null | sed 's/subject=//')
if echo "$CERT_ISSUER" | grep -qi "$CERT_SUBJECT"; then
echo -e " ${RED}[!] SSL: 自签名${NC}"
else
echo -e " ${GREEN}[✓] SSL: 正常签发${NC}"
fi
fi
fi
{
echo "============ Web 指纹 ============="
echo "URL: ${URL}"
echo "Server: ${SERVER_HEADER:-未知}"
echo "X-Powered-By: ${POWERED_BY:-无}"
echo "CVE匹配: ${CVE_HITS}"
echo "缺失安全头: ${MISSING_HEADERS}"
echo ""
echo "--- 响应头 ---"
echo "$HEADERS"
} > "${td}/web.txt" 2>/dev/null
echo -e " ${GREEN}[✓] ${td}/web.txt${NC}"
}
# ============================================================
# 主扫描循环
# ============================================================
for ((IDX=0; IDX<TOTAL; IDX++)); do
TU="${T_USER[$IDX]}"
TP="${T_PASS[$IDX]}"
TH="${T_HOST[$IDX]}"
TPT="${T_PORT[$IDX]}"
TK="${T_KEY[$IDX]}"
TA="${T_AUTH[$IDX]}"
NUM=$((IDX + 1))
SAFE_NAME=$(echo "${TU}@${TH}_${TPT}" | tr '/:.' '__')
TARGET_DIR="${SCAN_DIR}/${SAFE_NAME}"
mkdir -p "$TARGET_DIR"
TARGET_START=$(date +%s)
echo ""
echo -e "${YELLOW}==================================================${NC}"
echo -e "${YELLOW} [${NUM}/${TOTAL}] ${TU}@${TH}:${TPT} [${TA}]${NC}"
echo -e "${YELLOW}==================================================${NC}"
echo -e " 测试 SSH 连接..."
TEST_RESULT=$(remote_exec "$TU" "$TP" "$TH" "$TPT" "$TK" "$TA" "echo ok" 10 | tail -1 | tr -d '\r')
if [ "$TEST_RESULT" != "ok" ]; then
echo -e " ${RED}[✗] SSH 连接失败,跳过${NC}"
FAIL=$((FAIL + 1))
continue
fi
echo -e " ${GREEN}[✓] SSH 连接成功${NC}"
SUCCESS=$((SUCCESS + 1))
if [ "$SKIP_SYSINFO" -eq 0 ]; then
collect_sysinfo "$TU" "$TP" "$TH" "$TPT" "$TK" "$TA" "$TARGET_DIR"
fi
check_ssh_security "$TU" "$TP" "$TH" "$TPT" "$TK" "$TA" "$TARGET_DIR"
if [ "$SKIP_NETWORK" -eq 0 ]; then
check_network_risk "$TU" "$TP" "$TH" "$TPT" "$TK" "$TA" "$TARGET_DIR"
fi
if [ "$SKIP_CRON" -eq 0 ]; then
check_crontab_risk "$TU" "$TP" "$TH" "$TPT" "$TK" "$TA" "$TARGET_DIR"
fi
if [ "$SKIP_WEB" -eq 0 ]; then
scan_web_fingerprint "$TU" "$TP" "$TH" "$TPT" "$TK" "$TA" "$TARGET_DIR"
fi
if [ "$SKIP_MALWARE" -eq 0 ]; then
scan_malware "$TU" "$TP" "$TH" "$TPT" "$TK" "$TA" "$TARGET_DIR"
fi
TARGET_END=$(date +%s)
TARGET_ELAPSED=$((TARGET_END - TARGET_START))
echo -e "\n ${GREEN}[*] ${TU}@${TH} 扫描完成 (${TARGET_ELAPSED}s)${NC}"
done
# ============================================================
# 汇总报告
# ============================================================
SCRIPT_END=$(date +%s)
SCRIPT_ELAPSED=$((SCRIPT_END - SCRIPT_START))
echo ""
echo -e "${YELLOW}==================================================${NC}"
echo -e "${YELLOW} 扫描汇总${NC}"
echo -e "${YELLOW}==================================================${NC}"
SUMMARY="${SCAN_DIR}/summary.txt"
{
echo "========================================"
echo " 安全扫描汇总报告 v4.2"
echo " 时间: $(date '+%Y-%m-%d %H:%M:%S')"
echo " 目标: ${TOTAL} 成功: ${SUCCESS} 失败: ${FAIL}"
echo " 总耗时: ${SCRIPT_ELAPSED}秒"
echo "========================================"
echo ""
} > "$SUMMARY"
for ((IDX=0; IDX<TOTAL; IDX++)); do
TU="${T_USER[$IDX]}"
TH="${T_HOST[$IDX]}"
TPT="${T_PORT[$IDX]}"
TA="${T_AUTH[$IDX]}"
SAFE_NAME=$(echo "${TU}@${TH}_${TPT}" | tr '/:.' '__')
TARGET_DIR="${SCAN_DIR}/${SAFE_NAME}"
echo "### [$((IDX+1))/${TOTAL}] ${TU}@${TH}:${TPT} [${TA}] ###" >> "$SUMMARY"
if [ -f "${TARGET_DIR}/network.txt" ]; then
local_DN=$(grep -cE "0\.0\.0\.0:(3306|6379|27017|11211|9200|5432)" "${TARGET_DIR}/network.txt" 2>/dev/null)
local_DN=${local_DN:-0}
local_FP=$(grep -c "Failed password" "${TARGET_DIR}/network.txt" 2>/dev/null)
local_FP=${local_FP:-0}
[ "$local_DN" -gt 0 ] && echo " [!] 危险端口: ${local_DN}" >> "$SUMMARY"
[ "$local_FP" -gt 5 ] && echo " [!] SSH暴力破解: ${local_FP}次" >> "$SUMMARY"
fi
if [ -f "${TARGET_DIR}/filelist.txt" ]; then
local_INF=$(grep "Infected files:" "${TARGET_DIR}/filelist.txt" 2>/dev/null | tail -1 | awk '{print $NF}')
local_INF=${local_INF:-0}
[ "$local_INF" -gt 0 ] 2>/dev/null && echo " [!] ClamAV感染: ${local_INF}" >> "$SUMMARY"
fi
if [ -f "${TARGET_DIR}/crontab.txt" ]; then
local_SUSP=$(grep -ciE "curl.*\||wget.*\||base64|eval|/dev/tcp" "${TARGET_DIR}/crontab.txt" 2>/dev/null)
local_SUSP=${local_SUSP:-0}
[ "$local_SUSP" -gt 0 ] 2>/dev/null && echo " [!] 可疑定时任务: ${local_SUSP}条" >> "$SUMMARY"
fi
if [ -f "${TARGET_DIR}/ssh-security.txt" ]; then
grep -q "PermitRootLogin.*yes" "${TARGET_DIR}/ssh-security.txt" 2>/dev/null && echo " [!] SSH: root密码登录" >> "$SUMMARY"
grep -q "command=" "${TARGET_DIR}/ssh-security.txt" 2>/dev/null && echo " [!] SSH: 可能后门" >> "$SUMMARY"
fi
if [ -f "${TARGET_DIR}/web.txt" ]; then
local_WC=$(grep "CVE匹配:" "${TARGET_DIR}/web.txt" 2>/dev/null | awk '{print $2}')
local_WC=${local_WC:-0}
[ "$local_WC" -gt 0 ] 2>/dev/null && echo " [!] Web CVE: ${local_WC}" >> "$SUMMARY"
fi
echo "" >> "$SUMMARY"
done
echo "========================================" >> "$SUMMARY"
echo " 报告: ${SCAN_DIR}" >> "$SUMMARY"
echo "========================================" >> "$SUMMARY"
echo ""
cat "$SUMMARY"
echo ""
echo -e "${GREEN}详细报告: ${SCAN_DIR}${NC}"
echo ""
六、工具总览
| 工具 | 版本 | 部署方式 | 用途 |
|---|---|---|---|
| Nikto | 2.6.0 | Git 克隆 | Web 服务器配置与安全头扫描 |
| afrog | 3.5.3 (PoC 1648 个) | 预编译二进制 | PoC 漏洞验证 |
| ClamAV | 0.103.11 (362 万特征) | yum 安装 | 病毒/恶意文件扫描 |
| security-scan.sh | v4.2 | 本地脚本 | 统一远程扫描(集成以上工具 + SSH 远程检查) |