Skip to content

Server-Side Request Forgery Prevention Cheat Sheet / 服务器端请求伪造防护速查表

Introduction / 介绍

The objective of the cheat sheet is to provide advice regarding the protection against Server Side Request Forgery (SSRF) attack.

速查表的目的是提供关于防范服务器端请求伪造 (SSRF) 攻击的建议。

This cheat sheet will focus on the defensive point of view and will not explain how to perform this attack. This talk from the security researcher Orange Tsai as well as this document provide techniques on how to perform this kind of attack.

本速查表将侧重于防御角度,不会解释如何执行此类攻击。安全研究员Orange Tsai 的以下演讲以及此文档提供了执行此类攻击的技术。

Context / 上下文

SSRF is an attack vector that abuses an application to interact with the internal/external network or the machine itself. One of the enablers for this vector is the mishandling of URLs, as showcased in the following examples:

SSRF 是一种攻击向量,滥用应用程序与内部/外部网络或机器本身进行交互。此向量的一种启用方式是对 URL 的不当处理,以下是一些示例:

  • Image on an external server (e.g. user enters image URL of their avatar for the application to download and use).
    中文:外部服务器上的图片(例如,用户输入头像的图片 URL 以供应用程序下载和使用)。
  • Custom WebHook (users have to specify Webhook handlers or Callback URLs).
    中文:自定义 WebHook(用户必须指定 Webhook 处理程序或回调 URL)。
  • Internal requests to interact with another service to serve a specific functionality. Most of the times, user data is sent along to be processed, and if poorly handled, can perform specific injection attacks.
    中文:内部请求以与另一个服务交互以提供特定功能。大多数情况下,用户数据会随请求一起发送以进行处理,如果处理不当,可能会执行特定的注入攻击。

Overview of a SSRF common flow / SSRF 常见流程概述

SSRF Common Flow

SSRF 常见流程

Notes:

