日志里 24 个 IP 占了 40.8% 的访问量,直到我发现它们是我自己 CDN 的回源节点

0 阅读8分钟

变量探索(SEQVEC)

上一篇《蜜罐里的攻击者画像》里,我把「先确认这个 IP 是不是真的」放在了最前面,标成第 0 步 —— 因为它是所有分析的前提:前提错了,后面算得再认真都是白算。

那一步只写了一小段,这一篇把它展开:异常是怎么冒出来的、根因在哪、怎么修、修完还有什么没堵上。

先说结论:数据没丢,也不用重采。真实 IP 一直都在日志里,是我读错了字段。

下面所有数字都是我自己机器上的实测。IP 都打了码(保留前两段), 一是没必要把完整地址挂出去,二是那个头名我更不能写全 —— 原因写在第三节。


一、异常:一个 IP,怎么可能有 14 种身份

给蜜罐日志做 IP 画像的时候,出现了一个说不通的现象:

43.175.███.███   34 次   打过 8 个不同子域名   出现过 9 种不同 UA
43.175.███.███   29 次   打过 5 个不同子域名   出现过 14 种不同 UA

一个攻击者只有一种工具。

一个 IP 上同时出现 curl、企业级攻击面扫描、泄露情报采集、还有伪装成 Chrome 的脚本 —— 那是好几家公司在扫,不是一个"人"。

而且它还在打我不同的域名。

这只可能是一种东西:共享中间节点。 顺着查下去,43.174 / 43.175 这两个段里的 24 个 IP,在我抽出的 500 条样本里占了 204 条 —— 40.8%。

它们属于同一批机房、同一个 AS。也就是说:如果我拿这批 IP 去做"攻击者画像",我画出的是机房的地图,不是攻击者的画像。

留一条判断手法

以后再看到"日志里全是同一个机房的 IP",用这一条就够了:

看一个 IP 打了几种 UA、几个 host。

真实来源    1 种 UA,1–2 个站
中间节点    多种 UA,多个站

一个 IP 横跨多个站、带着十几种 UA —— 它不是访客,它是替访客接电话的那个人。


二、根因:不是"没传真实 IP",是"传了但源站没读"

原理其实很简单,两句话:

访客 → CDN 节点 → 回源 → 我的源站
                  ↑
                  源站看到的 TCP 对端地址,是 CDN 的回源节点

HTTP 是建立在 TCP 上的,而真实客户端 IP 在 TCP 层已经丢了 —— 因为它不是直接跟我连的。

真实 IP 只能靠 HTTP 头部"带话"过来。

所以我这边受影响的不只是蜜罐日志:

nginx 的 access_log        → 假
宝塔的"来源统计"           → 假
那个登录限制插件的告警邮件  → 假
我自己的分析脚本           → 假

全都在看 $remote_addr,而 $remote_addr 是回源节点的地址。

但真实 IP 其实一直都在

9 月 24 号上 CDN 的时候,我改过蜜罐的日志格式,顺手把 xff 也记下来了:

{"ip":"43.175.███.███", "ra":"43.175.███.███", "xff":"160.176.███.███", ...}
   ↑ 回源节点(当时以为是真的)                    ↑ 真实访客,一直都在

也就是说:数据没丢,是我的分析脚本读的是 ip 字段,没读 xff。

这件事值得单独说一句 —— 出了这种问题,第一反应通常是"数据得重采"。但大多数时候数据就在那儿,只是取数的那一行代码看错了地方。

边界要说清楚:X-Forwarded-For 是标准头,任何人都能伪造。 所以它适合用来做事后分析,不能拿来当访问控制的依据。


三、修法:CDN 侧 + 源站,两边都要动

上一篇我给的是通用两行:

set_real_ip_from <CDN 回源 IP 段>;
real_ip_header X-Forwarded-For;

真去落地的时候撞上一个前提问题:我用的 EdgeOne 免费版,控制台里拿不到回源 IP 段。

set_real_ip_from 的作用是"只信任这些来源传过来的头"。拿不到段,这个值就只能写得很宽 —— 而这正是安全上最不该做的事。

所以最后的方案是换个方向:不管来源,改管头名。

第 1 步:EdgeOne 侧,用一个自定义头名把真实 IP 带过来

站点加速 → 规则里加一个自定义请求头,值是客户端真实 IP,随回源请求一起带到源站。

关键点是头名不用 X-Forwarded-For:

X-Forwarded-For          所有扫描器、所有攻击工具都会尝试伪造它
X-Hx-Rc-<一串随机字符>-Ip  只有知道这个名字的人才构造得出来

这个头名我不能写在文章里。 它现在就是这个方案唯一的防线 —— 写出来,方案当场作废。

第 2 步:源站 nginx,信任这个头

set_real_ip_from 0.0.0.0/0;
set_real_ip_from ::/0;
real_ip_header X-Hx-Rc-<同上>;

配完 nginx -t && nginx -s reload,$remote_addr 就变成真实客户端 IP 了。

