Loading

Java审计之SSRF

Java审计之SSRF

Java中的SSRF

SSRF(Server-Side Request Forge, 服务端请求伪造),一般在一些请求url资源的时候会遇到,如?url=https://ip:prot/xxx.jpg请求别的站点的静态资源之类的。

SSRF在PHP中可能骚操作会比较多,主要是因为支持gopher协议,可利用的姿势大概有:

  1. http协议+burp进行端口探测
  2. http协议进行主机存活探测
  3. file协议读取本地文件
  4. http/gopher/dict打内网redis
  5. gopher打内网其他脆弱服务
  6. 盲SSRF+gopher+302跳转打内网脆弱服务
  7. ......

但是java中(高版本jdk)仅支持如下协议

file ftp mailto http https jar netdoc

而低版本的jdk是有gopher的(参考javasec.org),这个具体还没有研究,后面遇到会进行补充。

回顾相关的常用类

URL

# 构造方法
new URL(String url)  
表示一个URL对象
# openConnection
public URLConnection openConnection() throws IOException
返回一个URLConnection实例,表示与URL引用的远程对象的URL 。
每次当调用此URL的协议处理程序的URLStreamHandler.openConnection(URL)方法时, 都会创建一个新的URLConnection实例。
应该注意的是,URLConnection实例不会在创建时建立实际的网络连接。 这只会在调用URLConnection.connect()时发生。

URLConnection

protected	URLConnection(URL url)
构造与指定URL的URL连接。

HttpURLConnection与URLConnection的区别

URLConnection 可以走邮件、文件传输协议,而HttpURLConnection 就单指浏览器的HTTP协议

也就是说,存在漏洞的代码比如在调用getInputStream时当前的对象为HttpURLConnection对象则只能走http协议去做一些探测内网ip存活/端口探测/打redis(有回显);而如果是URLConnection对象则可以利用file协议进行文件读取。

而对于http/https协议可利用的姿势就比较少,但是java默认在http/https协议会:

  • 默认启用了透明NTLM认证
  • 默认跟随跳转

但是302跳转后会进行协议判断,总体来说java的SSRF利用姿势依然会很有限。

其他具体区别可参照下图

可能存在漏洞的点

HttpURLConnection.getInputStream
URLConnection.getInputStream
Request.Get.execute
Request.Post.execute
URL.openStream
ImageIO.read
OkHttpClient.newCall.execute
HttpClients.execute
HttpClient.execute

HttpURLConnection

这个类只能用http协议

@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();
        }
}

URLConnection

用这个类就可以使用一些文件传输协议了,如file:///

public class SSRFServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        String url = req.getParameter("url");   //接收url的传参
        String htmlContent;
        PrintWriter writer = resp.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();
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doGet(req, resp);
    }
}

file协议

netdoc协议

url=netdoc:///Users/xxxx/sql.txt

jar协议

jar 协议语法,jar:{url}!/{entry},url是文件的路径,entry是想要解压出来的文件

当然这里url不仅仅是http协议,也可以是file协议或者netdoc

jar协议处理文件过程

  1. 下载 jar/zip 文件到临时文件中
  2. 提取出我们指定的文件
  3. 删除临时文件

jar+http

http://localhost:8888/ssrfServlet.do?url=jar:http://127.0.0.1:4444/sql.txt.zip!/sql.txt

python起个http

jar+file

http://localhost:8888/ssrfServlet.do?url=jar:file:///Users/xxx/Desktop/sql.txt.zip!/sql.txt

jar+netdoc

http://localhost:8888/ssrfServlet.do?url=jar:netdoc:///Users/xxx/Desktop/sql.txt.zip!/sql.txt

PS:如果文件路径存在反斜杠需要url编码成%5c

其实上面不管是URLConnection和HttpURLConnection都并不是在该类new对象时建立的URL,这点在文章开头也提到了

每次当调用此URL的协议处理程序的URLStreamHandler.openConnection(URL)方法时, 都会创建一个新的URLConnection实例。
应该注意的是,URLConnection实例不会在创建时建立实际的网络连接。 这只会在调用URLConnection.connect()时发生。

