Java审计之SSRF
Java中的SSRF
SSRF(Server-Side Request Forge, 服务端请求伪造),一般在一些请求url资源的时候会遇到,如
?url=https://ip:prot/xxx.jpg
请求别的站点的静态资源之类的。
SSRF在PHP中可能骚操作会比较多,主要是因为支持gopher协议,可利用的姿势大概有:
- http协议+burp进行端口探测
- http协议进行主机存活探测
- file协议读取本地文件
- http/gopher/dict打内网redis
- gopher打内网其他脆弱服务
- 盲SSRF+gopher+302跳转打内网脆弱服务
- ……
但是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协议处理文件过程
- 下载 jar/zip 文件到临时文件中
- 提取出我们指定的文件
- 删除临时文件
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);
修复建议
- 限制协议只能为http/https,防止跨协议
- 设置内网ip黑名单(正确判定内网ip、正确获取host)
小结
其实更多的还是小结了一些可能会出现漏洞的代码和例子,但是比如如何绕过一些ip或者host限制,如8进制、16进制ip进行绕过;30x跳转后转换协议绕过;DNS rebinding等等都没有详细的记录。后面会更深入的去做一下眼研究。感兴趣的师傅可以参考鹅厂这篇文章。
https://security.tencent.com/index.php/blog/msg/179