首页 / 帮助文档 / 网站运营CDN日志与源站访问日志对比发现隐蔽攻击行为

网站运营CDN日志与源站访问日志对比发现隐蔽攻击行为

服务器被上传了webshell,后台多了几个不明管理员账户,但WAF和IPS日志里干干净净,这种情况十有八九是攻击流量绕过了前端CDN节点,直接打到了源站。CDN日志和源站访问日志,虽然记录的是同一个域名的访问行为,但它们看到的东西可能完全不同。把两份日志拉出来做对比,往往能发现那些被CDN过滤规则、速率限制和IP信誉库掩盖掉的隐蔽攻击。

为什么两份日志会不一致

CDN的工作原理决定了它充当的是反向代理角色。用户请求先到CDN边缘节点,节点根据缓存策略决定是直接返回内容还是回源拉取。这意味着源站看到的请求IP永远是CDN的回源IP段,而不是访客的真实IP。虽然CDN通常会通过X-Forwarded-For或自定义头字段传递真实IP,但攻击者恰恰利用了这个架构差异。他们通过全网扫描、历史DNS解析记录、证书透明度日志或者第三方服务,拿到源站的真实IP地址,然后直接向源站发起攻击,完全绕过CDN的安全防护层。这时候CDN日志里不会有任何记录,而源站日志里会出现大量异常请求。

直接对比能发现的三类典型隐蔽攻击

第一类是源站IP泄露后的直接CC攻击和漏洞扫描。攻击者拿到源站IP后,会直接对源站IP发起HTTP Flood或者慢速攻击。这些请求不会经过CDN,所以CDN日志里看不到任何波动,但源站日志会显示某个或某几个IP在短时间内发起大量请求。更隐蔽的做法是,攻击者会用大量代理IP对源站进行低频慢速扫描,探测备份文件、API接口或者管理后台。因为频率低,源站的单IP限流策略可能不会触发,但把源站日志和CDN日志的请求量按小时做差值对比,就会发现源站多出来的那部分流量。

第二类是伪造X-Forwarded-For头绕过CDN的IP访问控制。有些CDN配置不当,会直接信任客户端传递的X-Forwarded-For头。攻击者在请求中构造一个白名单IP,比如127.0.0.1或者内网地址,CDN节点把这个伪造的IP传给源站,源站应用程序如果只取X-Forwarded-For的第一个IP做鉴权,就会认为请求来自可信来源。在源站日志里,X-Forwarded-For字段显示的是正常IP,但把CDN日志里的客户端真实IP和源站日志里记录的X-Forwarded-For做比对,就会发现不一致。正常情况下,CDN添加的回源请求头里,X-Forwarded-For的最后一个IP应该是CDN看到的客户端IP,如果这个对应关系对不上,说明有人在中间篡改或者请求根本没走CDN。

第三类是WebSocket隧道和协议走私。攻击者在已经建立的WebSocket连接上传输恶意载荷,或者利用HTTP请求走私在CDN和源站之间制造解析差异。这类攻击在CDN日志里显示为正常的101切换协议或者200状态码,但源站日志如果记录了完整的请求体长度或者更详细的上游处理时间,就会发现某个连接的持续时长异常,或者请求体大小和CDN日志记录的不一致。把两份日志的请求体大小字段、连接时长字段拿出来做差值分析,异常值往往就是正在进行的隧道通信。

日志对比分析的具体操作步骤

第一步是统一日志格式和时区。CDN服务商提供的日志字段和源站Nginx或Apache的日志字段不完全一致,需要先把两边日志解析成统一的格式。至少需要对齐的字段包括:时间戳、请求方法、请求URI、状态码、客户端IP、X-Forwarded-For、User-Agent、请求体大小、响应时间。时间戳必须统一到秒级,时区统一成UTC,否则对比结果毫无意义。可以用awk或者Python脚本做预处理,把CDN日志里的时间戳从东八区转成UTC,同时把源站日志里的本地时间也转成UTC。

第二步是提取回源IP段做流量分离。从CDN服务商处获取完整的回源IP段列表,在源站日志里把所有来自这些IP段的请求标记为“CDN回源流量”,其余请求标记为“非CDN直连流量”。非CDN直连流量就是需要重点关注的对象。有些攻击者会伪造CDN的回源IP,但通常无法完成TCP三次握手,除非他们控制了CDN回源IP段内的机器。所以源站日志里出现的非回源IP段请求,要么是合法的内部监控探测,要么就是攻击者绕过了CDN。

第三步是做请求量差值分析。把CDN日志和源站日志按分钟粒度聚合,统计每分钟的请求总数。正常情况下,源站的请求量应该略小于或等于CDN日志的请求量,因为CDN会缓存住一部分请求不回源。如果源站某分钟的请求量大于CDN日志的请求量,说明有流量没经过CDN直接打到了源站。这种差值持续存在或者呈周期性波动,基本可以确认源站IP已经泄露。

第四步是做URI和参数维度的交叉比对。攻击者在绕过CDN直接攻击源站时,往往会尝试一些被CDN WAF规则拦截的敏感路径,比如/.env、/wp-config.php、/actuator、/druid/index.html这类文件。在CDN日志里,这些请求可能被WAF拦截返回403,或者被速率限制直接丢弃,根本不会出现在回源请求里。但源站日志如果记录了这些URI的访问,说明有人直接向源站发起了探测。把CDN日志里状态码为403或444的URI列表导出来,和源站日志的URI做交集,匹配上的就是漏网之鱼。

