Java 審計之SSRF篇

nice_0e3發表於2020-09-17

Java 審計之SSRF篇

0x00 前言

本篇文章來記錄一下Java SSRF的審計學習相關內容。

0x01 SSRF漏洞詳解

原理:

服務端提供了從其他伺服器應用獲取資料的功能且沒有對目標地址做過濾與限制。

大部分的web伺服器架構中,web伺服器自身都可以訪問網際網路和伺服器所在的內網。

ssrf作用:

對外網伺服器所在的內網、本地進行埠掃描,獲取一些服務的banner資訊 。

攻擊執行在內網或者本地的應用程式。

對內網web應用進行指紋識別,通過訪問預設檔案實現 。

攻擊內外網的web應用。sql注入、struct2、redis等。

利用file協議讀取本地檔案等。

php ssrf中的偽協議:

file dict sftp ldap tftp gopher

Java ssrf 中的偽協議:

file ftp mailto http https jar netdoc

0x02 SSRF產生過程

在java中ssrf會分比較多的場景,不像PHP中那樣支援各種偽協議都可以去直接使用。

SSRF中內網探測

@WebServlet("/ssrfServlet")
public class ssrfServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this.doGet(request, response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String url = request.getParameter("url");   //接收url的傳參
        String htmlContent;
        PrintWriter writer = response.getWriter();  //獲取響應的列印流物件
        URL u = new URL(url);   //例項化url的物件
        try {
            URLConnection urlConnection = u.openConnection();//開啟一個URL連線,並執行客戶端訪問資源。
            HttpURLConnection httpUrl = (HttpURLConnection) urlConnection;  //強轉為HttpURLConnection
            BufferedReader base = new BufferedReader(new InputStreamReader(httpUrl.getInputStream(), "UTF-8"));  //獲取url中的資源
            StringBuffer html = new StringBuffer();
            while ((htmlContent = base.readLine()) != null) {
                html.append(htmlContent);  //htmlContent新增到html裡面
            }
            base.close();

            writer.println(html);//響應中輸出讀取的資源
            writer.flush();

        } catch (Exception e) {
            e.printStackTrace();
            writer.println("請求失敗");
            writer.flush();
        }
}

在程式碼中HttpURLConnection httpUrl = (HttpURLConnection) urlConnection;,這個地方進行了強制轉換,去某度搜尋了一下具體用意。得出結論:

URLConnection:可以走郵件、檔案傳輸協議。
HttpURLConnection 只能走瀏覽器的HTTP協議

也就是說使用了強轉為HttpURLConnection後,利用中只能使用http協議去探測該伺服器內網的其他應用。

http://localhost:8080/ssrfServlet?url=http://www.baidu.com

這裡用來百度來做一個演示,因為懶得自己再在內網中搭建一個環境了。

在程式碼中,我們未對接收過來的url進行校驗,校驗其url是否是白名單的url就直接進行了建立url物件進行訪問和讀取資源,導致了ssrf的產生。

嘗試一下能不能讀取檔案

這裡會發現根本讀取不了,因為這裡只支援http和https的協議。

下面來試試,在不強制轉換成HttpURLConnection的情況下試試。

程式碼如下:

@WebServlet("/ssrfServlet")
public class ssrfServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this.doGet(request, response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String url = request.getParameter("url");   //接收url的傳參
        String htmlContent;
        PrintWriter writer = response.getWriter();  //獲取響應的列印流物件
        URL u = new URL(url);   //例項化url的物件
        try {
            URLConnection urlConnection = u.openConnection();//開啟一個URL連線,並執行客戶端訪問資源。
//            HttpURLConnection httpUrl = (HttpURLConnection) urlConnection;  //強轉為HttpURLConnection
            BufferedReader base = new BufferedReader(new InputStreamReader(urlConnection.getInputStream(), "UTF-8"));  //獲取url中的資源
            StringBuffer html = new StringBuffer();
            while ((htmlContent = base.readLine()) != null) {
                html.append(htmlContent);  //htmlContent新增到html裡面
            }
            base.close();

            writer.println(html);//響應中輸出讀取的資源
            writer.flush();

        } catch (Exception e) {
            e.printStackTrace();
            writer.println("請求失敗");
            writer.flush();
        }
http://localhost:8080/ssrfServlet?url=file:///c:%5c%5cwindows%5c%5cwin.ini

可以成功讀取到c:\windows\win.ini的檔案。

SSRF中的讀取檔案

程式碼如下:

@WebServlet("/readfileServlet")
public class downloadServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this.doGet(request,response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {


        String url = request.getParameter("url");
        int len;
        OutputStream outputStream = response.getOutputStream();
            URL file = new URL(url);
            byte[] bytes = new byte[1024];
        InputStream inputStream = file.openStream();

            while ((len = inputStream.read(bytes)) > 0) {
                outputStream.write(bytes, 0, len);
            }
    }
}

和上面的程式碼對比一下,發現其實都大致相同,唯一不同的地方是一個是用openStream方法獲取物件,一個是用openConnection獲取物件。兩個方法類似。

官方說明文件:

openConnection():返回一個例項,該例項表示與所引用的遠端物件的連線。 返回型別: URLConnection
openStream():開啟與此連線,並返回一個值以從該連線讀取。 			  返回型別:  InputStream 

詳細說明:

openConnection:返回一個URLConnection物件,它表示到URL所引用的遠端物件的連線。每次呼叫此URL的協議處理程式的openConnection方法都開啟一個新的連線。如果URL的協議(例如,HTTP或JAR)存在屬於以下包或其子包之一的公共、專用URLConnection子類:java.lang、java.io、java.util、java.net,返回的連線將為該子類的型別。例如,對於HTTP,將返回HttpURLConnection,對於JAR,將返回JarURLConnection。(返回到該URL的URLConnection!)

openStream():開啟到此URL的連線並返回一個用於從該連線讀入的InputStream。

這裡啟動一下伺服器,測試一下。

http://127.0.0.1:8080//downloadServlet?url=file:///C:%5c%5c1.txt

注意: 這裡是三個斜杆,並且反斜槓需要url編碼 否則就會報錯

未經過url編碼直接傳入反斜槓

SSRF中的檔案下載

漏洞程式碼:

@WebServlet("/downloadServlet")
public class downloadServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this.doGet(request,response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String filename = "1.txt";

        String url = request.getParameter("url");
        response.setHeader("content-disposition", "attachment;fileName=" + filename);
        int len;
        OutputStream outputStream = response.getOutputStream();
            URL file = new URL(url);
            byte[] bytes = new byte[1024];
        InputStream inputStream = file.openStream();

            while ((len = inputStream.read(bytes)) > 0) {
                outputStream.write(bytes, 0, len);
            }
    }
}

輸入:

http://localhost:8080/downloadServlet?url=file:///c:%5c%5c1.txt

這樣就把檔案給下載下來了,ssrf中的檔案下載和檔案讀取不同點在於響應頭。

 response.setHeader("content-disposition", "attachment;fileName=" + filename);

這段程式碼,設定mime型別為檔案型別,訪問瀏覽器的時候就會被下載下來。

參考文章

https://xz.aliyun.com/t/2761#toc-1
https://xz.aliyun.com/t/206/
https://xz.aliyun.com/t/7186

0x03 結尾

SSRF的一些產生也不止文章裡面寫到的這麼一點,包括一些第三方的元件,如果在未經過驗證的情況下發起一個遠端請求,那麼都有可能存在SSRF漏洞。

後面打算找套原始碼專門審計一下SSRF。

相關文章