而类似于openConnection方法与URL建立连接的还有openStream()

openStream():打开到此URL的连接并返回一个用于从该连接读入的InputStream。

SSRF文件读取

public class SSRFFileReadServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String url = req.getParameter("url");
        int len;
        OutputStream outputStream = resp.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);
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doGet(req, resp);
    }
}
http://localhost:8888/ssrfFileRead.do?url=http://127.0.0.1:4444/sql.txt&filename=sql.txt

SSRF文件下载

public class SSRFFileDownloadServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String filename = req.getParameter("filename");

        String url = req.getParameter("url");
        resp.setHeader("content-disposition", "attachment;fileName=" + filename);
        int len;
        OutputStream outputStream = resp.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);
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doGet(req, resp);
    }
}
http://localhost:8888/ssrfDownload.do?url=http://127.0.0.1:4444/sql.txt&filename=sql.txt

而这里文件读取和下载用到的就都是openStream建立的连接。

而文件下载和读取的区别就在于这一段header头,当如下header偷存在,就会是文件下载漏洞

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

imageIO

imageIO是JDK自带的操作图片的类

public class ssrf {
    public static BufferedImage read(URL url) throws IOException {
        if (url == null) {
            System.out.println("输入内容为空");
        }


        InputStream  istream = url.openStream();
        ImageInputStream stream =  ImageIO.createImageInputStream(istream);  //获取文件流


        BufferedImage bi = ImageIO.read(stream);  //返回 BufferedImage作为供给的解码结果

        return bi;
    }
}

servlet

@WebServlet("/httpclientServlet")
public class httpclientServlet extends HttpServlet {


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

    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String geturl = request.getParameter("url");
        ServletOutputStream outputStream = response.getOutputStream();
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        URL url = new URL(geturl);
        BufferedImage image = ssrf.read(url);

        ImageIO.write(image, "png", os);
        InputStream input = new ByteArrayInputStream(os.toByteArray());
        int len;
        byte[] bytes = new byte[1024];
        while ((len = input.read(bytes)) > 0) {
            outputStream.write(bytes, 0, len);
        }


    }

}

但是只能读取文件以及探测文件存不存在

HttpClient

org.apache.commons.httpclient.HttpClient

相关依赖

<dependency>
    <groupId>commons-httpclient</groupId>
    <artifactId>commons-httpclient</artifactId>
    <version>3.1</version>
</dependency>
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.12</version>
</dependency>
 CloseableHttpClient httpClient = HttpClients.createDefault();
  HttpGet getRequest = new HttpGet(url);
  HttpResponse response = httpClient.execute(getRequest);
  if(response.getStatusLine().getStatusCode() == 200)
    {
        HttpEntity entity = response.getEntity();
        return EntityUtils.toByteArray(entity);
    }
  throw new IOException("Error:下载图片失败");

OkHttp

okhttp是一个第三方类库,用于android中请求网络

  String url = request.getParameter("url");
  OkHttpClient httpClient = new OkHttpClient();
  Request request = new Request.Builder()
        .url(url)
        .build();
  Response response = httpClient.newCall(request).execute();
  return response.body().string(); 

HttpRequest

第三方的工具类

HttpRequest request = HttpRequest.get("http://www.baidu.com",true,'q',"baseball gloves","size",100);

修复建议

  1. 限制协议只能为http/https,防止跨协议
  2. 设置内网ip黑名单(正确判定内网ip、正确获取host)

小结

其实更多的还是小结了一些可能会出现漏洞的代码和例子,但是比如如何绕过一些ip或者host限制,如8进制、16进制ip进行绕过;30x跳转后转换协议绕过;DNS rebinding等等都没有详细的记录。后面会更深入的去做一下眼研究。感兴趣的师傅可以参考鹅厂这篇文章。

https://security.tencent.com/index.php/blog/msg/179

posted @ 2021-09-07 00:22  Zh1z3ven  阅读(1753)  评论(0编辑  收藏  举报