第五步是分析异常User-Agent和请求头组合。自动化扫描工具通常使用固定的User-Agent特征,或者空User-Agent。在CDN日志里,这些请求可能被Bot Management识别并拦截,源站日志里不会出现。但如果源站日志里出现了大量带有扫描器User-Agent的请求,而CDN日志里没有对应记录,说明扫描器绕过了CDN。更隐蔽的攻击者会伪造正常浏览器的User-Agent,但请求头的排列顺序、Accept-Language的值、或者缺少某些浏览器必带的头字段,这些细微差异在CDN日志里可能被忽略,但在源站日志里可以抓出来做异常检测。

用脚本自动化对比两份日志

下面这段Python脚本可以快速对比CDN日志和源站日志的请求量差异,找出源站多出来的请求。假设CDN日志和源站日志都已经预处理成统一格式,每行是一个JSON对象。

import json
from collections import defaultdict
from datetime import datetime

def load_logs(filepath, time_field, ip_field=None, cdn_ips=None):
    """加载日志并按分钟聚合,可选过滤CDN回源IP"""
    minute_counts = defaultdict(int)
    with open(filepath, 'r') as f:
        for line in f:
            try:
                log = json.loads(line.strip())
                ts = datetime.strptime(log[time_field], '%Y-%m-%dT%H:%M:%S')
                minute_key = ts.strftime('%Y-%m-%dT%H:%M')
                if cdn_ips and ip_field:
                    if log[ip_field] in cdn_ips:
                        minute_counts[minute_key] += 1
                else:
                    minute_counts[minute_key] += 1
            except:
                continue
    return minute_counts

# 加载CDN回源IP段
cdn_ips = set()
with open('cdn_ip_ranges.txt', 'r') as f:
    for line in f:
        cdn_ips.add(line.strip())

# 聚合CDN日志和源站日志
cdn_counts = load_logs('cdn_logs.json', 'timestamp')
origin_counts = load_logs('origin_logs.json', 'timestamp', 'remote_addr', cdn_ips)

# 找出源站比CDN多的分钟
anomalies = []
for minute, count in origin_counts.items():
    cdn_count = cdn_counts.get(minute, 0)
    if count > cdn_count * 1.2:  # 源站比CDN多20%以上
        anomalies.append((minute, cdn_count, count, count - cdn_count))

anomalies.sort(key=lambda x: x[3], reverse=True)
for minute, cdn_c, origin_c, diff in anomalies[:20]:
    print(f"时间: {minute}, CDN请求: {cdn_c}, 源站请求: {origin_c}, 差值: {diff}")

这个脚本的核心逻辑是:把源站日志里来自CDN回源IP段的请求单独统计,和CDN日志的请求量做分钟级对比。如果源站某分钟的请求量比CDN日志多出20%以上,就标记为异常。阈值可以根据业务实际情况调整,重点在于持续性的差值,而不是偶发的波动。

源站IP泄露后的加固措施

发现源站IP泄露后,单纯换IP只是暂时的解决方案。攻击者可以通过同样的手段再次获取新IP。更有效的做法是在源站前端做多层防护。第一层是在源站Nginx上配置默认虚拟主机,只响应明确配置的域名,对于直接通过IP访问的请求一律返回444断开连接。第二层是配置严格的iptables规则,只允许CDN回源IP段的80和443端口访问,其他所有来源IP一律DROP。第三层是在源站也部署一套轻量级的WAF规则,比如ModSecurity的OWASP核心规则集,作为CDN WAF被绕过后的最后一道防线。第四层是使用CDN服务商提供的源站保护功能,比如回源认证令牌,源站验证请求中携带的动态令牌,没有合法令牌的请求直接拒绝。

长期监控和告警机制

日志对比分析不应该是一次性的应急操作,而应该固化成日常的安全监控流程。可以搭建一个定时任务,每小时拉取CDN日志和源站日志,自动执行对比脚本,当源站非CDN流量占比超过阈值时触发告警。告警渠道可以对接钉钉、企业微信或者飞书的Webhook。监控指标除了请求量差值,还应该包括:源站日志里非CDN回源IP的独立IP数量变化趋势、源站日志里出现的新URI路径、源站日志里响应状态码为4xx和5xx的比例突增。这些指标组合起来,可以在攻击者完成信息收集阶段就发出预警,而不是等到webshell已经落地才发现。

另外需要注意日志本身的完整性和防篡改。攻击者拿到源站权限后,往往会第一时间清理日志。所以源站日志应该实时同步到远端日志服务,比如通过rsyslog或者Filebeat发送到独立的日志服务器,或者在本地写入的同时输出一份到标准输出被容器编排平台的日志采集系统收集。日志的存储周期至少保留90天,以便在发现异常后能够回溯攻击者的完整活动时间线。

把CDN日志和源站日志放在一起对比分析,本质上是利用两个独立数据源之间的信息差来发现盲区。攻击者可以绕过CDN,可以伪造请求头,可以模拟正常流量,但很难同时控制两个独立日志系统里的记录保持一致。这种交叉验证的思路,同样适用于其他多层架构的安全分析,比如反向代理和后端应用服务器的日志对比、API网关和微服务容器的日志对比。安全检测的突破点,往往就在这些看似冗余的数据重叠区域里。