SSRF漏洞
SSRF(Server-Side Request Forgery:服务器端请求伪造) 是一种由攻击者构造形成由服务端发起请求的一个安全漏洞。一般情况下,SSRF攻击的目标是从外网无法访问的内部系统。因为它是由服务端发起的,所以它能够请求到与它相连而与外网隔离的内部服务器系统。
支持的协议
file
ftp
mailto
http
https
jar
netdoc
Java 中能发起网络请求的类:
HttpClient 类
HttpURLConnection 类
URLConnection 类
URL 类
OkHttp 类
ImageIO 类
Request 类 (Request 是对 HttpClient 类进行了封装的类,类似于 Python 的 requests 库。)
其中,仅支持 HTTP/HTTPS 协议的类(即类名或封装的类名带 http):
HttpClient 类
HttpURLConnection 类
OkHttp 类
Request 类
支持 sun.net.www.protocol 所有协议的类:
URLConnection 类
URL 类
ImageIO 类
在Java中,
sun.net.www.protocol
包含了一些用于处理不同网络协议的类。然而,需要注意的是,sun.net.www.protocol
包是Sun/Oracle JDK的内部实现细节,它并不是Java平台的公共API的一部分。因此,这些类可能在不同的JDK版本中有所变化,而且它们并不受 Java 官方支持,可能在将来的版本中会被移除。以下是一些可能存在的
sun.net.www.protocol
包中的协议类的示例,但这并不是一个完整的列表,具体的类可能会因 JDK 版本而异:
- HTTP 协议:
sun.net.www.protocol.http.HttpURLConnection
- HTTPS 协议:
sun.net.www.protocol.https.HttpsURLConnectionImpl
- FTP 协议:
sun.net.www.protocol.ftp.FtpURLConnection
- Jar 协议:
sun.net.www.protocol.jar.JarURLConnection
- File 协议:
sun.net.www.protocol.file.FileURLConnection
请注意,使用这些类可能会导致你的代码对特定的JDK版本产生依赖,并且在未来的版本中可能会出现问题。推荐使用Java平台提供的标准API,如
java.net.URL
和java.net.HttpURLConnection
,以确保更好的可移植性和稳定性。以上内容由AI生成
程序中发起 HTTP 请求操作一般在获取远程图片、页面分享收藏等业务场景, 在代码审计时可重点关注一些 HTTP 请求操作函数,如下:
审计关键词
HttpClient.execute
HttpClient.executeMethod
HttpURLConnection.connect
HttpURLConnection.getInputStream
URL.openStream
URLConnection.getInputStream
Request.Get.execute
Request.Post.execute
ImageIO.read
OkHttpClient.newCall.execute
HttpServletRequest
BasicHttpRequest
漏洞代码
漏洞代码 1
package com.example;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
@WebServlet("/ssrf1")
public class ssrf1Servlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf-8");
resp.setCharacterEncoding("utf-8");
resp.setContentType("text/html; charset=utf-8");
String url = req.getParameter("url");
System.out.println(url);
StringBuffer sb = new StringBuffer();
URL pic = new URL(url);
URLConnection urlConnection = pic.openConnection();
BufferedReader in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream(),"UTF-8"));
String line;
while ((line =in.readLine())!=null){
sb.append(line);
}
in.close();
resp.getWriter().write(sb.toString());
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doGet(req,resp);
}
}
访问百度
访问本机文件
http://localhost:8080/ssrf1?url=file:///Users/Oi/Downloads/_%E6%9C%AA%E5%91%BD%E5%90%8D.txt
漏洞代码 2
package com.example;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import sun.net.www.http.HttpClient;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
@WebServlet("/ssrf2")
public class ssrf2Servlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String url =req.getParameter("url");
String content=null;
CloseableHttpClient httpclinet = HttpClients.createDefault(); //限制协议只能扫描内容
HttpGet httpGet = new HttpGet(url);
CloseableHttpResponse execute = httpclinet.execute(httpGet);
if(execute.getStatusLine().getStatusCode()==200){
HttpEntity entity = execute.getEntity();
content = EntityUtils.toString(entity, "utf-8");
}
resp.getWriter().write(content);
execute.close();
httpclinet.close();
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doGet(req,resp);
}
}
ImageIO ssrf
package com.example;
import javax.imageio.ImageIO;
import javax.imageio.stream.ImageInputStream;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
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;
}
}
package com.example;
import javax.imageio.ImageIO;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
@WebServlet("/ssrf3")
public class ssrf3Servlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String geturl = req.getParameter("url");
ServletOutputStream outputStream = resp.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);
}
}
}
可以判断文件是否存在
常见的漏洞代码
String url = request.getParameter("picurl");
StringBuffer response = new StringBuffer();
URL pic = new URL(url);
HttpURLConnection con = (HttpURLConnection) pic.openConnection();
con.setRequestMethod("GET");
con.setRequestProperty("User-Agent", "Mozilla/5.0");
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
modelMap.put("resp",response.toString());
return "getimg.htm";
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();
ImageIO类
// ImageIO ssrf vul
String url = request.getParameter("url");
URL u = new URL(url);
BufferedImage img = ImageIO.read(u); // 发起请求,触发漏洞
其他类
// Request漏洞示例
String url = request.getParameter("url");
return Request.Get(url).execute().returnContent().toString();//发起请求
// openStream漏洞示例
String url = request.getParameter("url");
URL u = new URL(url);
inputStream = u.openStream(); //发起请求
// 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(); //发起请求
// HttpClients漏洞示例
String url = request.getParameter("url");
CloseableHttpClient client = HttpClients.createDefault();
HttpGet httpGet = new HttpGet(url);
HttpResponse httpResponse = client.execute(httpGet); //发起请求
修复建议
-
设置内网ip黑名单(正确判定内网ip、正确获取host)
-
禁止30x跳转(跟随跳转需要从1开始重新检测)
-
限制请求的端口为http常用的端口,如 80、443、8080、8090等
-
去除url中的特殊字符
-
如果是域名的话,可以将url中的域名改为ip
-
根据请求来源,判定请求地址是否是固定请求来源,若是,则将这几个特定的域名/IP 添加到白名单,并拒绝白名单域名/IP 之外的请求
-
若业务需求和请求来源并不固定,则可以自己编写一个 ssrfCheck 函数,检测特定的域名、判断是否是内网 IP、判断是否为 http/https 协议等
-
请求时设置host header为ip
-
统一错误信息,避免用户根据错误信息来判断远端服务器的端口状态
-
根据业务需求,判定所需的域名是否是常用的几个,若是,则将这几个特定的域名加入白名单,并拒绝白名单域名之外的请求