注意:

  • SSRF is not limited to the HTTP protocol. Generally, the first request is HTTP, but in cases where the application itself performs the second request, it could use different protocols (e.g. FTP, SMB, SMTP, etc.) and schemes (e.g. file://, phar://, gopher://, data://, dict://, etc.).
    中文:SSRF 不仅限于 HTTP 协议。通常,第一次请求是 HTTP,但在应用程序本身执行第二次请求的情况下,它可能使用不同的协议(例如 FTP、SMB、SMTP 等)和方案(例如 file://、phar://、gopher://、data://、dict:// 等)。
  • If the application is vulnerable to XML eXternal Entity (XXE) injection then it can be exploited to perform a SSRF attack, take a look at the XXE cheat sheet to learn how to prevent the exposure to XXE.
    中文:如果应用程序容易受到 XML 外部实体 (XXE) 注入 的攻击,那么它可以被利用来执行 SSRF 攻击,请查看 XXE 漏洞预防备忘录 以了解如何防止 XXE 的暴露。

Cases / 案例

Depending on the application's functionality and requirements, there are two basic cases in which SSRF can happen:

根据应用程序的功能和需求,存在两种基本情况下可能会发生SSRF攻击:

  • Application can send request only to identified and trusted applications: Case when allowlist approach is available.
    中文:应用程序只能向 已识别和可信的应用程序 发送请求:当 允许列表 方法可用时的情况。
  • Application can send requests to ANY external IP address or domain name: Case when allowlist approach is unavailable.
    中文:应用程序可以向 任何外部 IP 地址或域名 发送请求:当 允许列表 方法不可用时的情况。

Because these two cases are very different, this cheat sheet will describe defenses against them separately.

由于这两种情况非常不同,这份备忘录将分别描述针对它们的防御措施。

Case 1 - Application can send request only to identified and trusted applications / 案例1 - 应用程序只能向已识别和可信的应用程序发送请求

Sometimes, an application needs to perform a request to another application, often located on another network, to perform a specific task. Depending on the business case, user input is required for the functionality to work.

有时,应用程序需要向另一个应用程序发送请求,通常位于另一个网络中,以执行特定任务。根据业务案例,用户输入对于功能正常工作是必需的。

Example / 示例

Take the example of a web application that receives and uses personal information from a user, such as their first name, last name, birth date etc. to create a profile in an internal HR system. By design, that web application will have to communicate using a protocol that the HR system understands to process that data.

中文:> 以一个接收并使用用户个人信息(如姓名、姓氏、出生日期等)以在内部 HR 系统中创建个人资料的 Web 应用程序为例。按照设计,该 Web 应用程序必须使用 HR 系统理解的协议来处理这些数据。 Basically, the user cannot reach the HR system directly, but, if the web application in charge of receiving user information is vulnerable to SSRF, the user can leverage it to access the HR system.

中文:> 基本上,用户无法直接访问 HR 系统,但如果负责接收用户信息的 Web 应用程序容易受到 SSRF 的攻击,用户可以利用它来访问 HR 系统。 The user leverages the web application as a proxy to the HR system.

中文:用户利用Web应用程序作为HR系统的代理。

The allowlist approach is a viable option since the internal application called by the VulnerableApplication is clearly identified in the technical/business flow. It can be stated that the required calls will only be targeted between those identified and trusted applications.

允许列表方法是一个可行的选项,因为被易受攻击的应用程序调用的内部应用程序在技术/业务流程中是明确识别的。可以声明,所需的调用将仅在这些已识别和可信的应用程序之间进行。

Available protections / 可用的保护措施

Several protective measures are possible at the Application and Network layers. To apply the defense in depth principle, both layers will be hardened against such attacks.

在应用层和网络层可以采取多种保护措施。为了应用深度防御原则,这两层都将对这类攻击进行加固。

Application layer / 应用层

The first level of protection that comes to mind is Input validation.

首先想到的保护级别是输入验证。

Based on that point, the following question comes to mind: How to perform this input validation?

基于这一点,产生了一个问题:如何执行这种输入验证?

As Orange Tsai shows in his talk, depending on the programming language used, parsers can be abused. One possible countermeasure is to apply the allowlist approach when input validation is used because, most of the time, the format of the information expected from the user is globally known.

正如Orange Tsai在他的演讲中展示的,根据所使用的编程语言,解析器可能会被滥用。一种可能的对策是在使用输入验证时应用白名单方法,因为大多数情况下,从用户那里期望的信息格式是全局已知的。

The request sent to the internal application will be based on the following information:

发送到内部应用程序的请求将基于以下信息:

  • String containing business data.
    中文:包含业务数据的字符串。
  • IP address (V4 or V6).
    中文:IP地址(V4或V6)。
  • Domain name.
    中文:域名.
  • URL.

Note: Disable the support for the following of the redirection in your web client in order to prevent the bypass of the input validation described in the section Exploitation tricks > Bypassing restrictions > Input validation > Unsafe redirect of this document.

注意: 在您的网络客户端中禁用以下重定向的支持,以防止绕过本文档中“利用技巧 > 绕过限制 > 输入验证 > 不安全的重定向”部分所描述的输入验证。

String / 字符串

In the context of SSRF, validations can be added to ensure that the input string respects the business/technical format expected.

在SSRF的上下文中,可以添加验证以确保输入字符串符合预期的业务/技术格式。

A regex can be used to ensure that data received is valid from a security point of view if the input data have a simple format (e.g. token, zip code, etc.). Otherwise, validation should be conducted using the libraries available from the string object because regex for complex formats are difficult to maintain and are highly error-prone.

可以使用正则表达式来确保如果输入数据具有简单的格式(例如令牌、邮编等),从安全角度接收到的数据是有效的。否则,应使用string对象可用的库进行验证,因为复杂格式的正则表达式难以维护且容易出错。

User input is assumed to be non-network related and consists of the user's personal information.

假设用户输入与网络无关,并包含用户的个人信息。

Example:

示例:

//Regex validation for a data having a simple format
if(Pattern.matches("[a-zA-Z0-9\\s\\-]{1,50}", userInput)){
    //Continue the processing because the input data is valid
}else{
    //Stop the processing and reject the request
}
IP address / IP地址

In the context of SSRF, there are 2 possible validations to perform:

在SSRF的上下文中,有2种可能的验证要执行:

  1. Ensure that the data provided is a valid IP V4 or V6 address.
    中文:确保提供的数据是有效的IPv4或IPv6地址。
  2. Ensure that the IP address provided belongs to one of the IP addresses of the identified and trusted applications.
    中文:确保提供的IP地址属于已识别和信任的应用程序之一的IP地址。

The first layer of validation can be applied using libraries that ensure the security of the IP address format, based on the technology used (library option is proposed here to delegate the managing of the IP address format and leverage battle-tested validation function):

第一层验证可以使用确保IP地址格式安全的库来实现,基于所使用的技术(此处提出库选项以委托管理IP地址格式并利用经过实战验证的验证函数):

Verification of the proposed libraries has been performed regarding the exposure to bypasses (Hex, Octal, Dword, URL and Mixed encoding) described in this article.

中文:已对提出的库进行了验证,以检查其是否暴露于本文中描述的绕过方法(十六进制、八进制、Dword、URL和混合编码):绕过SSRF保护。

  • JAVA: Method InetAddressValidator.isValid from the Apache Commons Validator library.
    中文:JAVA: 来自 Apache Commons Validator 库的 InetAddressValidator.isValid 方法。
    • It is NOT exposed to bypass using Hex, Octal, Dword, URL and Mixed encoding.
      中文:它不受十六进制、八进制、十进制、URL和混合编码的绕过影响。
  • .NET: Method IPAddress.TryParse from the SDK.
    中文:.NET: 来自SDK的 IPAddress.TryParse 方法。
    • It is exposed to bypass using Hex, Octal, Dword and Mixed encoding but NOT the URL encoding.
      中文:它受十六进制、八进制、十进制和混合编码的绕过影响,但不受URL编码的影响。
    • As allowlisting is used here, any bypass tentative will be blocked during the comparison against the allowed list of IP addresses.
      中文:由于这里使用了允许列表,任何绕过尝试都会在与允许的IP地址列表进行比较时被阻止。
  • JavaScript: Library ip-address.
    • It is NOT exposed to bypass using Hex, Octal, Dword, URL and Mixed encoding.
      中文:它不受十六进制、八进制、十进制、URL和混合编码的绕过影响。
  • Ruby: Class IPAddr from the SDK.
    中文:Ruby: 来自SDK的 IPAddr 类。
    • It is NOT exposed to bypass using Hex, Octal, Dword, URL and Mixed encoding.
      中文:它不受十六进制、八进制、十进制、URL和混合编码的绕过影响。

Use the output value of the method/library as the IP address to compare against the allowlist.

中文:使用方法/库的输出值作为IP地址与允许列表进行比较。**

After ensuring the validity of the incoming IP address, the second layer of validation is applied. An allowlist is created after determining all the IP addresses (v4 and v6 to avoid bypasses) of the identified and trusted applications. The valid IP is cross-checked with that list to ensure its communication with the internal application (string strict comparison with case sensitive).

在确保传入IP地址的有效性之后,应用第二层验证。在确定所有已识别和信任应用的IP地址(v4和v6以避免绕过)后创建白名单。将有效的IP与该列表进行交叉检查,以确保其与内部应用的通信(字符串严格比较,区分大小写)。

Domain name / 域名

In the attempt of validate domain names, it is apparent to do a DNS resolution to verify the existence of the domain. In general, it is not a bad idea, yet it opens up the application to attacks depending on the configuration used regarding the DNS servers used for the domain name resolution:

在尝试验证域名时,进行DNS解析以验证域名的存在是显而易见的。一般来说,这并不是一个坏主意,但它会使应用程序容易受到DNS服务器配置的影响而遭受攻击:

  • It can disclose information to external DNS resolvers.
    中文:它可能会向外部DNS解析器泄露信息。
  • It can be used by an attacker to bind a legit domain name to an internal IP address. See the section Exploitation tricks > Bypassing restrictions > Input validation > DNS pinning of this document.
    中文:这可以被攻击者用来将合法域名绑定到内部IP地址。参见此文档中的利用技巧 > 绕过限制 > 输入验证 > DNS固定部分。
  • An attacker can use it to deliver a malicious payload to the internal DNS resolvers and the API (SDK or third-party) used by the application to handle the DNS communication and then, potentially, trigger a vulnerability in one of these components.
    中文:攻击者可以利用它向内部DNS解析器和应用程序(SDK或第三方)使用的API(SDK或第三方)传递恶意负载,该API用于处理DNS通信,然后可能触发这些组件中的一个漏洞。

In the context of SSRF, there are two validations to perform:

在SSRF的上下文中,有两个验证需要执行:

  1. Ensure that the data provided is a valid domain name.
    中文:确保提供的数据是有效的域名。
  2. Ensure that the domain name provided belongs to one of the domain names of the identified and trusted applications (the allowlisting comes to action here).
    中文:确保提供的域名属于已识别和信任的应用程序中的域名之一(此处允许列表生效)。

Similar to the IP address validation, the first layer of validation can be applied using libraries that ensure the security of the domain name format, based on the technology used (library option is proposed here in order to delegate the managing of the domain name format and leverage battle tested validation function):

类似于IP地址验证,第一层验证可以使用确保域名格式安全的库来实现,基于所使用的技术(此处提出库选项,以便委托域名格式的管理并利用经过实战验证的验证功能):

Verification of the proposed libraries has been performed to ensure that the proposed functions do not perform any DNS resolution query.

中文:已对提出的库进行验证,以确保提出的函数不会执行任何DNS解析查询。

Example of execution of the proposed regex for Ruby:

以下为Ruby中提出的正则表达式的执行示例:

domain_names = ["owasp.org","owasp-test.org","doc-test.owasp.org","doc.owasp.org",
                "<script>alert(1)</script>","<script>alert(1)</script>.owasp.org"]
domain_names.each { |domain_name|
    if ( domain_name =~ /^(((?!-))(xn--|_{1,1})?[a-z0-9-]{0,61}[a-z0-9]{1,1}\.)*(xn--)?([a-z0-9][a-z0-9\-]{0,60}|[a-z0-9-]{1,30}\.[a-z]{2,})$/ )
        puts "[i] #{domain_name} is VALID"
    else
        puts "[!] #{domain_name} is INVALID"
    end
}
$ ruby test.rb
[i] owasp.org is VALID
[i] owasp-test.org is VALID
[i] doc-test.owasp.org is VALID
[i] doc.owasp.org is VALID
[!] <script>alert(1)</script> is INVALID
[!] <script>alert(1)</script>.owasp.org is INVALID

After ensuring the validity of the incoming domain name, the second layer of validation is applied:

在确保传入域名的有效性后,应用第二层验证:

  1. Build an allowlist with all the domain names of every identified and trusted applications.
    中文:构建一个包含所有已识别和信任的应用程序域名的允许列表。
  2. Verify that the domain name received is part of this allowlist (string strict comparison with case sensitive).

Domain allowlisting alone does not prevent DNS rebinding. Validate the resolved destination IP addresses against the application's permitted networks, then ensure the HTTP client connects only to validated addresses. A second, unchecked DNS lookup between validation and connection can bypass these checks; see GitLab's URL blocker guidance.

Use an HTTP client mechanism that connects to the validated IP while preserving the original hostname for the HTTP Host header, TLS Server Name Indication (SNI), and certificate verification, as illustrated by curl's custom address resolution. Apply the destination policy to retries and fallback connections as well. The following DNS configuration and monitoring provide additional detection, not a substitute for connection-time enforcement:

  1. Ensure that the domains that are part of your organization are resolved by your internal DNS server first in the chains of DNS resolvers.
  2. Monitor the domains allowlist in order to detect when any of them resolves to a/an:
    中文:监控域名白名单,以便检测其中任何域名解析为: - Local IP address (V4 + V6). - Internal IP of your organization (expected to be in private IP ranges) for the domain that are not part of your organization.

The following Python3 script can be used, as a starting point, for the monitoring mentioned above:

# Dependencies: pip install ipaddress dnspython
import ipaddress
import dns.resolver

# Configure the allowlist to check
DOMAINS_ALLOWLIST = ["owasp.org", "labslinux"]

# Configure the DNS resolver to use for all DNS queries
DNS_RESOLVER = dns.resolver.Resolver()
DNS_RESOLVER.nameservers = ["1.1.1.1"]

def verify_dns_records(domain, records, type):
    """
    Verify if one of the DNS records resolve to a non public IP address.
    Return a boolean indicating if any error has been detected.
    """
    error_detected = False
    if records is not None:
        for record in records:
            value = record.to_text().strip()
            try:
                ip = ipaddress.ip_address(value)
                # See https://docs.python.org/3/library/ipaddress.html#ipaddress.IPv4Address.is_global
                if not ip.is_global:
                    print("[!] DNS record type '%s' for domain name '%s' resolve to
                    a non public IP address '%s'!" % (type, domain, value))
                    error_detected = True
            except ValueError:
                error_detected = True
                print("[!] '%s' is not valid IP address!" % value)
    return error_detected

def check():
    """
    Perform the check of the allowlist of domains.
    Return a boolean indicating if any error has been detected.
    """
    error_detected = False
    for domain in DOMAINS_ALLOWLIST:
        # Get the IPs of the current domain
        # See https://en.wikipedia.org/wiki/List_of_DNS_record_types
        try:
            # A = IPv4 address record
            ip_v4_records = DNS_RESOLVER.query(domain, "A")
        except Exception as e:
            ip_v4_records = None
            print("[i] Cannot get A record for domain '%s': %s\n" % (domain,e))
        try:
            # AAAA = IPv6 address record
            ip_v6_records = DNS_RESOLVER.query(domain, "AAAA")
        except Exception as e:
            ip_v6_records = None
            print("[i] Cannot get AAAA record for domain '%s': %s\n" % (domain,e))
        # Verify the IPs obtained
        if verify_dns_records(domain, ip_v4_records, "A")
        or verify_dns_records(domain, ip_v6_records, "AAAA"):
            error_detected = True
    return error_detected

if __name__== "__main__":
    if check():
        exit(1)
    else:
        exit(0)
URL

Do not accept complete URLs from the user because URL are difficult to validate and the parser can be abused depending on the technology used as showcased by the following talk of Orange Tsai.

不要接受完整的URL,因为URL难以验证,并且解析器可能会根据所使用的技术的不同而被滥用,如以下演讲所示,演讲者为Orange Tsai。

If network related information is really needed then only accept a valid IP address or domain name.

如果确实需要网络相关信息,则只接受有效的IP地址或域名。

Match the host against an allowlist, and build the request yourself. The input here is an IP address or a domain name rather than a whole URL, so compare that value against an explicit allowlist of permitted destinations, then build the request from the entry that matched, together with a scheme, port and path the application fixes itself. Where that host was extracted from a URL the user supplied, do not copy the other components of that URL across either: carrying its path or query through, rather than rebuilding them, hands the next component something it has to parse again.

将主机与允许列表匹配,并自行构建请求。 此处的输入是一个IP地址或域名,而不是整个URL,因此将此值与允许的目的地显式允许列表进行比较,然后从匹配的条目构建请求,包括方案、端口和应用程序固定的路径。如果该主机是从用户提供的URL中提取的,则不要复制该URL的其他组件:通过传递路径或查询而不是重新构建它们,将使下一个组件再次解析。

Treat parser disagreement as a rejection. Where a URL does cross a service boundary as a string and is parsed again at the other end, two implementations can read different hosts from the same bytes. http://example.com\@evil.com is such a string: a parser following the WHATWG URL Standard treats the backslash under a special scheme as a path separator and reads the host as example.com, while the userinfo grammar in RFC 3986 admits no backslash at all, so the string is not a URI that RFC defines. An implementation that does not enforce the grammar still returns a host, because it takes everything before the final @ to be userinfo: CPython's urllib.parse derives the host with netloc.rpartition('@'), so urlsplit("http://example.com\@evil.com").hostname returns evil.com. Reject a URL whose host is not read identically by every parser in play, rather than reconciling the readings.

将解析器不一致视为拒绝。 当一个URL作为字符串跨越服务边界并在另一端再次解析时,两个实现可以从相同的字节中读取不同的主机。http://example.com\@evil.com是这样的字符串:遵循WHATWG URL标准的解析器将特殊方案下的反斜杠视为路径分隔符,并将主机读取为example.com,而RFC 3986中的userinfo语法根本不允许反斜杠,因此该字符串不是RFC定义的URI。如果一个实现没有强制执行语法,它仍然会返回一个主机,因为它将最后的@之前的所有内容视为用户信息:CPython的urllib.parse模块使用netloc.rpartition('@')(https://github.com/python/cpython/blob/v3.11.15/Lib/urllib/parse.py#L208)推导出主机,因此urlsplit("http://example.com\@evil.com").hostname返回evil.com。拒绝那些主机不是由每个参与解析器以相同方式读取的URL,而不是协调读取结果。

Network layer / 网络层

The objective of the Network layer security is to prevent the VulnerableApplication from performing calls to arbitrary applications. Only allowed routes will be available for this application in order to limit its network access to only those that it should communicate with.

网络层安全的目标是防止VulnerableApplication执行对任意应用程序的调用。只有允许的路由将可用于此应用程序,以限制其网络访问仅限于它应该与之通信的那些。

The Firewall component, as a specific device or using the one provided within the operating system, will be used here to define the legitimate flows.

防火墙组件,作为一个特定设备或使用操作系统内提供的组件,将在这里用来定义合法的流量。

In the schema below, a Firewall component is leveraged to limit the application's access, and in turn, limit the impact of an application vulnerable to SSRF:

在下面的方案中,利用防火墙组件来限制应用程序的访问,从而限制易受SSRF攻击的应用程序的影响:

Case 1 for Network layer protection about flows that we want to prevent

关于我们想要阻止的流量的网络层保护案例 1

Network segregation) (网络隔离) (see this set of implementation advice can also be leveraged and is highly recommended in order to block illegitimate calls directly at network level itself.

Case 2 - Application can send requests to ANY external IP address or domain name / 案例2 - 应用程序可以发送请求到任何外部IP地址或域名

This case happens when a user can control a URL to an External resource and the application makes a request to this URL (e.g. in case of WebHooks). Allow lists cannot be used here because the list of IPs/domains is often unknown upfront and is dynamically changing.

当用户可以控制指向外部资源的URL,并且应用程序向该URL发起请求时(例如在WebHooks的情况下),这种情况就会发生。不允许列表在这里无法使用,因为IP地址/域名的列表通常在事先是未知的,并且是动态变化的。

In this scenario, External refers to any IP that doesn't belong to the internal network, and should be reached by going over the public internet.

在这种情况下,外部指的是不属于内部网络的任何IP,并且需要通过公共互联网才能访问。

Thus, the call from the Vulnerable Application:

因此,来自易受攻击的应用程序的调用:

  • Is NOT targeting one of the IP/domain located inside the company's global network.
    中文:不是针对公司全球网络内位于的任何一个IP/域名。
  • Uses a convention defined between the VulnerableApplication and the expected IP/domain in order to prove that the call has been legitimately initiated.
    中文:使用在VulnerableApplication和预期的IP/域名之间定义的约定,以证明调用是合法发起的。

Challenges in blocking URLs at application layer / 在应用层阻止URL的挑战

Based on the business requirements of the above mentioned applications, the allowlist approach is not a valid solution. Despite knowing that the block-list approach is not an impenetrable wall, it is the best solution in this scenario. It is informing the application what it should not do.

根据上述应用程序的业务需求,允许列表方法不是一个有效的解决方案。尽管知道阻止列表方法不是坚不可摧的墙壁,但在这种情况下,它是最好的解决方案。它是在告诉应用程序它不应该做什么。

Here is why filtering URLs is hard at the Application layer:

以下是为什么在应用层过滤URL是困难的:

  • It implies that the application must be able to detect, at the code level, that the provided IP (V4 + V6) is not part of the official private networks ranges including also localhost and IPv4/v6 Link-Local addresses. Not every SDK provides a built-in feature for this kind of verification, and leaves the handling up to the developer to understand all of its pitfalls and possible values, which makes it a demanding task.
    中文:这意味着应用程序必须在代码级别能够检测到提供的IP(V4 + V6)不属于官方的私有网络范围,包括localhost和IPv4/v6 Link-Local地址。并非每个SDK都提供内置的此类验证功能,并将处理留给了开发者去理解所有潜在的问题和可能的值,这使得它成为一个具有挑战性的任务。
  • Same remark for domain name: The company must maintain a list of all internal domain names and provide a centralized service to allow an application to verify if a provided domain name is an internal one. For this verification, an internal DNS resolver can be queried by the application but this internal DNS resolver must not resolve external domain names.
    中文:关于域名的相同备注:公司必须维护所有内部域名的列表,并提供一个集中式服务,以允许应用程序验证提供的域名是否为内部域名。为此验证,应用程序可以查询内部DNS解析器,但此内部DNS解析器不得解析外部域名。

Available protections / 可用的保护措施

Taking into consideration the same assumption in the following example for the following sections.

考虑到以下示例中相同的假设,以下章节也适用。

Application layer / 应用层

Like for the case n°1, it is assumed that the IP Address or domain name is required to create the request that will be sent to the TargetApplication.

类似于案例n°1,假设IP地址或域名是创建将发送到目标应用程序的请求所必需的。

The first validation on the input data presented in the case n°1 on the 3 types of data will be the same for this case BUT the second validation will differ. Indeed, here we must use the block-list approach.

对于案例n°1中呈现的输入数据的第一次验证,在3种数据类型上将与本案相同但第二次验证将不同。实际上,这里我们必须使用黑名单方法。

Regarding the proof of legitimacy of the request: The TargetedApplication that will receive the request must generate a random token (ex: alphanumeric of 20 characters) that is expected to be passed by the caller (in body via a parameter for which the name is also defined by the application itself and only allow characters set [a-z]{1,10}) to perform a valid request. The receiving endpoint must only accept HTTP POST requests.

中文:关于请求合法性的证明:将接收请求的TargetedApplication必须生成一个随机令牌(例如:20个字符的字母数字组合),该令牌预期将由调用者(通过参数传递,参数名称也由应用程序本身定义,并且只允许字符集[a-z]{1,10})传递以执行有效请求。接收端点必须仅接受HTTP POST请求。

Validation flow (if one the validation steps fail then the request is rejected):

验证流程(如果验证步骤之一失败,则请求被拒绝):

  1. The application will receive the IP address or domain name of the TargetedApplication and it will apply the first validation on the input data using the libraries/regex mentioned in this section.
    中文:应用程序将接收到TargetedApplication的IP地址或域名,它将使用本节中提到的库/正则表达式对输入数据进行第一次验证。
  2. The second validation will be applied against the IP address or domain name of the TargetedApplication using the following block-list approach:
    中文:第二次验证将使用以下黑名单方法应用于目标应用程序的IP地址或域名: - For IP address:
    中文:对于IP地址:
    • The application will verify that it is a public one (see the hint provided in the next paragraph with the python code sample).
      中文:应用程序将验证它是一个公开的应用程序(参见下一段提供的Python代码示例中的提示)。
    • For domain name:
      中文:对于域名:
      1. The application will verify that it is a public one by trying to resolve the domain name against the DNS resolver that will only resolve internal domain name. Here, it must return a response indicating that it do not know the provided domain because the expected value received must be a public domain.
        中文:应用程序将通过尝试将域名与仅解析内部域名的DNS解析器进行解析来验证它是否为公开的。在这里,它必须返回一个响应,表明它不知道提供的域名,因为接收到的预期值必须是公开域名。
      2. Retrieve all the IP addresses behind the domain name (records A + AAAA for IPv4 + IPv6) and apply the same public-address verification described above. Bind the connection to a validated address as described under domain name validation; checking DNS answers separately does not prevent DNS rebinding.
        中文:检索域名背后的所有IP地址(IPv4的记录A + IPv6的AAAA)并应用上述相同的公共地址验证。将连接绑定到如域名验证中所述的有效地址;单独检查DNS响应不能防止DNS重绑定。
  3. The application will receive the protocol to use for the request via a dedicated input parameter for which it will verify the value against an allowed list of protocols (HTTP or HTTPS).
    中文:应用程序将通过一个专用输入参数接收用于请求的协议,它将对该值与允许的协议列表(HTTP或HTTPS)进行验证。
  4. The application will receive the parameter name for the token to pass to the TargetedApplication via a dedicated input parameter for which it will only allow the characters set [a-z]{1,10}.
    中文:应用程序将通过一个专用输入参数接收传递给TargetedApplication的令牌参数名称,它只允许字符集[a-z]{1,10}。
  5. The application will receive the token itself via a dedicated input parameter for which it will only allow the characters set [a-zA-Z0-9]{20}.
    中文:应用程序将通过一个专用输入参数接收令牌本身,它只允许字符集[a-zA-Z0-9]{20}。
  6. The application will receive and validate (from a security point of view) any business data needed to perform a valid call.
    中文:应用程序将接收并验证(从安全角度)执行有效调用所需的所有业务数据。
  7. The application will build the HTTP POST request using only validated information and connect only to an IP address validated in step 2 (don't forget to disable the support for redirection in the web client used).
    中文:应用程序将仅使用验证信息构建HTTP POST请求,并且仅连接到步骤2中验证过的IP地址(不要忘记在Web客户端中禁用重定向)。
Network layer / 网络层

Similar to the following section.

类似于以下部分.

IMDSv2 in AWS / AWS中的IMDSv2

In cloud environments SSRF is often used to access and steal credentials and access tokens from metadata services (e.g. AWS Instance Metadata Service, Azure Instance Metadata Service, GCP metadata server).

在云环境中,SSRF常被用于访问和窃取元数据服务(例如AWS实例元数据服务、Azure实例元数据服务、GCP元数据服务器)的凭据和访问令牌。

IMDSv2 is an additional defense-in-depth mechanism for AWS that mitigates some of the instances of SSRF.

To leverage this protection migrate to IMDSv2 and disable old IMDSv1. Check out AWS documentation for more details.

为了利用这种保护,迁移到IMDSv2并禁用旧的IMDSv1。有关更多详细信息,请参阅AWS文档.

Deny-list (Last Resort) / 拒绝列表(最后手段)

Deny-lists are bypass-prone. Prefer allow-lists.

拒绝列表容易绕过。优先考虑允许列表。

When unavoidable, block these minimum ranges:

在无法避免的情况下,阻止以下最小范围:

Service / 服务 Block IPs/Domains
AWS IMDS 169.254.169.254, metadata.amazonaws.com
GCP Metadata
GCP 元数据
metadata.google.internal, 169.254.169.254
Azure IMDS 169.254.169.254
Localhost
本地主机
127.0.0.0/8, 0.0.0.0/8, ::1/128
RFC1918 Private
RFC1918 私有
10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16
IPv6 Unique Local
IPv6 唯一本地
fc00::/7 (RFC 4193)
IPv6 Link-Local
IPv6 链路本地
fe80::/10 (RFC 4291)
Multicast
组播
224.0.0.0/4, ff00::/8

Full production example: ComputerCraft SSRF deny-list

完整生产示例: ComputerCraft SSRF 禁用列表

Sources:

来源:

Semgrep Rules / Semgrep 规则

Semgrep is a command-line tool for offline static analysis. Use pre-built or custom rules to enforce code and security standards in your codebase. Explore the Semgrep rules for SSRF to effectively identify and investigate potential SSRF vulnerabilities.

探索 Semgrep 规则 以有效地识别和调查潜在的 SSRF 漏洞。

Tools and code used for schemas / 用于模式的结构化工具和代码

Mermaid code for SSRF common flow (printscreen are used to capture PNG image inserted into this cheat sheet):

Mermaid 代码用于 SSRF 常见流程(使用截图捕获的 PNG 图像插入到本速查表中):

sequenceDiagram
    participant Attacker
    participant VulnerableApplication
    participant TargetedApplication
    Attacker->>VulnerableApplication: Crafted HTTP request
    VulnerableApplication->>TargetedApplication: Request (HTTP, FTP...)
    Note left of TargetedApplication: Use payload included<br>into the request to<br>VulnerableApplication
    TargetedApplication->>VulnerableApplication: Response
    VulnerableApplication->>Attacker: Response
    Note left of VulnerableApplication: Include response<br>from the<br>TargetedApplication

Draw.io schema XML code for the "case 1 for network layer protection about flows that we want to prevent" schema (printscreen are used to capture PNG image inserted into this cheat sheet).

Draw.io 模式 XML 代码用于“关于我们想要阻止的流量的网络层保护案例 1”模式(使用截图捕获的 PNG 图像插入到本速查表中).

References / 参考