AI智能
改变未来

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对象# openConnectionpublic 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.getInputStreamURLConnection.getInputStreamRequest.Get.executeRequest.Post.executeURL.openStreamImageIO.readOkHttpClient.newCall.executeHttpClients.executeHttpClient.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;  //强转为HttpURLConnectionBufferedReader 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 {@Overrideprotected 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;  //强转为HttpURLConnectionBufferedReader 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();}}@Overrideprotected 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 {@Overrideprotected 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);}}@Overrideprotected 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 {@Overrideprotected 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);}}@Overrideprotected 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

赞(0) 打赏
未经允许不得转载:爱站程序员基地 » Java审计之SSRF