SSRF漏洞
SSRF(Server-side Request Forge, 服务端请求伪造)。 由攻击者构造的攻击链接传给服务端执行造成的漏洞,一般用来在外网探测或攻击内网服务。
利用伪协议读取文件
由于Java没有php的cURL,所以Java SSRF支持的协议,不能像php使用curl -V
查看。
Java网络请求支持的协议可通过下面几种方法检测:
- 代码中遍历协议
- 官方文档中查看
import sun.net.www.protocol
查看
SSRF是由发起网络请求的方法造成。所以先整理Java能发起网络请求的类。
- HttpClient及其派生类Request等
- HttpURLConnection
- URLConnection
- URL
- okhttp
这里只简单分析URLConnection类实现对伪协议的支持。
漏洞代码
@RequestMapping(value = "/urlConnection/vuln", method = {RequestMethod.POST, RequestMethod.GET})
public String URLConnectionVuln(String url) {
return HttpUtils.URLConnection(url);
}
这里调用了工具类HttpUtils中的URLConnecione方法。
public static String URLConnection(String url) {
try {
URL u = new URL(url);
URLConnection urlConnection = u.openConnection();
BufferedReader in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream())); //send request
// BufferedReader in = new BufferedReader(new InputStreamReader(u.openConnection().getInputStream()));
String inputLine;
StringBuilder html = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
html.append(inputLine);
}
in.close();
return html.toString();
} catch (Exception e) {
logger.error(e.getMessage());
return e.getMessage();
}
}
这里调用了URL类中的openConnection方法,继续到接口查看,发现这里调用的handler类中的openConnection方法
点击之后就会跳转到file协议下的Handler.class文件中,这里就是对file协议的处理逻辑
public synchronized URLConnection openConnection(URL var1) throws IOException {
return this.openConnection(var1, (Proxy)null);
}
public synchronized URLConnection openConnection(URL var1, Proxy var2) throws IOException {
String var4 = var1.getFile();
String var5 = var1.getHost();
String var3 = ParseUtil.decode(var4);
var3 = var3.replace('/', '\\');
var3 = var3.replace('|', ':');
if (var5 != null && !var5.equals("") && !var5.equalsIgnoreCase("localhost") && !var5.equals("~")) {
var3 = "\\\\" + var5 + var3;
File var6 = new File(var3);
if (var6.exists()) {
return new UNCFileURLConnection(var1, var6, var3);
} else {
URLConnection var7;
try {
URL var8 = new URL("ftp", var5, var4 + (var1.getRef() == null ? "" : "#" + var1.getRef()));
if (var2 != null) {
var7 = var8.openConnection(var2);
} else {
var7 = var8.openConnection();
}
} catch (IOException var10) {
var7 = null;
}
if (var7 == null) {
throw new IOException("Unable to connect to: " + var1.toExternalForm());
} else {
return var7;
}
}
} else {
return this.createFileURLConnection(var1, new File(var3));
}
}
上面的代码对file协议获的参数进行处理,由于这里不满足条件,没有进入if语句,于是直接进行了返回。
这里调用FileURLConnection的构造函数。
最后urlConnection变量的值就是FileURLConnection对象,接下来会调用这个对象的getInputStream方法返回输入流。
可以看到这里首先调用了该类中的connect()方法。这个方法调用方法BufferedInputStream从文件中读取数据,放到is变量中。
最后使用while循环将读取到的文件流逐行输出。
漏洞修复
@GetMapping("/urlConnection/sec")
public String URLConnectionSec(String url) {
// Decline not http/https protocol
if (!SecurityUtil.isHttp(url)) {
return "[-] SSRF check failed";
}
try {
SecurityUtil.startSSRFHook();
return HttpUtils.URLConnection(url);
} catch (SSRFException | IOException e) {
return e.getMessage();
} finally {
SecurityUtil.stopSSRFHook();
}
}
上面的代码,首先对url进行验证,验证url是否以http和https开头,接着调用钩子函数实现工厂。
具体代码可以看看源码的security模块下的ssrf包。
除此之外,其他类的实现也是编写了安全代码。
例如httpURLConnection类,其他相关类的实现基本类似。
@GetMapping("/HttpURLConnection/sec")
public String httpURLConnection(@RequestParam String url) {
try {
SecurityUtil.startSSRFHook();
return HttpUtils.HTTPURLConnection(url);
} catch (SSRFException | IOException e) {
return e.getMessage();
} finally {
SecurityUtil.stopSSRFHook();
}
}
任意文件下载读取文件
漏洞代码
@GetMapping("/openStream")
public void openStream(@RequestParam String url, HttpServletResponse response) throws IOException {
InputStream inputStream = null;
OutputStream outputStream = null;
try {
String downLoadImgFileName = WebUtils.getNameWithoutExtension(url) + "." + WebUtils.getFileExtension(url);
// download
response.setHeader("content-disposition", "attachment;fileName=" + downLoadImgFileName);
URL u = new URL(url);
int length;
byte[] bytes = new byte[1024];
inputStream = u.openStream(); // send request
outputStream = response.getOutputStream();
while ((length = inputStream.read(bytes)) > 0) {
outputStream.write(bytes, 0, length);
}
} catch (Exception e) {
logger.error(e.toString());
} finally {
if (inputStream != null) {
inputStream.close();
}
if (outputStream != null) {
outputStream.close();
}
}
}
这里访问http://localhost:8080/ssrf/openStream?url=file:/D:/test.txt即可获取D盘下的test.txt文件
URL类会对传入的参数url进行处理,这里没有特殊格式,所以传出的变量url没有改变。同样的这里使用file协议,调用和上面的一样。
最后设置的response作为响应,是客户端下载文件。
漏洞修复
修复方法类似,使用作者定义的钩子即可。
最好是不使用伪协议实现文件下载。