从1开始的Java代码审计·第四弹·SSRF

最近的事情真的太多了 ≧﹏≦ ,趁着刚搞完小组的面试,发篇热乎的。

简介

自从 2016 年猪猪侠在乌云峰会发表议题——《Build Your SSRF Exploit Framework》SSRF 才开始火起来的吧,攻击面越来越广,利用某些奇技淫巧可以直接GETSHELL。

SSRF 的介绍、利用方式,PDF里都有讲,就不再赘述了,开始进入正题。

支持的协议

Java 支持的协议可以在 sum.net.www.protocol 包下看到,如下图:

java-protocol

图中用的 JDK 是 1.7,可以看到有 gopher file ftp http https jar mailto netdoc 八种协议。其中最有意思的是 gopher 协议,可以用来构造其它协议的请求。但是,gopherJDK8 中已经被移除。 经过测试,在高版本的 JDK7 里,虽然 sun.net.www.protoocol 中还有 gopher 包,但是实际也已经不能使用,会抛 java.net.MalformedURLException: unknown protocol: gopher 的异常,被阉割地时间在2012年左右(From @K0rz3n)。

发起请求的流程

构造一个简单的 http 请求:

1
2
3
4
5
6
7
8
9
try {
URL u = new URL(url);
URLConnection urlConnection = u.openConnection();
InputStream inputStream = urlConnection.getInputStream();

//...
} catch (IOException e) {
e.printStackTrace();
}

简述下这个过程,首先,构造一个 URL 对象,调用 urlopenConnection() 方法来获取一个 URLConnection 实例,然后再调 getInputStream() 拿到 InputStream,也就是请求的响应流。之后,再做我们想要的其它事情。

在这个过程中,如果 URL 是可控的,那么就会存在 SSRF 漏洞。

下面分析下主要的几个方法。

new URL()

构造一个 URL 对象,构造时,可以指定 协议HOST端口文件路径URLStreamHandler

其中,URLStreamHandler 是一个抽象类,每个协议都有继承它的子类 —— Handler(可以在各协议的包下找到)。Handler 定义了该如何去打开一个连接( openConnection() )。

如果是直接传入一个 URL 字符串,会在构造对象时,根据 protocol 自动创建对应的 Handler 对象。

openConnection()

每次调用 openConnection() 时,都会创造一个新的实例,也就是 URLConnection。但是,在实例创建时,真实的网络连接实际上并没有建立。只有在调用 URLConnection.connect() 方法后才会建立连接。

getInputStream()

从打开的连接获取一个 InputStream,可以从中得到 URL 请求的响应流。在调用这个方法时,会自动调用 URLConnection.connect() 方法,也就是建立连接。所以一旦调用 getInputStream() 连接就已经建立好了,不管后续做什么操作,这个 URL 请求都已经发出去了。

另一种写法

1
2
3
4
5
6
7
URL u = new URL(url);
HttpURLConnection con = (HttpURLConnection) u.openConnection();
con.setRequestMethod("GET");
con.setRequestProperty("User-Agent", "Mozilla/5.0");
con.getInputStream();

//...

假设这里的 URL 使用户可控的,这段代码相对于上一段来说,会稍微 “安全” 些。如果攻击者想要使用 gopher 协议攻击内网服务,在第 2 行时,会由于类型强制转换失败而抛出异常。在异常抛出前,一直没有调用到 connect() 方法,所以请求并没有发出去。

2 行的类型转换相当于限定了协议,在能够明确使用的协议的情况下,建议用这种方式对协议做限制。

其它发起请求的例子

javax.imageio.ImageIO

这是 JDK 自带的类,它的 read() 方法,用来加载图片。它可以传入一个 URL 对象,且没有协议限制,如下:

ImageIO.java:1386

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static BufferedImage read(URL input) throws IOException {
if (input == null) {
throw new IllegalArgumentException("input == null!");
}

InputStream istream = null;
try {
istream = input.openStream();
} catch (IOException e) {
throw new IIOException("Can't get input stream from URL!", e);
}
ImageInputStream stream = createImageInputStream(istream);
BufferedImage bi;
try {
bi = read(stream);
if (bi == null) {
stream.close();
}
} finally {
istream.close();
}
return bi;
}

如果服务器在加载图片时,URL 是用户可控的,那么就会存在 SSRF 漏洞。

HttpClient

一个 get 请求的例子:

1
2
3
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpGet getRequest = new HttpGet(url);
HttpResponse response = httpClient.execute(getRequest);

问题同上。

其它

  • okhttp

  • Request

容易出现SSRF的功能点

只要是能够对外发起网络请求的地方,就有可能会出现SSRF漏洞。

  • 从指定url获取内容

  • 数据源连接

  • 后台状态刷新

  • webmail (POP3/SMTP/IMAP)

  • 文件处理 (加载图片/XML/PDF/ffpmg/ImageMagic)

修复策略

  • 避免 url 用户可控,包括 path

  • 统一请求响应及错误信息

  • 白名单校验url及ip

  • 限制协议及端口

  • TTL 设置为 0,防止 DNS Rebinding 攻击(Java默认为 0)

可以参考 @JoyChou 师傅的修复方案

相关文章

文章目录
  1. 1. 简介
  2. 2. 支持的协议
  3. 3. 发起请求的流程
    1. 3.1. new URL()
    2. 3.2. openConnection()
    3. 3.3. getInputStream()
    4. 3.4. 另一种写法
  4. 4. 其它发起请求的例子
    1. 4.1. javax.imageio.ImageIO
    2. 4.2. HttpClient
    3. 4.3. 其它
  5. 5. 容易出现SSRF的功能点
  6. 6. 修复策略
  7. 7. 相关文章
|