什么是SSRF?
SSRF(Server-Side Request Forge, 服务端请求伪造) ,即攻击者构造恶意参数使服务端对其它内/外网系统进行访问或者攻击的一种方式。
Java支持的网络请求协议:
file ftp http https gopher(jdk≤1.7) jar netdoc mailto
SSRF代码审计函数
SSRF漏洞一般用于URL在线翻译、文件或图片的加载等功能处
代码审计时需要关注的发起HTTP请求的类及函数:
HttpURLConnection. getInputStream
URLConnection. getInputStream
Request.Get. execute
Request.Post. execute
URL.openStream
ImageIO.read
OkHttpClient.newCall.execute
HttpClients. execute
HttpClient.execute
……
SSRF漏洞代码示例
首先在任意文件夹下创建一个txt文件,并随便输入几行内容
同时在该文件夹下打开cmd,开启一个http服务方便后续漏洞测试:
python -m http.server 80
1. URLConnection
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;
//urlConnection
@WebServlet("/ssrf1")
public class SSRFDemo 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");
URL u = new URL(url);
System.out.println(u);
URLConnection urlConnection = u.openConnection();
InputStreamReader inputStream = new InputStreamReader(urlConnection.getInputStream()) ;
BufferedReader bufferedReader = new BufferedReader(inputStream);
String line;
StringBuffer stringBuffer = new StringBuffer();
while ((line = bufferedReader.readLine()) !=null){
stringBuffer.append(line);
}
inputStream.close();
bufferedReader.close();
resp.getWriter().write(stringBuffer.toString());
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp
) throws ServletException, IOException {
this.doGet(req,resp);
}
}
- 访问
/ssrf1?url=file:///D:\Code\JavaCode\test\123.txt
反斜杠 \报错,这是因为tomcat对GET请求中的| {} 等特殊字符存在限制(RFC 3986)
- 可以修改为POST请求
url=file:///D:\Code\JavaCode\test\123.txt
- 可以修改为斜杠(/)
/ssrf1?url=file:///D:/Code/JavaCode/test/123.txt
- 可以设置URL编码
/ssrf1?url=file:///D:%5cCode%5cJavaCode%5ctest%5c123.txt
2. HttpURLConnection
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.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
//HttpURLConnection ssrf vul
//http
@WebServlet("/ssrf2")
public class SSRFDemo2 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String url = req.getParameter("url");
URL u = new URL(url);
System.out.println(u);
URLConnection urlConnection = u.openConnection();
HttpURLConnection httpUrl = (HttpURLConnection)urlConnection;
InputStreamReader inputStream = new InputStreamReader(httpUrl.getInputStream()) ;
BufferedReader bufferedReader = new BufferedReader(inputStream);
String line;
StringBuffer stringBuffer = new StringBuffer();
while ((line = bufferedReader.readLine()) !=null){
stringBuffer.append(line);
}
inputStream.close();
bufferedReader.close();
resp.getWriter().write(stringBuffer.toString());
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doGet(req, resp);
}
}
- HttpURLConnection 只允许HTTP/HTTPS协议
- 访问
/ssrf2?url=http://192.168.1.103:80/123.txt
3. ImageIO
import javax.imageio.ImageIO;
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.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
@WebServlet("/ssrf3")
public class SSRFDemo3 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// ImageIO ssrf vul
String url = req.getParameter("url");
URL u = new URL(url);
BufferedImage img = ImageIO.read(u); // 发起请求,触发漏洞
resp.getWriter().write(img.toString());
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doGet(req, resp);
}
}
- 访问
/ssrf3?url=http://192.168.1.103:80/123.txt
,,如果获取到的不是图片,会返回null
* 访问
/ssrf3?url=http://192.168.1.103:80/xuegao.jpg
4. openStream
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.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
//openStream
@WebServlet("/ssrf4")
public class SSRFDemo4 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String url = req.getParameter("url");
URL u = new URL(url);
InputStream inputStream = u.openStream();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String line;
StringBuffer stringBuffer = new StringBuffer();
while (( line = bufferedReader.readLine()) != null){
stringBuffer.append(line);
}
inputStream.close();
inputStreamReader.close();
bufferedReader.close();
resp.getWriter().write(stringBuffer.toString());
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doGet(req, resp);
}
}
- 访问
/ssrf4?url=http://192.168.1.103:80/123.txt
5. 还有一部分第三类库:
// Request漏洞示例
String url = request.getParameter("url");
return Request.Get(url).execute().returnContent().toString();//发起请求
// 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); //发起请求
漏洞防御
- 正确处理302跳转(在业务角度看,不能直接禁止302,而是对跳转的地址重新进行检查)
- 限制协议只能为http/https,防止跨协议
- 设置内网ip黑名单(正确判定内网ip、正确获取host)
- 设置常见web端口白名单(防止端口扫描,可能业务受限比较大)
private static int connectTime = 5 * 1000;
public static boolean checkSsrf(String url) {
HttpURLConnection httpURLConnection;
String finalUrl = url;
try {
do {
if(!Pattern.matches("^https?://.*/.*$", finalUrl)) { //只允许http/https协议
return false;
}
if(isInnerIp(url)) { //判断是否为内网ip
return false;
}
httpURLConnection = (HttpURLConnection) new URL(finalUrl).openConnection();
httpURLConnection.setInstanceFollowRedirects(false); //不跟随跳转
httpURLConnection.setUseCaches(false); //不使用缓存
httpURLConnection.setConnectTimeout(connectTime); //设置超时时间
httpURLConnection.connect(); //send dns request
int statusCode = httpURLConnection.getResponseCode();
if (statusCode >= 300 && statusCode <=307 && statusCode != 304 && statusCode != 306) {
String redirectedUrl = httpURLConnection.getHeaderField("Location");
if (null == redirectedUrl)
break;
finalUrl = redirectedUrl; //获取到跳转之后的url,再次进行判断
} else {
break;
}
} while (httpURLConnection.getResponseCode() != HttpURLConnection.HTTP_OK);//如果没有返回200,继续对跳转后的链接进行检查
httpURLConnection.disconnect();
} catch (Exception e) {
return true;
}
return true;
}
private static boolean isInnerIp(String url) throws URISyntaxException, UnknownHostException {
URI uri = new URI(url);
String host = uri.getHost(); //url转host
//这一步会发送dns请求,host转ip,各种进制也会转化为常见的x.x.x.x的格式
InetAddress inetAddress = InetAddress.getByName(host);
String ip = inetAddress.getHostAddress();
String blackSubnetlist[] = {"10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "127.0.0.0/8"}; //内网ip段
for(String subnet : blackSubnetlist) {
SubnetUtils subnetUtils = new SubnetUtils(subnet); //commons-net 3.6
if(subnetUtils.getInfo().isInRange(ip)) {
return true; //如果ip在内网段中,直接返回
}
}
return false;
}