好处是下游一行都不用改:日志、上报脚本、统计报表、工作台,全部自动变对。

两个必须知道的细节

① set_real_ip_from 0.0.0.0/0 是妥协,不是最佳实践。

它的意思是"所有来源传的头我都信"。正常做法是只信任 CDN 回源段 —— 免费版给不了段,我只能用"头名保密"来兜这个底。这个边界第六节单独讲。

② 已经有一处 map 不能删。

我原来的配置里有个 map $http_x_hx_rc_..._ip $hx_client_ip,被十几个站的反代配置引用着(proxy_set_header X-Real-IP $hx_client_ip)。

删了它,nginx -t 直接报 unknown "hx_client_ip" variable。

这条顺带说明一件事:应用层一直拿到的是真实 IP,只有 nginx 自己的日志是错的。 所以问题比我一开始以为的轻。

顺手加的一个字段

日志格式里加了 ra:

"ra":"$realip_remote_addr"    ← 保留回源节点地址

改完之后 ip 是真实访客、ra 是回源节点。留 ra 是为了以后排查"这次请求到底走没走 CDN"。


四、怎么知道改对了:打一枪就看出来

不用等第二天,从本机打一次就行。我打的是自己站上的一个诱饵路径:

curl -A "codex-realip-verify/1.0" https://<我的域名>/.env

日志里出来这一行:

{"ip":"123.156.███.███", "ra":"114.66.███.███", "ua":"codex-realip-verify/1.0", "host":"<我的域名>"}
   ↑ 真实客户端              ↑ 回源节点

ip 和 ra 是两个不同的值,就说明改造生效了。改造前这两个位置应该是同一个地址。

再补一步:从不同网络各打一次(手机热点、家里、公司),看日志里出现的 IP 跟你当时的出口 IP 对不对得上。

我改完顺手做了五个入口的健康检查(www / studio / linglu-b / lic / ops),都正常。


五、改完之后,立刻冒出来的两个新问题

① "最活跃的攻击者"变成了我自己。

上面那次验证请求,EdgeOne 看到的客户端 IP 是我的出口 IP —— 它在真实 IP 榜上直接排到了第一名(二十多次)。

所以在做任何"谁在攻击我"的统计之前,剔除名单得从两个扩到三个:

127.0.0.1            本机自测
49.233.███.███       服务器自己的公网 IP(监控在访问自己)
123.156.███.███      我自己的出口 IP(浏览、手动测试)

漏掉任何一个,你就会把自己的流量当成攻击者的。

② 回源节点的 IP 没有消失,只是换了地方。

它们在 ra 字段里躺着 —— 这不是残留,这是证据。以后怀疑"这个 IP 是不是中间节点",翻 ra 比猜快得多。


六、这个方案没堵上的地方(必须说清楚)

头名保密,挡得住随手扫,挡不住盯着你打的人。

如果有人知道了我源站的 IP,又知道了这个头名,他直连源站、自己塞一个假 IP,源站照样会信。

彻底闭合要两步,我目前还没做到:

① 拿到 EdgeOne 回源 IP 段
   → 把 set_real_ip_from 从 0.0.0.0/0 换成真实段
② 在腾讯云安全组里,把 80 / 443 的入站来源限制为回源段
   → 绕开 CDN 的直连请求在 TCP 层就被丢掉,伪造头也就没意义了

这一段我写出来不是谦虚 —— 是提醒:看到"改两行就解决"的方案,先问一句它信任了谁。


七、回滚

动过的两个文件都留了 .bak-20261006 备份:

cp -a /www/server/nginx/conf/edgeone-clientip.conf.bak-20261006 /www/server/nginx/conf/edgeone-clientip.conf
cp -a /www/server/nginx/conf/honeypot-http.conf.bak-20261006  /www/server/nginx/conf/honeypot-http.conf
nginx -t && nginx -s reload

服务器上改配置,先想好怎么退回去,再动手。这条比配置本身重要。


八、如果你也遇到,五步照做

1  确认 IP 是不是真的
   一个 IP 多种 UA、跨好几个站 → 中间节点,不是访客

2  翻日志,找有没有转发真实 IP 的字段
   常见的是 X-Forwarded-For 或 CDN 自己的头
   (很多平台默认就带着,只是你没读)

3  源站配 real_ip_header(拿不到回源段就用自定义头名 + 保密)

4  打一枪验证
   真实 IP 和回源节点应该出现在两个不同的字段里

5  把"自己"从统计里剔掉
   本机、服务器自己、自己的出口 IP —— 三个都要

最后

这件事真正让我后背发凉的地方,不是被扫描 —— 是我拿着一批错的 IP,做了一套像模像样的分析,还差点写进文章里。

数据错的时候,分析越认真越离谱,因为你根本不会怀疑它。

所以我现在的习惯改了一条:任何一个数字,先问它是从哪个字段来的。


关于作者

变量探索(SEQVEC)