Java 審計之SSRF篇

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協議去探測該伺服器內網的其他應用。

//localhost:8080/ssrfServlet?url=//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();
        }
//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。

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

//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);
            }
    }
}

輸入:

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

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

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

這段程式碼,設置mime類型為文件類型,訪問瀏覽器的時候就會被下載下來。

參考文章

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

0x03 結尾

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

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