目录

SSRF服务端请求伪造 - 安全宏观101

一、原理

网站提供了从其它服务器获取数据的功能,但是没有对目标地址做过滤。

二、危害

  1. 攻击目标网站的内网系统

    1. 攻击内网应用如redis(gopher或dict协议配合)mysql、zabbix、smtp
    2. 读取文件(file协议)
    3. 遍历端口/IP存活,根据响应长度判断(http、dict)
  2. 攻击云环境

    1. 攻击元数据地址
    2. 攻击Docker Remote API:Docker Remote API是一个取代远程命令行界面(rcli)的REST API,默认开放端口为2375。
    3. 攻击Kubelet API:在云环境中,可通过Kubelet API查询集群pod和node的信息,也可通过其执行命令
    4. 越权攻击云平台内其他组件或服务:由于云上各组件相互信任,当云平台内某个组件或服务存在SSRF漏洞时,就可通过此漏洞越权攻击其他组件或者服务

三、类型

  1. 漏洞分为什么类型,有什么区别

    1. 可回显:响应中返回结果
    2. 无回显:不返回任何信息
    3. 半回显:响应中不反回结果,但会暴露一些数据信息

四、易发场景

  • 研发在什么情况易写出这类问题

    • C端:url文件上传、OCR服务、docs粘贴外部资源
    • B端:自定义(流程编排、工具、回调地址)、大模型
    • 服务:sentry的配置“Allow Javascript Source fetching”默认打开,sentry在处理请求中的js地址时,会对地址发起请求,造成ssrf
    • 协议:rmi、rpc

五、发现方法

  • 黑盒规则

    • 一切传输url的位置都有可能存在

      • 爆破参数:share、wap、url、link、src、source、target、u、3g、display、sourceURl、imageURL、domain
  • 白盒规则

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//javax.imageio.ImageIO
"read"
//okhttp3.OkHttpClient
"newCall"
//org.apache.http.client.methods
"HttpGet"
"HttpPost"
"HttpPut"
"HttpDelet"
//org.apache.commons.httpclient.methods
"PostMethod"
"GetMethod"
"PutMethod"
"DeleteMethod"
//org.springframework.web.client.RestTemplate
"getForEntity"
"getForObject"
"postForLocation"
"postForObject"
"exchange"
"execute"
//java.net.URL
"openStream"
"openConnection"
//java.net.HttpURLConnection
"connect"
"getInputStream"
"getOutputStream"
//org.springframework.http.client.ClientHttpRequest
"execute"
//javax.net.ssl.HttpsURLConnection
"connect"
"getInputStream"
//java.net.URLConnection
"openConnection"
//org.apache.http.impl.client.CloseableHttpClient
"execute"
//org.apache.http.client.methods.CloseableHttpResponse
"execute"

java.net.URL,如下webgoat靶场SSRF中的代码,使用URL类中openStream()打开远程链接的数据流:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;

public class Ssrf {
    public static void main(String[] args) throws IOException {

        try {
            InputStream in = new URL("https://www.baidu.com").openStream(); // 转成InputStream
            BufferedReader reader = new BufferedReader(new InputStreamReader(in)); // 转成BufferedReader
            String line;
            while ((line = reader.readLine()) != null) { // 读取readLine
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

java.net.URLConnection,URL类的openConnection方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;

public class Ssrf {
    public static void main(String[] args) throws IOException {
        String url = "https://www.baidu.com";
        URLConnection urlConnection = new URL(url).openConnection();
        InputStream inputStream = urlConnection.getInputStream(); // 转成InputStream
        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); // 转成BufferedReader
        String line;
        while ((line = reader.readLine()) != null) { // 读取readLine
            System.out.println(line);
        }
    }
}

java.net.HttpURLConnection

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.HttpURLConnection;

public class Ssrf {
    public static void main(String[] args) throws IOException {
        String url = "https://www.baidu.com";
        HttpURLConnection con = (HttpURLConnection) new URL(url).openConnection();
        InputStream inputStream = con.getInputStream(); // 转成InputStream
        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); // 转成BufferedReader
        String line;
        while ((line = reader.readLine()) != null) { // 读取readLine
            System.out.println(line);
        }
    }
}

java.net.http,在JDK11后开始自带,由JDK9的jdk.incubator.http迁移而来:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class Ssrf {
    public static void main(String[] args) {
        String url = "https://www.baidu.com";
        HttpClient client = HttpClient.newHttpClient();
        HttpRequest request = HttpRequest.newBuilder()
                .uri(java.net.URI.create(url))
                .build();
        client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
                .thenApply(HttpResponse::body)
                .thenAccept(System.out::println)
                .join();
    }
}

Apache HttpComponents

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;

import java.io.BufferedReader;
import java.io.InputStreamReader;

public class Ssrf {
    public static void main(String[] args) throws Exception {
        String url = "https://www.baidu.com/";
        String requestBody = "{ \"key\": \"value\" }";

        try (CloseableHttpClient httpclient = HttpClients.createDefault()) {
            HttpPost httpPost = new HttpPost(url);
            httpPost.setEntity(new StringEntity(requestBody));

            HttpResponse response = httpclient.execute(httpPost);
            BufferedReader reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));

            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        }
    }
}

okhttp,Java 的 HTTP+SPDY 客户端开发包,同时也支持 Android,由Square 公司开源贡献。示例代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

import java.io.IOException;

