本文内容参考自《PHP安全之道》。
0x00 服务器请求伪造 (SSRF: Server-Side Request Forgery)
SSRF漏洞是一种由攻击者利用某服务器请求来获取内网或外网系统资源,从服务器发起请求的一个安全漏洞。由于它是由server端发起的,所以能够请求到与它相连而与外网隔离的内部系统。一般情况下,SSRF攻击的目标是企业的内网系统。
SSRF行程的原因大多是由于服务器提供了从其他服务器应用中获取数据的功能且没有对目标地址进行过滤和限制,比如从指定url地址中获取网页文本内容、加载指定地址的图片、文档等。
SSRF漏洞流程如下:
攻击者构造请求 ==> server端根据攻击者构造的请求对内网server进行请求 ==> 内网server将请求反馈给该server ==> 该server将获取到的内网资源返回给攻击者。
0x01 SSRF漏洞的危害
SSRF漏洞的危害主要是使服务器资源泄露。很多网站给用户提供了将外部url所指向的图片、文件直接保存到当前系统上的功能。一般来说,如果该url无效系统会忽略或者返回错误。
攻击者可以使用一些不常见但有效的url, 比如:
http://127.0.0.1:81/dir/images/
http://127.0.0.1:8080/dir/images/
http://127.0.0.1:8888/dir/images/
http://127.0.0.1:22/dir/images/
http://127.0.0.1:3306/dir/images/
然后根据server端返回的信息来判断端口是否开放。大部分应用不会去判断端口,只要是有效的url就会去请求。而大部分的TCP服务会在建立连接时发送banner信息(欢迎语,在banner信息中可以得到软件开发商、软件名称、版本、服务类型等信息)。banner信息是使用ASICC编码的, 能做作为原始的html数据展示。当然,服务端在处理返回信息的时候一般不会直接展示,但是不同的错误代码,返回信息的长度以及返回时间都可以作为依据来判断远程服务器的端口状态。
以下是一段未经过安全编码的代码,很可能被攻击者利用:
if(isset($_GET['url'])){
$url = $_GET['url'];
$file_name = '/tmp/'.date('YmdHis').'_'.rand().'.txt';
$ch = curl_init();
$time_out = 5;
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $time_out);
$content = curl_exec($ch);
$fp_in = fopen($file_name, 'w');
fwrite($fp_in, $content);
fclose($fp_in);
$fp_out = fopen($file_name, 'r');
$result = fread($fp_out, filesize($file_name));
fclose($fp_out);
echo $result;
}else{
echo '请输入url';
}
由于不安全编码很容易被攻击者利用对内网web系统进行指纹识别, 识别内网系统所使用的框架、平台、模块。通过对对应用框架的指纹(独特的文件和目录)可以识别出应用的类型甚至版本。
0x02 在PHP中容易引起SSRF的函数
PHP中用来获取远程或本地诶荣的函数, 比如
file_get_contents
,
fsocketopen
,
curl
等容易造成漏洞。
$content = file_get_contents($_GET['url']);
echo $content;
上面的代码中, 因为未安全的使用file_get_contents, 如果攻击者把'/etc/password' 放在参数url中, 则会直接展示该server的用户列表。
0x03 容易造成SSRF的场景
- 页面分享
- 翻译服务
- 图片加载与下载(包括自动下载外部图片)
- 图片、文章的收藏功能
0X04 SSRF漏洞的防御
合理控制访问权限,尽可能地控制PHP的访问权限,防止穿透到内网以及访问非授权的资源。
1. 无特殊需求, 不接收用户提交的url, 防止攻击者构建url进行穿透访问
2. 在没有对服务器本身we你按访问需求的情况下,建议开启PHP的 open_basedir 配置, 限制访问的目录.
open_basedir = /home/www/sites/
或者在init_set()中设置:
ini_set('open_basedir', '/home/www/sites/');
开启open_basedir可以有效的防止 file_get_contents、curl、fsocketopen 等函数对服务器敏感文件的访问。
3.如果必须接受用户传递的url,使用白名单 + 黑名单机制。
比如, 只允许用户请求特定的url,仅允许http、https请求,限制请求的host(屏蔽内网), 限制请求的端口,限制可访问的文件类型(如只允许访问html/图片等静态文件),统一返回错误信息(避免将请求的具体的错误信息返回给用户)。
使用parse_url来获取url的各个部分来进行url的判断, 使用pathinfo来判断文件类型
要屏蔽内网的ip(127., 172., 192., 10.)和localhost作为host的url
$arr_scheme_white_list = ['http', 'https']; //protocol限制
$arr_host_white_list = ['www.example.com']; //host白名单限制可访问的外网
$arr_host_black_list = ['127.', '172.', '192.', '10.', 'localhost']; //host黑名单限制访问内网
$arr_port_white_list = ['80', '443']; //端口限制
$arr_type_white_list = ['html', 'gif', 'jpg', 'jpeg', 'png']; //文件格式限制
$url = $_GET['url'];
$arr_url_info = parse_url($url);
$host = strtolower($arr_url_info['host']) ?? '';
$scheme = strtolower($arr_url_info['scheme'] ?? '');
$port = $arr_url_info['port'] ?? '';
//scheme/protocol限制
if(!in_array($scheme, $arr_scheme_white_list, true)){
die('访问地址错误');
}
//host限制
if(!in_array($host, $arr_host_white_list, true)){
die('访问地址错误');
}
foreach($arr_host_black_list AS $black_host){
if(strpos($host, $black_host) === 0){
die('访问地址错误!');
}
}
//端口限制
if(!in_array($port, $arr_port_white_list, true)){
die('访问地址错误');
}
//文件类型限制
$type = pathinfo($url, $arr_type_white_list);
if(!in_array($type, $arr_type_white_list, true)){
die('访问地址错误');
}
//获取文件内容
$file_info = file_get_contents($url);
if(empty($file_info)){
die('访问失败');
}else{
echo $file_info;
}
版权归原作者 aben_sky 所有, 如有侵权,请联系我们删除。