一、原理
网站提供了从其它服务器获取数据的功能,但是没有对目标地址做过滤。
二、危害
-
攻击目标网站的内网系统
- 攻击内网应用如redis(gopher或dict协议配合)mysql、zabbix、smtp
- 读取文件(file协议)
- 遍历端口/IP存活,根据响应长度判断(http、dict)
-
攻击云环境
- 攻击元数据地址
- 攻击Docker Remote API:Docker Remote API是一个取代远程命令行界面(rcli)的REST API,默认开放端口为2375。
- 攻击Kubelet API:在云环境中,可通过Kubelet API查询集群pod和node的信息,也可通过其执行命令
- 越权攻击云平台内其他组件或服务:由于云上各组件相互信任,当云平台内某个组件或服务存在SSRF漏洞时,就可通过此漏洞越权攻击其他组件或者服务
三、类型
-
漏洞分为什么类型,有什么区别
- 可回显:响应中返回结果
- 无回显:不返回任何信息
- 半回显:响应中不反回结果,但会暴露一些数据信息
四、易发场景
-
研发在什么情况易写出这类问题
- 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解析日志,怎么反查漏洞资产?
六、利用方法
七、修复方案
- rasp hook发包方法,对发包方法中dns解析的ip进行检查过滤
- 基于客户端改造,对发包方法中dns解析的ip进行检查过滤
- 隔离代理,通过ACL访问控制策略,让服务只能请求外网
- 域名白名单,限制协议,禁止302
- 过滤返回信息,避免用户根据返回信息来判断服务器的状态
- 检查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
|
-
规则绕过
九、面试问题
ssrf利用过程中如何获取内网ip?
- github搜索
- js中泄露
- 乌云历史漏洞
- 爆破192/172/10网段