public class Ssrf {
    public static void main(String[] args) throws IOException {
        String url = "https://www.baidu.com";

        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
                .url(url)
                .build();

        try (Response response = client.newCall(request).execute()) {
            if (!response.isSuccessful()) {
                throw new IOException("Unexpected response code: " + response);
            }

            String responseBody = response.body().string();
            System.out.println(responseBody);
        }
    }
}

Retrofit,Retrofit 是 Square 公司出品的默认基于 OkHttp 封装的一套 RESTful 网络请求框架,适用于 Android 和 Java 的类型安全HTTP 客户端,示例代码:

1
2
3
4
5
6
Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com/")
    .addConverterFactory(GsonConverterFactory.create())
    .build();

GitHubService service = retrofit.create(GitHubService.class);

RestTemplate,RestTemplate是Spring用于同步客户端HTTP访问的中心类,遵循RESTful规范,简化了与 HTTP 服务器的通信。

1
2
RestTemplate restTemplate = new RestTemplate();
        ResponseBean responseBean = restTemplate.postForObject(url, requestBean, ResponseBean.class);

OpenFeign,OpenFeign是一个声明式WebService客户端,其工作原理是将注释处理成模板化的请求,通过占位符{id}来简化API的处理,示例代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
interface Bank {
  @RequestLine("POST /account/{id}")
  Account getAccountInfo(@Param("id") String id);
}

public class BankService {
  public static void main(String[] args) {
    Bank bank = Feign.builder()
        .decoder(new AccountDecoder())
        .options(new Request.Options(10, TimeUnit.SECONDS, 60, TimeUnit.SECONDS, true))
        .target(Bank.class, "https://api.examplebank.com");
  }
}
  • 灰盒规则

    • hook发送dns请求的方法,在上面进行插桩,如果重放的内网地址进入了插桩的方法,就报漏洞
  • 监控规则

    • 监控越底层越有效,如果在waf监控会存在大量误报
    • 如果监控dns解析日志,怎么反查漏洞资产?

六、利用方法

  • php的网站(使用curl扩展去发起请求,支持的协议较多)

    • file协议:读取敏感文件

    • http、ftp协议:根据响应时间进行内网端口扫描;

    • 使用dict、gopher协议

      • gopher协议攻击内网中存在未授权访问漏洞的redis;
      • gopher协议攻击内网中存在无密码认证的mysql;
  • java的网站(从jdk1.7开始移除了对gopher协议的支持,jdk9中移除了协议netdoc,所以Java SSRF的利用方式比较局限)

    • file协议:任意文件读取
    • http协议:内网端口扫描,极端情况下也可以使用http协议攻击内网的struts2、thinkphp
    • jar协议:读取本地或远程压缩包中的文件
  • http协议的特殊使用场景

    • weblogic和python中的urllib库存在crlf注入漏洞,支持通过“\r\n”的方式来注入换行符,而redis是通过换行符来分隔每条命令,所以使用http协议就可以完成对redis未授权访问的攻击。
  • dns重绑定攻击

    • 修复ssrf时通常需要发起两次dns解析:

      • 第一次:取host的ip,检查是否为内网ip
      • 第二次:如果不是内网ip,则向host发起请求
      • 这两次DNS解析是有时间差的,通过将dns中的ttl设置为0,我们可以实现第一次解析时获得外网ip,第二次解析时获得内网ip,从而绕过限制。
      • 需要注意的是对于Java的ssrf,Java发起请求时默认TTL为30,所以在进行DNS重绑定时,需要在接近30秒时用条件竞争发起多次请求才可以成功。( Java 8及以下版本,默认的DNS缓存时间是5秒、Java 9及以上版本,默认的DNS缓存时间是30秒)
      • mailto:发送邮件,需要后端配置了邮件的发送服务器和相关的支持才能正常使用

七、修复方案

  1. rasp hook发包方法,对发包方法中dns解析的ip进行检查过滤
  2. 基于客户端改造,对发包方法中dns解析的ip进行检查过滤
  3. 隔离代理,通过ACL访问控制策略,让服务只能请求外网
  4. 域名白名单,限制协议,禁止302
  5. 过滤返回信息,避免用户根据返回信息来判断服务器的状态
  6. 检查host的ip时将公网ip记录下来,后面的请求绑定此ip

八、绕过方法

  • 将ip地址进行进制转换

    • 十进制,[http://127.0.0.1/%20=%20http://127.0.0.1](http://127.0.0.1/ = http://127.0.0.1)
  • dns解析

    • 利用dns解析,在域名上设置A记录,指向127.0.0.1
  • url跳转

    • 使用短url地址
    • 返回包添加location跳转,绕过协议限制
  • 本地回环地址

  •  1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    http://127.0.0.1
    http://localhost
    http://127.255.255.254
    127.0.0.1 - 127.255.255.254
    http://[::1]
    http://[::ffff:7f00:1]
    http://[::ffff:127.0.0.1]
    http://127.1
    http://127.0.1
    http://0:80
    
  • 规则绕过

九、面试问题

  • 怎么查看Java支持哪些协议

    • 从sun.net.www.protocol
    • Java11支持 file ftp http jar mailto
  • SSRF如何探测非HTTP协议

    • 用dict协议探测
  • 限制了只允许使用http协议,怎么绕?

    • 302绕过
  • 普通ssrf不能攻击redis,攻击redis的途径

    • SSRF遇到CRLF,可以实现换行,对redis的攻击

      • weblogic自己实现了一套socket方法,将用户传入的url直接带入到socket接口,并且没有检查url中的crlf
    • dict、gopher

ssrf利用过程中如何获取内网ip?

  • github搜索
  • js中泄露
  • 乌云历史漏洞
  • 爆破192/172/10网段