网站挂在代理后面,登录锁定和黑名单为什么会认错来源地址
网站挂在反向代理后面时,登录锁定和 IP 黑名单为什么会认错来源地址,根子在于两层地址被当成了一层。直连的时候,程序从连接本身读到的就是访问者地址,两层重合,看不出问题。前面加了一层代理或网关之后,连接的对端变成代理,真实访问者的地址只能靠代理写进转发头里带过来。这两条线索一条是握手时确定的、由不得对方编,一条是文本形式传过来的、发请求的人想写什么就写什么,一旦代码把它们视作同一种信息,判定对象就开始错位。
两层地址本来就不是一回事
第一层是连接层来源。它由传输层确定,程序拿到的是「谁跟我建立了这条 TCP 连接」。挂上代理之后,这个值稳定地指向代理自身的地址,日志里成片的相同来源就是这么来的。第二层是声称层来源,常见形态是 X-Forwarded-For 这类头,内容由前置节点追加,用来转述「最初请求来自哪里」。它能用,但可用性取决于链路是否可信:如果请求先经过攻击者控制的节点再进入站点,那这条声称完全可以是伪造的。
判定逻辑要区分这两件事:连接层来源是证据,声称层来源是证词。把证词当证据用,等于让访问者自己填报身份。
无条件信任来源头会出什么事
出问题的写法很直白:程序取地址时先看转发头,头里有什么就用什么,既不检查这一跳是不是自己的代理,也不管头里已经串了几段。PbootCMS 在 V3.2.25 的更新记录里修过这类问题——取地址函数无条件信任 X-Forwarded-For,导致 IP 黑白名单与登录锁定可以被绕过,同一版本把可信代理配置项加了进来。
后果具体落在两处。登录锁定是按来源地址累计失败次数的,地址可自报,攻击者每次换一个值,计数就永远落不到同一个人身上,锁定形同虚设。黑名单反过来是封禁手段,封的是真实对端,判定用的却是可伪造的字段,于是真地址照打、假地址被误伤。两处共用的缺陷是同一个:拿可编辑的字段当身份标识。
只在可信链路里取最右可信值
通行的做法是先声明可信代理网段,再决定从哪一段取值。请求经过多级代理时,转发头里会串出多个地址,越靠左的越可能是访问者自填,越靠右的越接近自己掌控的那一跳,所以取值规则应当是从右往左走,第一个不属于可信代理网段的地址即为来源。不在可信名单里的跳,它写进来的内容一律不采信,退回连接层地址。
这套规则的前提是把「我的代理是谁」配置成显式事实,而不是靠推断。配置项在程序里、在网关里都可能出现,两处都改才算改完;只改一层时,另一层仍在按旧规则取值,日志与判定的分歧就会继续留着。
限速与锁定该用哪一层地址
按来源做惩罚的功能,用哪一层地址取决于惩罚要对谁生效。限制暴力尝试、封禁滥用来源,目标是真实发起方,因此要用经过可信链路还原后的地址;针对某个入口做整体限流,作用对象是进入本站的这条通道,用连接层地址反而更稳。把两类混在一个开关上,常见现象是锁定误伤一大片同源出口的正常用户,而真正的高频尝试却在换一个自报值之后就恢复。
同一份地址还要看写进日志的字段是哪一层。排查时只有连接层地址,判定用的是声称层地址,两边对不上,复现与统计都会失真。
常见问题
问:把转发头整个删掉是不是更省事? 答:删掉之后连接层地址只剩代理自己,判定会退化成「按代理地址集体处置」,误伤范围反而更大。正确做法是保留并配可信网段,而不是取消这一层信息。
问:只有一层自建代理,还需要配可信网段吗? 答:需要。可信网段的作用是确认这一跳值得采信,不取决于层数多少。配了之后取值规则才有依据,将来加一层缓存或网关时也不用重新推导。
问:程序改了,网关那边还要动吗? 答:要。地址由前置节点写入、由程序读取,两端的约定改了才生效。只改程序时,前置节点是否追加、追加格式如何,都还在旧状态。