JAVA代码审计之SSRF
Posted Jiajiazml
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JAVA代码审计之SSRF相关的知识,希望对你有一定的参考价值。
1. SSRF漏洞简介
服务端请求伪造(Server-side Request Forge):是一种由攻击者构造形成由服务端发起请求的一个安全漏洞。很多web应用都提供了从其他的服务器上获取数据的功能。使用指定的URL,web应用便可以获取图片,下载文件,读取文件内容等。SSRF的实质是利用存在缺陷的web应用作为代理攻击远程和本地的服务器。一般情况下, SSRF攻击的目标是外网无法访问的内部系统,黑客可以利用SSRF漏洞获取内部系统的一些信息(正是因为它是由服务端发起的,所以它能够请求到与它相连而与外网隔离的内部系统)。SSRF形成的原因大都是由于服务端提供了从其他服务器应用获取数据的功能且没有对目标地址做过滤与限制。
Java中的SSRF支持sun.net.www.protocol 里的所有协议:http,https,file,ftp,mailto,jar,netdoc。相对于php,在java中SSRF的利用局限较大,一般利用http协议来探测端口,利用file协议读取任意文件。
2. SSRF攻击路径图
Hack要攻击内网服务器2、3、4只能通过存在SSRF漏洞的web服务器来进行攻击,不能直接访问内网服务器,这时候web服务器就是跳板
服务器2、3、4不通外网
SSRF审计函数
3. SSRF审计函数
SSRF漏洞一般位于远程图片加载与下载、图片或文章收藏功能、URL分享、通过URL在线翻译、转码等功能点处。当然,SSRF是由发起网络请求的方法造成。代码审计时需要关注的发起HTTP请求的类及函数,部分如下:
HttpURLConnection. getInputStream |
4. SSRF漏洞危害
1.内外网的端口和服务扫描
2.攻击运行在内网或者本地的应用程序
3.对内网web应用进行指纹识别,识别企业内部的资产信息
4.攻击内网的web应用,主要是使用GET参数就可以实现的攻击
5.向内部任意主机的任意端口发送精心构造的pPayload
6.利用file协议读取本地敏感文件
5.漏洞代码示例
5.1 HttpURLConnection
//HttpURLConnection ssrf vul String url = request.getParameter("url"); URL u = new URL(url); URLConnection urlConnection = u.openConnection(); HttpURLConnection httpUrl = (HttpURLConnection)urlConnection; BufferedReader in = new BufferedReader(new InputStreamReader(httpUrl.getInputStream())); //发起请求,触发漏洞 String inputLine; StringBuffer html = new StringBuffer(); while ((inputLine = in.readLine()) != null) html.append(inputLine); System.out.println("html:" + html.toString()); in.close();
以上代码大致意义如下:
URL对象用openconnection()打开连接,获得URLConnection类对象。
用InputStream()获取字节流
从打开的连接获取一个 InputStream,可以从中得到 URL 请求的响应流。在调用这个方法时,会自动调用 URLConnection.connect() 方法,也就是建立连接。所以一旦调用 getInputStream() 连接就已经建立好了,不管后续做什么操作,这个 URL 请求都已经发出去了。
然后InputStreamReader()将字节流转化成字符流,BufferedReader()将字符流以缓存形式输出的方式来快速获取网络数据流,最终一行一行的输入到 html 变量中,输出到浏览器。
5.2 urlConnection
//urlConnection ssrf vul String url = request.getParameter("url"); URL u = new URL(url); URLConnection urlConnection = u.openConnection(); BufferedReader in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream())); //发起请求,触发漏洞 String inputLine; StringBuffer html = new StringBuffer(); while ((inputLine = in.readLine()) != null) html.append(inputLine); System.out.println("html:" + html.toString()); in.close();
5.3 ImageIO
// ImageIO ssrf vul String url = request.getParameter("url"); URL u = new URL(url); BufferedImage img = ImageIO.read(u); // 发起请求,触发漏洞
5.4 其他
//1、 Request漏洞示例 String url = request.getParameter("url"); return Request.Get(url).execute().returnContent().toString();//发起请求 //2、 URL类中的openStream漏洞示例 String url = request.getParameter("url"); URL u = new URL(url); inputStream = u.openStream(); //发起请求 // 3、OkHttpClient漏洞示例 String url = request.getParameter("url"); OkHttpClient client = new OkHttpClient(); com.squareup.okhttp.Request ok_http = new com.squareup.okhttp.Request.Builder().url(url).build(); client.newCall(ok_http).execute(); //发起请求 // 4、HttpClients漏洞示例 String url = request.getParameter("url"); CloseableHttpClient client = HttpClients.createDefault(); HttpGet httpGet = new HttpGet(url); HttpResponse httpResponse = client.execute(httpGet); //发起请求
6SSRF实战案例
针对端口探测和任意文件下载/读取进行了实例说明。
6.1 内网端口探测
@WebServlet("/ssrfTest") public class ssrfTest extends HttpServlet private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException request.setCharacterEncoding("utf-8"); response.setContentType("text/html;charset=utf-8"); PrintWriter writer = response.getWriter(); //获取响应的打印流对象 String url = request.getParameter("url"); //接收url的传参 String htmlContent; try URL u = new URL(url); //实例化url的对象 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("ERROR"); writer.flush();
代码的主要功能即是模拟一个 http 请求,如果没有对请求地址进行限制和过滤,即可以利用来进行 SSRF 攻击。
在代码中HttpURLConnection httpUrl = (HttpURLConnection) urlConnection;,这个地方进行了强制转换:
URLConnection:可以走邮件、文件传输协议。 |
也就是说使用了强转为HttpURLConnection后,利用中只能使用http协议去探测该服务器内网的其他应用。
假设外网可以访问本机地址,但不能访问有道网站地址。这里直接用有道翻译进行演示
http://localhost:8080/ssrf/ssrfTest?url=https://fanyi.youdao.com/
在代码中,我们未对接收过来的url进行校验,校验其url是否是白名单的url就直接进行了创建url对象进行访问和读取资源,导致了ssrf的产生
尝试一下能不能读取文件,输入http://localhost:8080/ssrf/ssrfTest?url=file:///c:/windows/win.ini
发现根本读取不了,因为这里只支持http和https的协议。
6.2 任意文件读取
我们将上述代码删除一行,如下:
@WebServlet("/ssrfFileRead") public class ssrfFileRead extends HttpServlet private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException request.setCharacterEncoding("utf-8"); response.setContentType("text/html;charset=utf-8"); PrintWriter print = response.getWriter(); String url = request.getParameter("url"); String htmlContent; try URL u = new URL(url); URLConnection urlConnection = u.openConnection(); BufferedReader base = new BufferedReader(new InputStreamReader(urlConnection.getInputStream())); StringBuffer html = new StringBuffer(); while ((htmlContent = base.readLine()) != null) html.append(htmlContent); base.close(); print.println(html.toString()); print.flush(); catch (Exception e) e.printStackTrace(); print.println("ERROR!"); print.flush();
HttpURLconnection()是基于http协议的,而我们要用的是 file 协议,删除后即可利用file协议去读取任意文件 ,如下图所示:
输入 http://localhost:8080/ssrf/ssrfFileRead?url=file:///c:/windows/win.ini
输入 http://localhost:8080/ssrf/ssrfFileRead?url=file:///d:/1.txt
如果我们知道了网站的路径,可以直接读取其数据库连接的相关信息
6.3 任意文件下载
任意文件下载同理,只不过是将数据流写入到了文件中,如下代码:
@WebServlet("/ssrfFileDown") public class ssrfFileDown extends HttpServlet private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException int length; String downLoadImgFileName = "SsrfFileDownTest.txt"; InputStream inputStream = null; OutputStream outputStream = null; String url = req.getParameter("url"); try resp.setHeader("content-disposition", "attachment;fileName=" + downLoadImgFileName); URL file = new URL(url); byte[] bytes = new byte[1024]; inputStream = file.openStream(); outputStream = resp.getOutputStream(); while ((length = inputStream.read(bytes)) > 0) outputStream.write(bytes, 0, length); catch (Exception e) e.printStackTrace(); finally if (inputStream != null) inputStream.close(); if (outputStream != null) outputStream.close();
和上面的代码对比一下,发现其实都大致相同,唯一不同的地方是一个是用openStream方法获取对象,一个是用openConnection获取对象。两个方法类似。
详细说明:
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://localhost:8080/ssrf/ssrfFileDown?url=file:///c:/windows/win.ini
将获取的内容写入到SsrfFileDownTest.txt文件中,测试如下:
这样就把文件给下载下来了,ssrf中的文件下载和文件读取不同点在于响应头。
resp.setHeader("content-disposition", "attachment;fileName=" + downLoadImgFileName);
7.SSRF漏洞防御
1.限制协议为HTTP、HTTPS协议。
2.禁止30x跳转。(http重定向状态码)
3.设置URL白名单或者限制内网IP。
4.限制请求的端口为http常用的端口。
以上例中HttpURLConnection为例,防御代码如下:
String url = request.getParameter("url"); if (!SSRFHostCheck(url)) System.out.println("warning!!! illegal url:" + url); return; URL u = new URL(url); URLConnection urlConnection = u.openConnection(); HttpURLConnection httpUrl = (HttpURLConnection)urlConnection; httpUrl.setInstanceFollowRedirects(false); //禁止30x跳转 BufferedReader in = new BufferedReader(new InputStreamReader(httpUrl.getInputStream())); //send request …………………… public static Boolean SSRFHostCheck(String url) try URL u = new URL(url); // 限制为http和https协议 if (!u.getProtocol().startsWith("http") && !u.getProtocol().startsWith("https")) String uProtocol = u.getProtocol(); System.out.println("illegal Protocol:" + uProtocol); return false; // 获取域名或IP,并转为小写 String host = u.getHost().toLowerCase(); String hostwhitelist = "192.168.199.209"; //白名单 if (host.equals(hostwhitelist)) System.out.println("ok_host:" + host); return true; else System.out.println("illegal host:" + host); return false; catch (Exception e) return false;
Java代码审计之路二(SSRF漏洞审计)
文章目录
SSRF
Java 网络请求支持的协议,包括:http、https、file、mailto、jar、netdoc,但是相对 PHP 可以利用很多伪协议来说 Java SSRF 还是略显单调
补充:
mailto:是一个用于发送邮件的 URL 协议
jar:Jar URL协议解析,协议可以用来读取 zip 格式文件(包括 jar 包)中的内容
netdoc 协议:在大部分情况下可代替 file
虽然支持多种协议,但是 Java 的 SSRF 利用方式比较局限,一般只用以下两种方式
- 利用 file 协议任意文件读取
- 利用 http 协议端口探测
环境搭建
代码来自 Java 代码审计入门篇一书作者 panda https://github.com/cn-panda/JavaCodeAudit
因为 panda 师傅使用的是 eclipse,用 idea 的话需要稍微操作一下
-
使用 idea 导入项目
File–>New–>Project from Existing sources
选择 Eclispe 项目,然后点击 OK,进入下一步
选择第二项,导入 Eclipse 项目
然后一路 next 即可
在 WEB-INF 目录下新建 lib 目录和 classes 目录
-
配置项目
打开 Project Structure 窗口
设置 Modules,Eclipse 导入的项目此处主要是将 Dependencies 中关于 Eclipse 的依赖移除即可,然后点击加号添加 lib 目录
点击加号,选择 tomcat 所在的目录,在 lib 目录下找到 servlet-api.jar 这个 jar 包导入完成即可,不导入会提示
java: 程序包 javax.servlet 不存在
然后设置 Libraries,选择之前新建的 lib 目录
配置打包方式 Artifacts,点击 Artifacts 选项卡
-
配置 tomcat
-
启动访问 http://localhost:8080/ssrf_Web_exploded/
漏洞分析
程序一个简单利用代码 Demo
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
PrintWriter print = response.getWriter();
String url = request.getParameter("url");
String htmlContent;
try
URL u = new URL(url);
URLConnection urlConnection = u.openConnection();
HttpURLConnection httpUrl = (HttpURLConnection) urlConnection;
BufferedReader base = new BufferedReader(new InputStreamReader(httpUrl.getInputStream(), "UTF-8"));
StringBuffer html = new StringBuffer();
while ((htmlContent = base.readLine()) != null)
html.append(htmlContent);
base.close();
print.println("<b>端口探测</b></br>");
print.println("<b>url:" + url + "</b></br>");
print.println(html.toString());
print.flush();
catch (Exception e)
e.printStackTrace();
print.println("ERROR!");
print.flush();
- URL 对象用 openConnection 打开连接,获取 URLConnection 类对象
- 用 InputStream 获取字节流
- 然后用 InputStreamReader 将字节流转换成字符流
- BufferedReader 将字符流以缓存形式输出的方式来获取网络数据流,
- 最后一行行输出到浏览器
这样就通过发送 http 请求来发起内网 ssrf 攻击
如果端口没有开启 http/https 服务,则会返回ERROR
字符,通过返回结果判断是否开放端口,特别注意的是不像 PHP 可以通过 dict 伪协议一样可以判断所有端口服务,这里只能判断 http 服务是否开启
其他利用方式
任意文件读取/下载
在刚才的代码基础上删除画框的行
HttpURLconnection() 是基于 http 协议的,删除后可以使用 file 协议去读取文件,文件下载也一样不过是将数据流写入了文件中
敏感函数
Java 代码审计中要注意的可能会触发 ssrf 的敏感函数如下
HttpClient.execute()
HttpClient.executeMethod()
HttpURLConnection.connect()
HttpURLConnection.getInputStream()
URL.openStream()
HttpServletRequest()
getParameter()
URL
HttpClient()
Request(对HttpClient封装后的类)
HttpURLConnection()
URLConnection()
okhttp()
BasicHttpEntityEnclosingRequest()
DefaultBHttpClientConnection()
BasicHttpRequest()
参考
Java 代码审计入门篇
以上是关于JAVA代码审计之SSRF的主要内容,如果未能解决你的问题,请参考以下文章