URLConnection

在java中,Java抽象出来了一个URLConnection类,它用来表示应用程序以及与URL建立通信连接的所有类的超类,通过URL类中的openConnection方法获取到URLConnection的类对象。

Java中URLConnection支持的协议可以在sun.net.www.protocol看到。

protocol.jpg

由上图可以看到,支持的协议有以下几个(当前jdk版本:1.7.0_80):

  1. file ftp mailto http https jar netdoc gopher

虽然看到有gopher,但是gopher实际在jdk8版本以后被阉割了,jdk7高版本虽然存在,但是需要设置具体可以看 https://bugzilla.redhat.com/show_bug.cgi?id=865541以及http://hg.openjdk.java.net/jdk7u/jdk7u/jdk/rev/8067bdeb4e31 其中每个协议都有一个Handle,Handle定义了这个协议如何去打开一个连接。

我们来使用URL发起一个简单的请求

  1. public class URLConnectionDemo {
  2. public static void main(String[] args) throws IOException {
  3. URL url = new URL("https://www.baidu.com");
  4. // 打开和url之间的连接
  5. URLConnection connection = url.openConnection();
  6. // 设置请求参数
  7. connection.setRequestProperty("user-agent", "javasec");
  8. connection.setConnectTimeout(1000);
  9. connection.setReadTimeout(1000);
  10. ...
  11. // 建立实际连接
  12. connection.connect();
  13. // 获取响应头字段信息列表
  14. connection.getHeaderFields();
  15. // 获取URL响应
  16. connection.getInputStream();
  17. StringBuilder response = new StringBuilder();
  18. BufferedReader in = new BufferedReader(
  19. new InputStreamReader(connection.getInputStream()));
  20. String line;
  21. while ((line = in.readLine()) != null) {
  22. response.append("/n").append(line);
  23. }
  24. System.out.print(response.toString());
  25. }
  26. }

大概描述一下这个过程,首先使用URL建立一个对象,调用url对象中的openConnection来获取一个URLConnection的实例,然后通过在URLConnection设置各种请求参数以及一些配置,在使用其中的connect方法来发起请求,然后在调用getInputStream来获请求的响应流。 这是一个基本的请求到响应的过程。

SSRF

SSRF(Server-side Request Forge, 服务端请求伪造)。 由攻击者构造的攻击链接传给服务端执行造成的漏洞,一般用来在外网探测或攻击内网服务。

SSRF漏洞形成的原因大部分是因为服务端提供了可以从其他服务器获取资源的功能,然而并没有对用户的输入以及发起请求的url进行过滤&限制,从而导致了ssrf的漏洞。

通常ssrf容易出现的功能点如下面几种场景

  • 抓取用户输入图片的地址并且本地化存储
  • 从远程服务器请求资源
  • 对外发起网络请求
  • ……

黑客在使用ssrf漏洞的时候,大部分是用来读取文件内容或者对内网服务端口探测,或者在域环境情况下且是win主机下进行ntlmrelay攻击。

  1. URL url = new URL(url);
  2. URLConnection connection = url.openConnection();
  3. connection.connect();
  4. connection.getInputStream();
  5. StringBuilder response = new StringBuilder();
  6. BufferedReader in = new BufferedReader(
  7. new InputStreamReader(connection.getInputStream()));
  8. String line;
  9. while ((line = in.readLine()) != null) {
  10. response.append("/n").append(line);
  11. }
  12. System.out.print(response.toString());

比如上面代码中的url可控,那么将url参数传入为file:///etc/passwd

  1. URL url = new URL("file:///etc/passwd");
  2. URLConnection connection = url.openConnection();
  3. connection.connect();
  4. ...

以上代码运行以后则会读取本地/etc/passwd文件的内容。

file_read_passwd.jpg

但是如果上述代码中将url.openConnection()返回的对象强转为HttpURLConnection,则会抛出如下异常

  1. Exception in thread "main" java.lang.ClassCastException: sun.net.www.protocol.file.FileURLConnection cannot be cast to java.net.HttpURLConnection

由此看来,ssrf漏洞也对使用不同类发起的url请求也是有所区别的,如果是URLConnection|URL发起的请求,那么对于上文中所提到的所有protocol都支持,但是如果经过二次包装或者其他的一些类发出的请求,比如

  1. HttpURLConnection
  2. HttpClient
  3. Request
  4. okhttp
  5. ……

那么只支持发起http|https协议,否则会抛出异常。

如果传入的是http://192.168.xx.xx:80,且192.168.xx.xx80端口存在的,则会将其网页源码输出出来

url_connection_get_web_port.jpg

但如果是非web端口的服务,则会爆出Invalid Http responseConnection reset异常。如果能将此异常抛出来,那么就可以对内网所有服务端口进行探测。

java中默认对(http|https)做了一些事情,比如:

  • 默认启用了透明NTLM认证
  • 默认跟随跳转

关于NTLM认证的过程这边不在复述,大家可以看该文章《Ghidra 从 XXE 到 RCE》 默认跟随跳转这其中有一个坑点,就是

follow_redirect.jpg

它会对跟随跳转的url进行协议判断,所以Java的SSRF漏洞利用方式整体比较有限。

  • 利用file协议读取文件内容(仅限使用URLConnection|URL发起的请求)
  • 利用http 进行内网web服务端口探测
  • 利用http 进行内网非web服务端口探测(如果将异常抛出来的情况下)
  • 利用http进行ntlmrelay攻击(仅限HttpURLConnection或者二次包装HttpURLConnection并未复写AuthenticationInfo方法的对象)

对于防御ssrf漏洞的攻击,不单单要对传入的协议进行判断过滤,也要对其中访问的地址进行限制过滤。