本帖最后由 小德 于 2026-9-10 19:57 编辑
无任何可信代理校验 → 直连源站任意伪造
只要站点 cdn=true,任何人只要能访问到源站 IP(源站暴露、IP 扫描、DNS 历史记录),即可完全控制 ngx.ctx.ip:
- GET / HTTP/1.1
- Host: victim.com
- CF-Connecting-IP: 1.2.3.4
复制代码
影响面(全部基于 ngx.ctx.ip):
IP 白名单绕过:waf.lua:140-143 命中后 return true,在 UA/URL/CC/POST/GET 全部检测之前直接放行。
CC 防护完全失效:cc.lua:83/115/168/233/307/309 以 ngx.ctx.ip 为计数键,每换一个头就是全新计数器。
地域/城市/IDC 封禁失效:drop_abroad、drop_china、reg_city、idc 全部读 ngx.ctx.iplong / ngx.ctx.country。
封禁第三方 IP(放大攻击):IpInfo.lan_ip() → ipinfo.bt_ip_filter(ip, 86400)(ipinfo.lua:160-195)会把伪造的 IP 写进 /dev/shm/.bt_ip_filter,即系统防火墙拉黑列表。攻击者只需故意触发一条规则,即可让任意第三方 IP 被服务器防火墙封禁最长 24 小时。
日志与统计污染:database.totla_log_insert、insert_ip_count、UV/UIP 快照、攻击溯源全部失真;Public.send_spider()(ipinfo.lua:597)还会把伪造 IP 当作蜘蛛候选上报。
remote-host 逻辑让伪造在"CDN 链路"下依然成立
ipinfo.lua:218-223:
- if request_header['remote-host'] and request_header['remote-host']~=nil then
- if request_header['remote-host']==client_ip then
- client_ip=tmpe[1]
- client_ip=ngx.re.gsub(client_ip," ","","jo")
- end
- end
复制代码
remote-host 不是任何 CDN 的标准头,完全由客户端控制并会被 CDN 透传。默认分支取 XFF 最后一个值(真实 IP),但攻击者只要同时把 remote-host 填成自己的真实 IP,就会被强制改判为 XFF 的第一个值:
- X-Forwarded-For: 8.8.8.8 ← 攻击者伪造
- remote-host: 203.0.113.9 ← 攻击者自己的真实 IP
复制代码
Cloudflare 追加后 → X-Forwarded-For: 8.8.8.8, 203.0.113.9 → 取最后一个 = 203.0.113.9 → remote-host == client_ip 成立 → 改取 tmpe[1] = 8.8.8.8
即:即使站点正确接入 Cloudflare、流量没有绕过 CDN,IP 依然可伪造。 这段代码等价于给了攻击者一个"取首/取尾"的开关。
cdn_baidu 模式取 XFF 首值 → 必然可伪造
ipinfo.lua:211-213。开启"百度 CDN"后直接取 tmpe[1],即客户端最先写入的值,任何场景下都可伪造。
is_localhost 覆盖不全,is_internal_ip 根本没被调用
public.luaL526-L544
- function public.is_localhost(ip)
- if not ip then return false end
- if ip == "unknown" then return false end
- if public.find_str(ip, ":") then return false end -- IPv6 直接放行
- local ip_nmber = public.ip2long(ip)
- if ip_nmber == 0 then return false end
- if ip_nmber >= 2130706433 and ip_nmber <= 2130706687 then -- 仅 127.0.0.1 ~ 127.0.0.255
- return true
- end
- return false
- end
复制代码
回环段是 127.0.0.0/8,这里只覆盖 127.0.0.1–127.0.0.255 → 127.1.1.1、127.0.1.1 均可伪造成功。
含 : 直接 return false → ::1、0:0:0:0:0:0:0:1 等 IPv6 回环地址全部绕过检查。
私网段 10/8、172.16/12、192.168/16 完全未过滤:项目里已有 public.is_internal_ip()(public.lua:550-577),但在 get_client_ip_bylog 中从未调用(全仓库仅 database.lua:294 用了一次)。很多站点把内网 IP 或服务器 IP 加进白名单 → 直接全站 WAF 失效。
同名头只取第一条
ipinfo.lua:207-208:type(header_tmp)=="table" 时取 header_tmp[1]。攻击者先发的那条头会被优先采用,CDN 后续追加/单独设置的头被忽略。正确做法应取最后一条,或按可信跳数从右往左回溯。
cdn=true 但 cdn_header 缺失 → 整个 WAF fail-open
ipinfo.lua:204 的 ipairs(Site_config[server_name]['cdn_header']) 在 cdn_header 为 nil 时抛错。而 waf.lua:224-233 用 pcall 包住了整个 btwaf_run:
waf.luaL224-L233
- local ok,error = pcall(function()
- return btwaf_run()
- end)
- if not ok then
- if not ngx.shared.spider:get("btwaf_access") then
- Public.logs(error)
- ngx.shared.spider:set("btwaf_access",1,180)
- end
- end
复制代码
IpInfo.get_client_ip_bylog() 在第 63 行,异常后 CC、URL、GET、POST、Cookie、UA、扫描器……全部跳过,请求正常放行,且错误信息 3 分钟才记一次日志。这比伪造 IP 更严重。
Public.get_client_ip() 未定义 → 自定义规则整条链路 fail-open
modules/customize.lua:938:
customize.luaL938-L940
- local ip = Public.get_client_ip()
- local cache_key ="customize_cc__"..v..ip
- local safe_count = ngx.shared.drop_sum:incr(cache_key, 1)
复制代码
全仓库只有这 5 处 get_client_ip 匹配,没有任何定义(public.lua 里不存在 public.get_client_ip,也没有 __index 元表兜底)。命中"自定义 CC 防御"规则时必然 attempt to call a nil value,同样被 waf.lua 的 pcall 吞掉 → 后续所有检测(args/post/URL 规则)被跳过。顺带一提,此处本应直接用 ngx.ctx.ip。
|
|