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
URLConnection. getInputStream
Request.Get. execute
Request.Post. execute
URL.openStream
ImageIO.read
OkHttpClient.newCall.execute
HttpClients. execute
HttpClient.execute
……
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协议
也就是说使用了强转为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;
}
}
版权归原作者 Jiajiazml 所有, 如有侵权,请联系我们删除。