《白帽子讲 Web 安全》之服务端请求伪造(SSRF)深度剖析:从攻击到防御

引言

在当今复杂的网络环境中,Web 应用安全犹如一座时刻需要精心守护的堡垒。随着技术的不断演进,各类安全威胁层出不穷,其中服务端请求伪造(SSRF)正逐渐成为令开发者与安全从业者头疼的一大难题。吴翰清在《白帽子讲 Web 安全》中对 SSRF 进行了深入探讨,接下来让我们详细梳理这一安全隐患的相关知识。

SSRF 攻击简介

定义

服务端请求伪造(Server Side Request Forgery,SSRF) 本质上是一种严重的 Web 应用漏洞。攻击者通过精心构造特定的参数值,诱使 Web 应用将外部输入参数当作 URL 来处理,进而驱使服务端去访问那些服务器程序原本预期之外的 URL。这些输入参数形式多样,既可以是不完整的 URL,也能是单纯的域名、IP 地址或者端口值等。

示例

以提供翻译服务的网站为例,正常情况下用户提交网页 URL 供网站进行翻译操作。但倘若该网站存在 SSRF 漏洞,攻击者便有机可乘。他们提交恶意 URL,网站服务器在毫无察觉的情况下,按照指令访问该恶意 URL,甚至可能因此访问到内网资源 ,导致敏感信息泄露 等严重后果。比如攻击者提交 "http://192.168.1.100/internal_secret_file",若服务器未做有效防范,就会尝试访问内网 IP 对应的敏感文件。

SSRF 漏洞成因

1.用户输入处理不当

许多 Web 应用在处理用户输入时过于草率。当使用用户输入来构造 HTTP 请求的 URL 时,却没有进行全面且严格的验证与过滤。像是未仔细检查 URL 所采用的协议是否合法,是常见的 HTTP/HTTPS,还是潜藏风险的 file、gopher 等协议;对域名的合法性以及端口值是否处于合理范围也未加以核实。以 Python 的 Flask 框架为例,如果代码编写如下:

python 复制代码
from flask import Flask, request

app = Flask(__name__)

@app.route('/fetch')
def fetch():
    url = request.args.get('url')
    # 这里没有对url进行任何验证就直接使用
    response = requests.get(url)
    return response.text

如此一来,用户输入的任何 URL 都可能被服务器访问,极大地增加了 SSRF 风险。

2.使用未经验证的第三方库或框架

在开发过程中,不少开发者依赖各种第三方库或框架来加快开发进度。然而,部分未经充分安全验证的库或框架,在处理 URL 请求环节存在安全漏洞,极易被攻击者利用。例如某些老旧版本的 HTTP 请求库,在处理 URL 重定向等操作时,没有对目标 URL 进行严格检查,攻击者可借此构造恶意请求,突破应用的安全防线。

3.缺乏安全配置

服务器自身的配置 若存在缺陷,同样会为 SSRF 攻击敞开大门。比如服务器未对可访问的 URL 范围做出明确限制,或者未严格检查请求来源,使得攻击者能够轻易绕过安全限制。以 Nginx 服务器为例,如果配置文件中没有设置合理的访问控制规则,像以下错误配置:

javascript 复制代码
server {
    listen 80;
    server_name example.com;
    location / {
        proxy_pass $request_uri;
        # 这里没有对proxy_pass的目标进行任何限制
    }
}

攻击者就可以通过精心构造请求,让服务器访问任意他们指定的 URL。

SSRF 攻击进阶

1.攻击内网应用

攻击者一旦发现 Web 应用存在 SSRF 漏洞 ,便会将目标对准内网应用或服务。通过构造特殊的 URL,诱使服务器去访问内网的 Redis、MySQL 等数据库服务。

例如利用 file 协议,攻击者可以构造类似 "file:///etc/passwd" 的 URL,若服务器权限配置不当,就会读取服务器本地的敏感文件。

利用 gopher 协议攻击 Redis 服务时,攻击者构造的 URL 可以向 Redis 服务器发送恶意指令,如修改数据库内容、获取敏感数据等。假设 Redis 服务在内网 IP 为 192.168.1.101,端口为 6379,攻击者构造的 gopher URL 可能如下:

javascript 复制代码
gopher://192.168.1.101:6379/_*3%0d%0a$3%0d%0aset%0d%0a$1%0d%0a1%0d%0a$4%0d%0amalic%0d%0a

这条 URL 会尝试在 Redis 中设置恶意数据。

2.端口扫描

端口扫描 也是攻击者利用 SSRF 漏洞的常见手段。通过向不同的端口发送请求,并根据响应判断端口是否开放,攻击者能够探测内网的服务和端口信息。攻击者可以编写脚本,利用 SSRF 漏洞构造一系列请求来扫描内网端口。例如使用 Python 编写一个简单的端口扫描脚本,结合 SSRF 漏洞向不同端口发送请求:

python 复制代码
import requests

def scan_port(url, port):
    target_url = f"{url}:{port}"
    try:
        response = requests.get(target_url)
        if response.status_code == 200:
            print(f"端口 {port} 开放")
    except requests.RequestException:
        pass

base_url = "http://vulnerable_server/ssrf_endpoint?url="
for port in range(1, 1025):
    scan_port(base_url, port)

通过这种方式,攻击者可以为后续更深入的攻击做准备。

3.攻击非 Web 应用

SSRF 的攻击范围 并不局限于 Web 应用,基于网络协议的其他服务 ,如FTP、SMTP 等同样可能成为受害者。攻击者可以构造符合相应协议的 URL,借助 SSRF 漏洞与这些服务进行交互并执行恶意操作。例如构造一个针对 FTP 服务的 URL"ftp://attacker_server:21",如果服务器对该 URL 请求未做限制,攻击者可能通过 FTP 服务上传恶意文件、获取敏感信息等。

SSRF 绕过技巧

1.绕过限制访问内网

为突破服务器对URL 的限制 从而访问内网资源 ,攻击者想出了多种特殊方法。DNS 重绑定技术 是其中之一,攻击者通过控制 DNS 服务器,使域名在不同时间解析为不同 IP 地址。先让域名解析为外网可访问的 IP 地址,绕过服务器对访问内网 IP 的限制;在获取服务器连接后,再将域名解析为内网 IP 地址,从而实现对内网资源的访问。另外,攻击者还会使用特殊的 URL 编码和变形方式绕过过滤。例如将 "." 编码为 "%2e",将 "/" 编码为 "%2f" 等,以此绕过服务器对 URL 格式的简单过滤规则。

2.利用协议特性

不同的网络协议 具有各自独特的特性,攻击者善于利用这些特性来绕过安全检测。以 HTTP 协议中的 30x 重定向为例,攻击者可以构造一个包含重定向指令的 URL,将服务器的请求重定向到内网地址。假设攻击者控制了一个恶意网站 "evil.com",他们构造的 URL 可能是 "http://vulnerable_server/ssrf_endpoint?url=http://evil.com/redirect?target=http://192.168.1.100",当服务器访问evil.com时,evil.com返回 302 重定向指令,将服务器请求重定向到内网 IP 地址 192.168.1.100,从而绕过服务器原本对直接访问内网地址的限制。

SSRF 防御方案

1.使用 URL 黑名单或白名单

黑名单

采用黑名单机制 ,即限制访问特定的 URL 。然而,这种方式存在较大局限性,因为难以穷举所有危险的 URL,而且攻击者总能通过各种手段绕过黑名单限制,所以实际安全性较低。例如攻击者可以通过对危险 URL 进行变形、编码等方式,使其不在黑名单范围内。

白名单

相较而言,白名单机制 更为可靠 。它只允许访问特定的、经过严格验证的 URL。在实际应用中,明确指定允许访问的域名或 IP 地址范围是关键。例如在 Java 的 Spring 框架中,可以通过配置文件设置允许访问的 URL 白名单:

javascript 复制代码
security.ssrf.whitelist=example.com,192.168.1.0/24

这样服务器只会访问白名单内的 URL,大大降低了 SSRF 攻击风险。

2.验证输入的 URL

对用户输入的 URL 进行严格验证 是防御 SSRF 的重要一环。需要检查 URL 所采用的协议,只允许安全的协议,如 HTTP 或 HTTPS 协议,禁止 file、gopher 等可能被用于恶意攻击的协议。同时,对域名和 IP 地址的合法性也要进行核实。例如使用 Python 的urllib.parse库验证 URL:

python 复制代码
from urllib.parse import urlparse

def validate_url(url):
    result = urlparse(url)
    if result.scheme not in ['http', 'https']:
        return False
    try:
        # 验证域名或IP地址的合法性
        socket.gethostbyname(result.netloc.split(':')[0])
        return True
    except socket.gaierror:
        return False

3.限制请求的端口和协议

明确限制服务器可以访问的端口和协议 ,是减少攻击面的有效手段。禁止服务器访问不必要的端口,如一些常见的敏感端口,像 Redis 的 6379 端口MySQL 的 3306 端口等。同时,只允许使用必要的协议,避免启用可能被攻击者利用的协议。例如在服务器配置文件中设置只允许 HTTP 和 HTTPS 协议访问特定端口:

javascript 复制代码
server {
    listen 80;
    server_name example.com;
    location / {
        proxy_pass http://backend_server;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        # 限制只允许HTTP和HTTPS协议
        if ($scheme!~ ^(http|https)$) {
            return 403;
        }
        # 限制只允许访问特定端口,如80和443
        if ($server_port!~ ^(80|443)$) {
            return 403;
        }
    }
}

4.使用 DNS 解析控制

对 URL 中的域名进行 DNS 解析,并检查解析后的 IP 地址是否合法,能够有效防止通过 DNS 重绑定等技术绕过限制。在服务器端获取 URL 中的域名后,先进行 DNS 解析,然后对比解析得到的 IP 地址是否在允许范围内。例如在 Node.js 中可以使用dns模块进行 DNS 解析验证:

javascript 复制代码
const dns = require('dns');
const url = require('url');

function validateUrlWithDns(urlStr) {
    const parsedUrl = url.parse(urlStr);
    return new Promise((resolve, reject) => {
        dns.lookup(parsedUrl.hostname, (err, address) => {
            if (err) {
                reject(err);
            } else {
                // 假设允许的IP范围是192.168.1.0/24
                const allowedRange = '192.168.1.0/24';
                if (isInRange(address, allowedRange)) {
                    resolve(true);
                } else {
                    reject(new Error('IP地址不在允许范围内'));
                }
            }
        });
    });
}

function isInRange(ip, range) {
    const [start, end] = range.split('/')[1] === '32'? [ip, ip] : getRange(range);
    return ip >= start && ip <= end;
}

function getRange(cidr) {
    const parts = cidr.split('/');
    const ip = parts[0].split('.').reduce((acc, part) => acc * 256 + parseInt(part, 10), 0);
    const bits = parseInt(parts[1], 10);
    const mask = (0xffffffff << (32 - bits)) >>> 0;
    const start = ip & mask;
    const end = start | ~mask;
    return [start, end];
}

5.记录和监控

记录 所有的请求和响应 ,对防御 SSRF 至关重要。通过详细记录,便于及时发现异常请求。同时,对服务器的网络访问进行实时监控,一旦察觉到可疑的 SSRF 攻击行为,能够迅速采取措施。例如使用日志管理工具,如 ELK(Elasticsearch、Logstash、Kibana)组合,将服务器的请求和响应日志收集到 Elasticsearch 中,通过 Logstash 进行日志处理和过滤,再利用 Kibana 进行可视化展示和分析。一旦发现大量指向内网 IP 的请求或者异常的 URL 请求,安全人员可以及时介入调查并采取相应防御措施。

小结

服务端请求伪造(SSRF) 作为近年来日益猖獗的 Web 安全风险,给 Web 应用的安全带来了巨大挑战。攻击者凭借这一漏洞,能够肆意访问内网资源 、进行端口扫描等恶意操作,严重威胁数据安全与系统稳定。为有效防御 SSRF,我们需综合运用多种策略。

输入验证 层面,严格把关用户输入的 URL,确保其合法性;采用白名单机制,精准限定服务器可访问的 URL 范围;

在网络层面 ,限制请求的协议和端口,减少攻击面;借助DNS 解析 控制,防范 DNS 重绑定等绕过手段;同时,强化监控与日志记录 ,以便及时察觉并处置潜在攻击。只有全方位、多层次地构建防御体系,持续关注安全动态,不断优化防御策略,才能在这场与 SSRF 的对抗中,守护好 Web 应用的安全,为用户营造可靠的网络环境。喜欢就点点赞和关注评论一起进步呗

相关推荐
Apifox6 分钟前
如何在 Apifox 中通过 CLI 运行包含云端数据库连接配置的测试场景
前端·后端·程序员
一张假钞9 分钟前
Firefox默认在新标签页打开收藏栏链接
前端·firefox
高达可以过山车不行9 分钟前
Firefox账号同步书签不一致(火狐浏览器书签同步不一致)
前端·firefox
m0_5937581010 分钟前
firefox 136.0.4版本离线安装MarkDown插件
前端·firefox
网络抓包与爬虫12 分钟前
Wireshark——抓包分析
websocket·网络协议·tcp/ip·http·网络安全·https·udp
掘金一周13 分钟前
金石焕新程 >> 瓜分万元现金大奖征文活动即将回归 | 掘金一周 4.3
前端·人工智能·后端
三翼鸟数字化技术团队31 分钟前
Vue自定义指令最佳实践教程
前端·vue.js
Jasmin Tin Wei1 小时前
蓝桥杯 web 学海无涯(axios、ecahrts)版本二
前端·蓝桥杯
圈圈编码1 小时前
Spring Task 定时任务
java·前端·spring
转转技术团队1 小时前
代码变更暗藏危机?代码影响范围分析为你保驾护航
前端·javascript·node.js