网站安全扫描这件事,很多运维和站长习惯等出了问题再处理,但现在的攻击手段早就不是手工注入、挂黑链那种慢节奏了。批量漏洞利用、供应链投毒、API密钥泄露、Webshell后门植入,往往在几小时内就能完成。如果扫描还停留在每月一次甚至想起来才做,风险窗口会非常大。定时检测的核心价值不是“发现已经被利用的漏洞”,而是在攻击者大规模扫描之前,先把自己暴露的资产和弱点找出来。
自动化的安全扫描要真正落地,不能只装一个工具然后看报告。你需要把它拆成几个层次:资产发现、漏洞检测、内容篡改监控、可用性监测、证书与配置检查。每一层解决的问题不同,定时频率和告警策略也不一样。下面直接讲具体怎么搭建和配置一套可用的定时扫描体系。
先解决资产清单问题,扫描对象不能靠记忆很多站点风险来自“被遗忘的资产”。比如旧版测试站、临时活动页、某个子域名指向了已经下线的系统,或者后台地址没有从外网移除。定时扫描的第一步不是扫漏洞,而是持续发现和确认资产范围。可以用子域名枚举、证书透明日志、DNS记录变化监测来建立资产池。资产池要定期更新,建议每天跑一次轻量级的子域名发现任务,每周做一次全量端口和服务识别。
资产发现常用的组合是:用被动接口收集子域名,再用主动DNS解析验证存活,最后对存活主机做端口扫描和指纹识别。这个过程完全可以脚本化。下面是一段简化版的资产发现脚本逻辑,适合放在定时任务里执行。
#!/bin/bash
# 每日子域名发现与存活检测
DOMAIN="example.com"
DATE=$(date +%F)
OUTPUT_DIR="/var/scan/assets/$DATE"
mkdir -p "$OUTPUT_DIR"
# 被动收集子域名
subfinder -d "$DOMAIN" -silent > "$OUTPUT_DIR/subdomains_raw.txt"
# 主动枚举补充
amass enum -passive -d "$DOMAIN" -o "$OUTPUT_DIR/amass.txt" 2>/dev/null
cat "$OUTPUT_DIR/subdomains_raw.txt" "$OUTPUT_DIR/amass.txt" | sort -u > "$OUTPUT_DIR/subdomains.txt"
# 存活检测
httpx -l "$OUTPUT_DIR/subdomains.txt" -silent -title -status-code -tech-detect -o "$OUTPUT_DIR/live_hosts.txt"
# 与昨日资产对比,输出新增和消失的域名
if [ -f /var/scan/assets/previous_subdomains.txt ]; then
comm -23 "$OUTPUT_DIR/subdomains.txt" /var/scan/assets/previous_subdomains.txt > "$OUTPUT_DIR/new_subdomains.txt"
comm -13 "$OUTPUT_DIR/subdomains.txt" /var/scan/assets/previous_subdomains.txt > "$OUTPUT_DIR/removed_subdomains.txt"
fi
cp "$OUTPUT_DIR/subdomains.txt" /var/scan/assets/previous_subdomains.txt
新增的子域名要重点检查,因为攻击者也经常盯着新出现的资产。很多入侵事件里,攻击者比运维更早发现新上线的测试系统。资产变化本身就应该触发告警,而不是等到漏洞扫描结果出来才关注。
漏洞检测要分层,不能只依赖单一扫描器自动化漏洞扫描最容易犯的错是把一个通用扫描器当成全部。通用扫描器对已知CVE、常见配置错误有效,但对业务逻辑漏洞、API未授权访问、越权问题基本无能为力。所以定时扫描应该分成三层:基础设施层、Web应用层、业务逻辑层。
基础设施层每周扫一次即可,重点是开放端口、服务版本、已知CVE、弱口令。Nuclei配合自定义模板可以覆盖大部分场景。Web应用层建议每天扫一次,针对所有存活的HTTP服务做指纹识别后的定向检测,比如检测到WordPress就自动加载WordPress相关模板,检测到Spring Boot就检查Actuator端点暴露。业务逻辑层则需要结合接口清单做半自动检测,比如定期重放关键API请求,检查是否出现未授权返回。
一个实用的做法是把Nuclei的定时任务和资产发现结果联动。每天资产发现完成后,自动对新发现的域名跑一遍完整模板,对已知域名跑增量模板。这样既能控制扫描时间,又能保证新资产第一时间被检测。
# 对新发现子域名执行完整漏洞扫描
if [ -s /var/scan/assets/$DATE/new_subdomains.txt ]; then
nuclei -l /var/scan/assets/$DATE/new_subdomains.txt \
-t /opt/nuclei-templates/ \
-severity critical,high,medium \
-o /var/scan/vuln/new_assets_$DATE.txt \
-silent
fi
# 对所有存活站点执行每日增量扫描
nuclei -l /var/scan/assets/$DATE/live_hosts.txt \
-t /opt/nuclei-templates/exposures/ \
-t /opt/nuclei-templates/misconfiguration/ \
-t /opt/nuclei-templates/default-logins/ \
-severity critical,high \
-o /var/scan/vuln/daily_$DATE.txt \
-silent
扫描结果不能只看漏洞数量,要区分“理论风险”和“可利用风险”。很多扫描器报出来的中低危问题在实际环境中无法被利用,如果每次都全量推送告警,很快就会出现告警疲劳。建议对扫描结果做二次验证,比如对报告的高危漏洞用curl或专用PoC脚本确认,只有验证通过的才进入紧急告警流程。
内容篡改监控比想象中重要漏洞扫描能发现弱点,但无法告诉你网站是否已经被篡改。很多黑链植入、SEO劫持、挖矿脚本注入,在页面视觉上几乎看不出来,但会严重影响站点信誉和搜索表现。定时检测站点风险必须包含内容完整性监控。
具体做法是对核心页面建立基线哈希值,定时抓取页面内容计算哈希,与基线对比。如果发现变化,再进一步分析变化内容。对于动态页面,可以只监控页面中的静态部分,比如页脚、头部导航、特定的JS文件。对于静态资源,直接监控文件的哈希值变化。频率建议每6小时一次,关键页面可以缩短到每小时一次。
#!/bin/bash
# 页面篡改检测
URLS=("https://www.example.com/" "https://www.example.com/about" "https://www.example.com/js/main.js")
BASELINE_DIR="/var/scan/baseline"
ALERT_LOG="/var/scan/alerts/tamper_$(date +%F).log"
for url in "${URLS[@]}"; do
filename=$(echo "$url" | md5sum | awk '{print $1}')
current_hash=$(curl -s "$url" | md5sum | awk '{print $1}')
if [ -f "$BASELINE_DIR/$filename.hash" ]; then
baseline_hash=$(cat "$BASELINE_DIR/$filename.hash")
if [ "$current_hash" != "$baseline_hash" ]; then
echo "[ALERT] Content changed: $url" >> "$ALERT_LOG"
# 抓取当前内容用于对比分析
curl -s "$url" > "/var/scan/alerts/content_${filename}_$(date +%H%M).html"
fi
else
# 首次运行建立基线
echo "$current_hash" > "$BASELINE_DIR/$filename.hash"
fi
done
除了页面哈希,还应该监控网站外链的异常变化。很多黑链攻击会在页面中插入隐藏链接,指向赌博、色情或诈骗站点。可以定时提取页面中所有外链域名,与白名单对比,发现陌生域名立即告警。这个检测对SEO尤其关键,因为搜索引擎对站点被黑后的外链污染非常敏感。
证书和配置检查要纳入定时任务证书过期导致站点无法访问,严格来说不算安全攻击,但属于可用性风险,而且经常被忽略。自动化扫描体系里应该包含证书到期时间检查,提前30天、15天、7天分别告警。同时检查证书链完整性、是否使用了弱加密套件、是否支持过时的TLS版本。
安全响应头检查同样重要。CSP、X-Frame-Options、X-Content-Type-Options、Strict-Transport-Security这些响应头配置缺失,不会直接导致入侵,但会显著增加攻击面。比如缺少CSP会让XSS攻击更容易执行,缺少HSTS会让中间人攻击有机可乘。这些检查可以用简单的脚本实现,每天跑一次即可。
#!/bin/bash
# 安全响应头检查
DOMAIN="example.com"
DATE=$(date +%F)
RESULT_FILE="/var/scan/headers/headers_$DATE.txt"
check_header() {
local url=$1
local header=$2
local value=$(curl -sI "https://$url" | grep -i "^$header:" | head -1)
if [ -z "$value" ]; then
echo "[MISSING] $url - $header" >> "$RESULT_FILE"
fi
}
for sub in www api admin; do
host="$sub.$DOMAIN"
check_header "$host" "Content-Security-Policy"
check_header "$host" "X-Frame-Options"
check_header "$host" "X-Content-Type-Options"
check_header "$host" "Strict-Transport-Security"
check_header "$host" "Referrer-Policy"
done
配置检查还应该包括目录列表是否开启、备份文件是否可访问、Git或SVN元数据是否暴露、常见后台路径是否可访问。这些问题通用扫描器有时会漏掉,但攻击者的字典里一定有这些路径。定时用自定义字典做轻量级路径探测,可以在攻击者之前发现这些低级但致命的暴露。
定时策略和告警分级决定体系是否有效扫描频率不是越高越好。频率过高会消耗服务器资源,也可能触发WAF或CDN的防护机制导致IP被封。合理的策略是:资产发现每天一次,漏洞扫描新资产即时触发、全量资产每周一次,内容篡改监控每6小时一次,证书检查每天一次,可用性监控每5分钟一次。关键业务接口的可用性和响应时间监控应该独立出来,使用更短的间隔。
告警必须分级。紧急告警包括:新发现的严重漏洞、内容被篡改、证书即将过期、核心接口不可用。这些告警应该通过即时通讯工具或短信推送。普通告警包括:中低危漏洞、安全头缺失、非核心资产变化,这些可以汇总成日报发送。不要把所有扫描结果都推送到聊天群,那样只会让真正重要的告警被淹没。
告警内容要包含足够的信息用于快速响应。至少要有:受影响资产、问题描述、发现时间、验证方式、建议修复方案。如果告警里只写“发现3个高危漏洞”,接收者还需要去翻报告,响应速度会大打折扣。自动化体系的价值在于缩短从发现到处置的时间,告警信息不完整等于把时间又浪费回去。
扫描日志和报告要留存,至少保留90天。一方面用于趋势分析,比如某个子域名反复出现同类问题,说明修复没有到位。另一方面在安全事件调查时,历史扫描数据可以帮助确定漏洞存在的时间窗口。报告格式建议同时保留机器可读的JSON和人工可读的HTML摘要,方便不同角色使用。
最后要强调的是,自动化扫描不能替代人工渗透测试。定时检测解决的是持续监控和快速发现的问题,但深层次的业务逻辑漏洞、权限绕过、供应链风险,仍然需要定期的人工深度测试。把自动化扫描作为日常防线,把人工渗透作为阶段性深度体检,两者配合才能把站点风险控制在可接受范围内。
