SSRF服务器请求伪造
一、概述
1.定义
- 攻击者通过伪造服务器发送对其他内网服务器的请求,来访问本不应由外网访问到的内部资源。
- 这种漏洞一般出现在由用户提供地址,服务器主动请求该地址的功能上,如解析外部图片地址,通过url下载外部文件。
- 当攻击者找到可利用的ssrf漏洞或者获得服务器一定权限的时候可以通过该服务器发送内网请求,扫描内网服务及端口,达到内网探针的作用。
- 攻击者可以将具有ssrf漏洞的服务器当作跳板,通过构造请求来攻击其他服务器,从而隐藏自己的IP来增加溯源难度。
-
漏洞成因,以PHP代码为例
<?php 
 // 用户输入的URL参数 
 $url = $_GET['url']; 
 
 // 使用file_get_contents函数向用户指定的URL发起请求 
 $response = file_get_contents($url); 
 
 // 输出请求结果 
 echo $response; 
 ?> 
 
 //或者 
 
 <?php 
 // 接收用户通过POST请求提交的'url'参数 
 $_POST['url']; 
 
 // 初始化cURL会话 
 $ch = curl_init(); 
 
 // 设置cURL的请求URL为用户提供的'url'参数 
 // 这里的漏洞是,用户可以控制这个URL,因此攻击者可以提供任意URL或IP 
 curl_setopt($ch, CURLOPT_URL, $_POST['url']); 
 
 // 禁止输出响应头部(但仍然输出响应体) 
 curl_setopt($ch, CURLOPT_HEADER, false); 
 
 // 执行cURL请求,这里没有对返回的响应内容做处理,直接执行并忽略结果 
 curl_exec($ch); 
 
 // 关闭cURL会话 
 curl_close($ch); 
 ?>
2.与csrf区别
- csrf是利用服务器对用户的信任来实现的,让用户在未察觉的情况下完成一些未授权操作,其攻击的目标是用户。
- ssrf是利用服务对用户输入验证不严,服务器可以访问内网资源来攻击服务器,从而实现对内网资源的未授权访问。
二、利用
1.协议
-
http:
http://192.168.101.1/phpmyadmin/
-
file:
file:///etc/passwd
-
dict:
dict://192.168.101.1:3306/info
字典查询协议,设计用于客户端查询字典服务器以获取定义和词语信息,它是基于文本的协议,通常在TCP的2628端口运行。
-
ftp:
ftp://192.168.101.1:21
-
sftp:
sftp://user:[email protected]:22/file.txt
-
gopher:
gopher://127.0.0.1:3306/_GET%XXXXXX
gopher是一个在http诞生以前在访问网络资源的协议,它可以将多个数据包整合发送。对于不使用http协议的内网服务器,可以使用gopher构造自定义TCP请求, 对内网的数据库、SMTP服务、FTP服务发送请求。可以使用Gopherus工具来构造url请求。
2.无回显
如果有回显直接构造发包再看回包就好了,这里不再赘述
- 如果ssrf漏洞无回显,则使用上述协议利用存在困难,因为无法确定请求中携带的代码或命令是否被执行。
- 根据响应时间进行大致判断,如果执行成功一般返回的比较快,没有则返回较慢,但这也和网络环境有关,如果本身正常访问该站点就很慢那就很难判断。
- 如果服务器可以出网,可以构造对dnslog网站或者攻击者自己的服务器某端口的http请求让服务器访问,然后查看访问记录判断是否存在漏洞。
- 如果已经明确存在漏洞但是没有回显,可以构造将执行结果写到前端目录中的文件中,然后通过访问该文件查看执行结果
三、防御
1.过滤输入
-
验证URL格式:只允许合法的URL格式。可以使用PHP的
filter_var()
函数来验证用户输入的URL,确保它符合预期。 -
限制协议类型:禁止非HTTP/HTTPS协议(如
file://
、ftp://
、gopher://
等)。确保服务器只允许通过HTTP或HTTPS协议进行请求。
// 验证URL是否为有效的URL格式 
if (!filter_var($_POST['url'], FILTER_VALIDATE_URL)) { 
    die('Invalid URL'); 
} 
 
// 限制URL协议为HTTP或HTTPS 
$url = $_POST['url']; 
if (strpos($url, 'http://') !== 0 && strpos($url, 'https://') !== 0) { 
    die('Only HTTP and HTTPS protocols are allowed'); 
}
2.限制访问范围
- 使用白名单:通过白名单的方式,限制可以请求的外部域名或IP范围。白名单应该包含只允许访问的合法目标地址,如信任的API服务器或特定的服务。
-
禁止访问内网资源:阻止通过SSRF请求访问本地地址(如
127.0.0.1
、localhost
)或内部网络中的资源。可以通过检测目标IP地址来判断请求是否指向内网资源。 - DNS解析:攻击者可以通过SSRF向外部DNS发起请求,因此可以通过解析请求的域名并与实际目标IP进行对比,避免DNS欺骗攻击。
-
IP地址检查:解析URL后,检查目标IP是否在受信任的范围内,避免攻击者通过内网IP(如
127.0.0.1
、10.x.x.x
等)发起请求。 - 限制请求端口:只允许访问80、443端口的url
// 检查URL中的主机名是否属于白名单 
$allowed_hosts = ['example.com', 'trusted-api.com']; 
$parsed_url = parse_url($_POST['url']); 
if (!in_array($parsed_url['host'], $allowed_hosts)) { 
    die('URL not allowed'); 
} 
 
 
 
$url = $_POST['url']; 
$parsed_url = parse_url($url); 
$ip = gethostbyname($parsed_url['host']);  // 解析域名到IP 
 
// 检查目标IP是否在受信任的范围内 
// FILTER_VALIDATE_IP检查IP地址是否有效 
// FILTER_FLAG_NO_PRIV_RANGE是标志,禁止IP地址属于局域网IP 
// FILTER_FLAG_NO_RES_RANGE标志禁止IP地址为保留地址,如全零、回环、广播、多播等 
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) { 
    // 允许请求 
} else { 
    die('IP is within a restricted range'); 
} 
 
 
 
$port = isset($parsed_url['port']) ? $parsed_url['port'] : 80; // 默认为80(HTTP) 
 
// 限制允许的端口范围(例如只允许HTTP/HTTPS的80和443端口) 
if ($port !== 80 && $port !== 443) { 
    die('Access to this port is not allowed'); 
} 

3.防火墙和网络隔离
- 内网和外网隔离:确保应用服务器与敏感的内部服务(如数据库、文件服务器、内部API)之间的网络是隔离的。即使应用受到SSRF攻击,也无法直接访问内网资源。
- 防火墙和网络策略:使用防火墙和网络策略来限制来自应用服务器的出站请求。可以通过白名单机制限制允许访问的外部资源。