Skip to content

openresty使用lua修改HTTP-Header

背景

内网部署,只通过IP访问相应服务,而渗透测试反馈说如果请求头中的Host被修改成恶意地址,302重定向就会跳转到Host字段对应的地址。
本来是想用proxy_set_header + $server_addr解决这个安全风险

nginx
server {
    ...
    proxy_set_header Host $server_addr;
    ...
}

但是考虑到如下两点,还是决定用lua解决。

  1. 使用$server_addr时,每次请求都会触发一次系统调用,有性能隐患;
  2. 因为$server_addr取的是接收请求或连接的那块网卡的地址,所以如果nginx部署在docker网络中,或nginx前还有其他前置机,会导致Host的值是客户端到达不了的地址。

脚本编写

对外开放的IP写在了一个配置文件中,所以可以读取这个文件中的IP,保存到全局变量中,修改Header时直接用全局变量中的值。
假设lua脚本保存路径为/etc/demo.lua,配置文件位置为/etc/demo.conf,IP字段名为sys.en.ip,举个例子

properties
...
foo.bar1=34g
foo.bar2=3444
sys.en.ip=10.10.10.10
...

读取IP的lua脚本如下

lua
function readEntranceAddr()
    local file = io.open('/etc/demo.conf','r')
    local sysEntranceAddr = nil;
    for line in file:lines() do
        sysEntranceAddr = line:match('^sys%.en%.ip%s?=(.*)$')
        if (sysEntranceAddr ~= nil) then
            break
        end
    end
    file:close()
    return sysEntranceAddr
end
entraceAddr = readEntranceAddr()

OpenResty加载lua脚本

使用init_by_lua_file指令,加载读IP的脚本。
按照openresty的文档,init_by_lua_file应该在http的context中,在实际测试时,放到http或server中都可以。
为了在所有服务中都能使用这个脚本,init_by_lua_file放在了nginx.conf的http中。

nginx
http {
    ...
    init_by_lua_file '/etc/demo.lua';
    ...
}

修改Request中的Header

如果接收到请求之后,还要通过proxy_pass等指令将请求代理走,可能会使用proxy_set_header修改请求头。
当nginx的变量不能满足要求时,就得通过lua脚本修改请求头字段了。
以背景描述的需求为例,需要把请求头中的Host强制改为配置文件中指定的IP。
对应的生命周期是access阶段,所以配置文件如下

nginx
http {
    ...
    init_by_lua_file '/etc/demo.lua';
    access_by_lua_block {
        ngx.req.set_header("Host", entraceAddr .. ":" .. ngx.var.server_port)
    }
    ...
}

修改Response中的Header

主要是用来给浏览器发送安全策略,假设Response中也需要带回Host字段,且值为配置文件中指定的IP。
可以使用header_filter_by_lua指令完成,配置文件如下

nginx
http {
    ...
    init_by_lua_file '/etc/demo.lua';
    header_filter_by_lua '
        ngx.header["Host"] = entraceAddr .. ":" .. ngx.var.server_port
    ';
    ...
}

参考

lua-nginx-module文档
nginx proxy模块文档