[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M79Q6EEAF0RG8Z5PRCSQPQMM":3,"skill-same:01M79Q6EEAF0RG8Z5PRCSQPQMM":43},{"skillNo":4,"name":5,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":15,"categories":16,"keywords":17,"publishedAt":18,"linkOnly":19,"markdownContent":20,"markdownBody":20,"frontMatter":21,"refName":22,"commitSha":23,"skillDirectory":24,"forksCount":13,"repositoryHtmlUrl":25,"repositoryHomepageUrl":6,"ownerHtmlUrl":26,"ownerBlogUrl":6,"ownerDescription":27,"updatedAt":28,"install":29,"resources":42},"01M79Q6EEAF0RG8Z5PRCSQPQMM","server-side-request-forgery-prevention-cheat-sheet",null,"JunMystery\u002FAgent-Guidance","JunMystery","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F161468934?v=4","01M79MTMV8F0BY8NK1A92RA8A0","Jun","01M79MTMWCFBKPHN5B25E6V20B",0,"MIT","https:\u002F\u002Fgithub.com\u002FJunMystery\u002FAgent-Guidance\u002Fblob\u002Fmain\u002Fskills\u002Fserver-side-request-forgery-prevention-cheat-sheet\u002FSKILL.md",[],[],"2026-11-15 14:19:17",false,"# Server-Side Request Forgery Prevention Cheat Sheet\n\n## Introduction\n\nThe objective of the cheat sheet is to provide advice regarding the protection against [Server Side Request Forgery](https:\u002F\u002Fwww.acunetix.com\u002Fblog\u002Farticles\u002Fserver-side-request-forgery-vulnerability\u002F) (SSRF) attack.\n\nThis cheat sheet will focus on the defensive point of view and will not explain how to perform this attack. This [talk](..\u002Fassets\u002FServer_Side_Request_Forgery_Prevention_Cheat_Sheet_Orange_Tsai_Talk.pdf) from the security researcher [Orange Tsai](https:\u002F\u002Ftwitter.com\u002Forange_8361) as well as this [document](..\u002Fassets\u002FServer_Side_Request_Forgery_Prevention_Cheat_Sheet_SSRF_Bible.pdf) provide techniques on how to perform this kind of attack.\n\n## Context\n\nSSRF is an attack vector that abuses an application to interact with the internal\u002Fexternal network or the machine itself. One of the enablers for this vector is the mishandling of URLs, as showcased in the following examples:\n\n- Image on an external server (*e.g.* user enters image URL of their avatar for the application to download and use).\n- Custom [WebHook](https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FWebhook) (users have to specify Webhook handlers or Callback URLs).\n- 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.\n\n## Overview of a SSRF common flow\n\n![SSRF Common Flow](..\u002Fassets\u002FServer_Side_Request_Forgery_Prevention_Cheat_Sheet_SSRF_Common_Flow.png)\n\n*Notes:*\n\n- 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:\u002F\u002F`, `phar:\u002F\u002F`, `gopher:\u002F\u002F`, `data:\u002F\u002F`, `dict:\u002F\u002F`, etc.).\n- If the application is vulnerable to [XML eXternal Entity (XXE) injection](https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Fxxe) then it can be exploited to perform a [SSRF attack](https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Fxxe#exploiting-xxe-to-perform-ssrf-attacks), take a look at the [XXE cheat sheet](XML_External_Entity_Prevention_Cheat_Sheet.md) to learn how to prevent the exposure to XXE.\n\n## Cases\n\nDepending on the application's functionality and requirements, there are two basic cases in which SSRF can happen:\n\n- Application can send request only to **identified and trusted applications**: Case when [allowlist](https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FWhitelisting) approach is available.\n- Application can send requests to **ANY external IP address or domain name**: Case when [allowlist](https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FWhitelisting) approach is unavailable.\n\nBecause these two cases are very different, this cheat sheet will describe defenses against them separately.\n\n### Case 1 - Application can send request only to identified and trusted applications\n\nSometimes, 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.\n\n#### Example\n\n > 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.\n > 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.\n > The user leverages the web application as a proxy to the HR system.\n\nThe allowlist approach is a viable option since the internal application called by the *VulnerableApplication* is clearly identified in the technical\u002Fbusiness flow. It can be stated that the required calls will only be targeted between those identified and trusted applications.\n\n#### Available protections\n\nSeveral 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.\n\n##### Application layer\n\nThe first level of protection that comes to mind is [Input validation](Input_Validation_Cheat_Sheet.md).\n\nBased on that point, the following question comes to mind: *How to perform this input validation?*\n\nAs [Orange Tsai](https:\u002F\u002Ftwitter.com\u002Forange_8361) shows in his [talk](..\u002Fassets\u002FServer_Side_Request_Forgery_Prevention_Cheat_Sheet_Orange_Tsai_Talk.pdf), depending on the programming language used, parsers can be abused. One possible countermeasure is to apply the [allowlist approach](Input_Validation_Cheat_Sheet.md#allowlist-vs-denylist) when input validation is used because, most of the time, the format of the information expected from the user is globally known.\n\nThe request sent to the internal application will be based on the following information:\n\n- String containing business data.\n- IP address (V4 or V6).\n- Domain name.\n- URL.\n\n**Note:** Disable the support for the following of the [redirection](https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FHTTP\u002FRedirections) 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](..\u002Fassets\u002FServer_Side_Request_Forgery_Prevention_Cheat_Sheet_SSRF_Bible.pdf).\n\n###### String\n\nIn the context of SSRF, validations can be added to ensure that the input string respects the business\u002Ftechnical format expected.\n\nA [regex](https:\u002F\u002Fwww.regular-expressions.info\u002F) 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.\n\nUser input is assumed to be non-network related and consists of the user's personal information.\n\nExample:\n\n```java\n\u002F\u002FRegex validation for a data having a simple format\nif(Pattern.matches(\"[a-zA-Z0-9\\\\s\\\\-]{1,50}\", userInput)){\n    \u002F\u002FContinue the processing because the input data is valid\n}else{\n    \u002F\u002FStop the processing and reject the request\n}\n```\n\n###### IP address\n\nIn the context of SSRF, there are 2 possible validations to perform:\n\n1. Ensure that the data provided is a valid IP V4 or V6 address.\n2. Ensure that the IP address provided belongs to one of the IP addresses of the identified and trusted applications.\n\nThe 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):\n\n> Verification of the proposed libraries has been performed regarding the exposure to bypasses (Hex, Octal, Dword, URL and Mixed encoding) described in this [article](https:\u002F\u002Fmedium.com\u002F@vickieli\u002Fbypassing-ssrf-protection-e111ae70727b).\n\n- **JAVA:** Method [InetAddressValidator.isValid](http:\u002F\u002Fcommons.apache.org\u002Fproper\u002Fcommons-validator\u002Fapidocs\u002Forg\u002Fapache\u002Fcommons\u002Fvalidator\u002Froutines\u002FInetAddressValidator.html#isValid(java.lang.String)) from the [Apache Commons Validator](http:\u002F\u002Fcommons.apache.org\u002Fproper\u002Fcommons-validator\u002F) library.\n    - **It is NOT exposed** to bypass using Hex, Octal, Dword, URL and Mixed encoding.\n- **.NET**: Method [IPAddress.TryParse](https:\u002F\u002Fdocs.microsoft.com\u002Fen-us\u002Fdotnet\u002Fapi\u002Fsystem.net.ipaddress.tryparse?view=netframework-4.8) from the SDK.\n    - **It is exposed** to bypass using Hex, Octal, Dword and Mixed encoding but **NOT** the URL encoding.\n    - As allowlisting is used here, any bypass tentative will be blocked during the comparison against the allowed list of IP addresses.\n- **JavaScript**: Library [ip-address](https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Fip-address).\n    - **It is NOT exposed** to bypass using Hex, Octal, Dword, URL and Mixed encoding.\n- **Ruby**: Class [IPAddr](https:\u002F\u002Fruby-doc.org\u002Fstdlib-2.0.0\u002Flibdoc\u002Fipaddr\u002Frdoc\u002FIPAddr.html) from the SDK.\n    - **It is NOT exposed** to bypass using Hex, Octal, Dword, URL and Mixed encoding.\n\n> **Use the output value of the method\u002Flibrary as the IP address to compare against the allowlist.**\n\nAfter 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).\n\n###### Domain name\n\nIn 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:\n\n- It can disclose information to external DNS resolvers.\n- 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](..\u002Fassets\u002FServer_Side_Request_Forgery_Prevention_Cheat_Sheet_SSRF_Bible.pdf).\n- 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.\n\nIn the context of SSRF, there are two validations to perform:\n\n1. Ensure that the data provided is a valid domain name.\n2. 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).\n\nSimilar 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):\n\n> Verification of the proposed libraries has been performed to ensure that the proposed functions do not perform any DNS resolution query.\n\n- **JAVA:** Method [DomainValidator.isValid](https:\u002F\u002Fcommons.apache.org\u002Fproper\u002Fcommons-validator\u002Fapidocs\u002Forg\u002Fapache\u002Fcommons\u002Fvalidator\u002Froutines\u002FDomainValidator.html#isValid(java.lang.String)) from the [Apache Commons Validator](http:\u002F\u002Fcommons.apache.org\u002Fproper\u002Fcommons-validator\u002F) library.\n- **.NET**: Method [Uri.CheckHostName](https:\u002F\u002Fdocs.microsoft.com\u002Fen-us\u002Fdotnet\u002Fapi\u002Fsystem.uri.checkhostname?view=netframework-4.8) from the SDK.\n- **JavaScript**: Library [is-valid-domain](https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Fis-valid-domain).\n- **Python**: Module [validators.domain](https:\u002F\u002Fvalidators.readthedocs.io\u002Fen\u002Flatest\u002F#module-validators.domain).\n- **Ruby**: No valid dedicated gem has been found.\n    - [domainator](https:\u002F\u002Fgithub.com\u002Fmhuggins\u002Fdomainator), [public_suffix](https:\u002F\u002Fgithub.com\u002Fweppos\u002Fpublicsuffix-ruby) and [addressable](https:\u002F\u002Fgithub.com\u002Fsporkmonger\u002Faddressable) has been tested but unfortunately they all consider `\u003Cscript>alert(1)\u003C\u002Fscript>.owasp.org` as a valid domain name.\n    - This regex, taken from [here](https:\u002F\u002Fstackoverflow.com\u002Fa\u002F26987741), can be used: `^(((?!-))(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,})$`\n\nExample of execution of the proposed regex for Ruby:\n\n```ruby\ndomain_names = [\"owasp.org\",\"owasp-test.org\",\"doc-test.owasp.org\",\"doc.owasp.org\",\n                \"\u003Cscript>alert(1)\u003C\u002Fscript>\",\"\u003Cscript>alert(1)\u003C\u002Fscript>.owasp.org\"]\ndomain_names.each { |domain_name|\n    if ( domain_name =~ \u002F^(((?!-))(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,})$\u002F )\n        puts \"[i] #{domain_name} is VALID\"\n    else\n        puts \"[!] #{domain_name} is INVALID\"\n    end\n}\n```\n\n```bash\n$ ruby test.rb\n[i] owasp.org is VALID\n[i] owasp-test.org is VALID\n[i] doc-test.owasp.org is VALID\n[i] doc.owasp.org is VALID\n[!] \u003Cscript>alert(1)\u003C\u002Fscript> is INVALID\n[!] \u003Cscript>alert(1)\u003C\u002Fscript>.owasp.org is INVALID\n```\n\nAfter ensuring the validity of the incoming domain name, the second layer of validation is applied:\n\n1. Build an allowlist with all the domain names of every identified and trusted applications.\n2. Verify that the domain name received is part of this allowlist (string strict comparison with case sensitive).\n\nDomain 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](https:\u002F\u002Fdocs.gitlab.com\u002Fdevelopment\u002Fsecure_coding_guidelines\u002Fruby\u002F#url-blocker--validation-libraries).\n\nUse 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](https:\u002F\u002Feverything.curl.dev\u002Fusingcurl\u002Fconnections\u002Fname.html#provide-a-custom-ip-address-for-a-name). 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:\n\n1. Ensure that the domains that are part of your organization are resolved by your internal DNS server first in the chains of DNS resolvers.\n2. Monitor the domains allowlist in order to detect when any of them resolves to a\u002Fan:\n   - Local IP address (V4 + V6).\n   - Internal IP of your organization (expected to be in private IP ranges) for the domain that are not part of your organization.\n\nThe following Python3 script can be used, as a starting point, for the monitoring mentioned above:\n\n```python\n# Dependencies: pip install ipaddress dnspython\nimport ipaddress\nimport dns.resolver\n\n# Configure the allowlist to check\nDOMAINS_ALLOWLIST = [\"owasp.org\", \"labslinux\"]\n\n# Configure the DNS resolver to use for all DNS queries\nDNS_RESOLVER = dns.resolver.Resolver()\nDNS_RESOLVER.nameservers = [\"1.1.1.1\"]\n\ndef verify_dns_records(domain, records, type):\n    \"\"\"\n    Verify if one of the DNS records resolve to a non public IP address.\n    Return a boolean indicating if any error has been detected.\n    \"\"\"\n    error_detected = False\n    if records is not None:\n        for record in records:\n            value = record.to_text().strip()\n            try:\n                ip = ipaddress.ip_address(value)\n                # See https:\u002F\u002Fdocs.python.org\u002F3\u002Flibrary\u002Fipaddress.html#ipaddress.IPv4Address.is_global\n                if not ip.is_global:\n                    print(\"[!] DNS record type '%s' for domain name '%s' resolve to\n                    a non public IP address '%s'!\" % (type, domain, value))\n                    error_detected = True\n            except ValueError:\n                error_detected = True\n                print(\"[!] '%s' is not valid IP address!\" % value)\n    return error_detected\n\ndef check():\n    \"\"\"\n    Perform the check of the allowlist of domains.\n    Return a boolean indicating if any error has been detected.\n    \"\"\"\n    error_detected = False\n    for domain in DOMAINS_ALLOWLIST:\n        # Get the IPs of the current domain\n        # See https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FList_of_DNS_record_types\n        try:\n            # A = IPv4 address record\n            ip_v4_records = DNS_RESOLVER.query(domain, \"A\")\n        except Exception as e:\n            ip_v4_records = None\n            print(\"[i] Cannot get A record for domain '%s': %s\\n\" % (domain,e))\n        try:\n            # AAAA = IPv6 address record\n            ip_v6_records = DNS_RESOLVER.query(domain, \"AAAA\")\n        except Exception as e:\n            ip_v6_records = None\n            print(\"[i] Cannot get AAAA record for domain '%s': %s\\n\" % (domain,e))\n        # Verify the IPs obtained\n        if verify_dns_records(domain, ip_v4_records, \"A\")\n        or verify_dns_records(domain, ip_v6_records, \"AAAA\"):\n            error_detected = True\n    return error_detected\n\nif __name__== \"__main__\":\n    if check():\n        exit(1)\n    else:\n        exit(0)\n```\n\n###### URL\n\nDo 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](..\u002Fassets\u002FServer_Side_Request_Forgery_Prevention_Cheat_Sheet_Orange_Tsai_Talk.pdf) of [Orange Tsai](https:\u002F\u002Ftwitter.com\u002Forange_8361).\n\nIf network related information is really needed then only accept a valid IP address or domain name.\n\n**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](https:\u002F\u002Fcheatsheetseries.owasp.org\u002Fcheatsheets\u002FInput_Validation_Cheat_Sheet.html#allowlist-vs-denylist) 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.\n\n**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:\u002F\u002Fexample.com\\@evil.com` is such a string: a parser following the [WHATWG URL Standard](https:\u002F\u002Furl.spec.whatwg.org\u002F#url-parsing) 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](https:\u002F\u002Fwww.rfc-editor.org\u002Frfc\u002Frfc3986#section-3.2.1) 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`](https:\u002F\u002Fdocs.python.org\u002F3\u002Flibrary\u002Furllib.parse.html#urllib.parse.urlsplit) derives the host with [`netloc.rpartition('@')`](https:\u002F\u002Fgithub.com\u002Fpython\u002Fcpython\u002Fblob\u002Fv3.11.15\u002FLib\u002Furllib\u002Fparse.py#L208), so `urlsplit(\"http:\u002F\u002Fexample.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.\n\n##### Network layer\n\nThe 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.\n\nThe Firewall component, as a specific device or using the one provided within the operating system, will be used here to define the legitimate flows.\n\nIn 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:\n\n![Case 1 for Network layer protection about flows that we want to prevent](..\u002Fassets\u002FServer_Side_Request_Forgery_Prevention_Cheat_Sheet_Case1_NetworkLayer_PreventFlow.png)\n\n[Network segregation](https:\u002F\u002Fwww.mwrinfosecurity.com\u002Four-thinking\u002Fmaking-the-case-for-network-segregation) (see this set of [implementation advice](https:\u002F\u002Fwww.cyber.gov.au\u002Facsc\u002Fview-all-content\u002Fpublications\u002Fimplementing-network-segmentation-and-segregation) can also be leveraged and **is highly recommended in order to block illegitimate calls directly at network level itself**.\n\n### Case 2 - Application can send requests to ANY external IP address or domain name\n\nThis 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](https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FWebhook)). Allow lists cannot be used here because the list of IPs\u002Fdomains is often unknown upfront and is dynamically changing.\n\nIn 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.\n\nThus, the call from the *Vulnerable Application*:\n\n- **Is NOT** targeting one of the IP\u002Fdomain *located inside* the company's global network.\n- Uses a convention defined between the *VulnerableApplication* and the expected IP\u002Fdomain in order to *prove* that the call has been legitimately initiated.\n\n#### Challenges in blocking URLs at application layer\n\nBased 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.\n\nHere is why filtering URLs is hard at the Application layer:\n\n- 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](https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FPrivate_network) including also *localhost* and *IPv4\u002Fv6 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.\n- 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.\n\n#### Available protections\n\nTaking into consideration the same assumption in the following [example](Server_Side_Request_Forgery_Prevention_Cheat_Sheet.md#example) for the following sections.\n\n##### Application layer\n\nLike for the case [n°1](Server_Side_Request_Forgery_Prevention_Cheat_Sheet.md#case-1---application-can-send-request-only-to-identified-and-trusted-applications), it is assumed that the `IP Address` or `domain name` is required to create the request that will be sent to the *TargetApplication*.\n\nThe first validation on the input data presented in the case [n°1](Server_Side_Request_Forgery_Prevention_Cheat_Sheet.md#application-layer) 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\n> **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.\n\n**Validation flow (if one the validation steps fail then the request is rejected):**\n\n1. 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\u002Fregex mentioned in this [section](Server_Side_Request_Forgery_Prevention_Cheat_Sheet.md#application-layer).\n2. The second validation will be applied against the IP address or domain name of the *TargetedApplication* using the following block-list approach:\n   - For IP address:\n     - The application will verify that it is a public one (see the hint provided in the next paragraph with the python code sample).\n   - For domain name:\n        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.\n        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](#domain-name); checking DNS answers separately does not prevent DNS rebinding.\n3. 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`).\n4. 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}`.\n5. 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}`.\n6. The application will receive and validate (from a security point of view) any business data needed to perform a valid call.\n7. 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](https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FHTTP\u002FRedirections) in the web client used*).\n\n##### Network layer\n\nSimilar to the following [section](Server_Side_Request_Forgery_Prevention_Cheat_Sheet.md#network-layer).\n\n## IMDSv2 in AWS\n\nIn 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).\n\n[IMDSv2](https:\u002F\u002Faws.amazon.com\u002Fblogs\u002Fsecurity\u002Fdefense-in-depth-open-firewalls-reverse-proxies-ssrf-vulnerabilities-ec2-instance-metadata-service\u002F) is an additional defense-in-depth mechanism for AWS that mitigates some of the instances of SSRF.\n\nTo leverage this protection migrate to IMDSv2 and disable old IMDSv1. Check out [AWS documentation](https:\u002F\u002Fdocs.aws.amazon.com\u002FAWSEC2\u002Flatest\u002FUserGuide\u002Finstancedata-data-retrieval.html) for more details.\n\n## Deny-list (Last Resort)\n\n**Deny-lists are bypass-prone. Prefer allow-lists.**\n\n**When unavoidable, block these minimum ranges:**\n\n| Service | Block IPs\u002FDomains |\n|---------|-------------------|\n| **AWS IMDS** | `169.254.169.254`, `metadata.amazonaws.com` |\n| **GCP Metadata** | `metadata.google.internal`, `169.254.169.254` |\n| **Azure IMDS** | `169.254.169.254` |\n| **Localhost** | `127.0.0.0\u002F8`, `0.0.0.0\u002F8`, `::1\u002F128` |\n| **RFC1918 Private** | `10.0.0.0\u002F8`, `172.16.0.0\u002F12`, `192.168.0.0\u002F16` |\n| **IPv6 Unique Local** | `fc00::\u002F7` ([RFC 4193](https:\u002F\u002Fdatatracker.ietf.org\u002Fdoc\u002Fhtml\u002Frfc4193#section-3.1)) |\n| **IPv6 Link-Local** | `fe80::\u002F10` ([RFC 4291](https:\u002F\u002Fdatatracker.ietf.org\u002Fdoc\u002Fhtml\u002Frfc4291#section-2.5.6)) |\n| **Multicast** | `224.0.0.0\u002F4`, `ff00::\u002F8` |\n\n**Full production example:** [ComputerCraft SSRF deny-list](https:\u002F\u002Fgithub.com\u002Fcc-tweaked\u002FCC-Tweaked\u002Fblob\u002Fb9ed66983d714bcb5c6bf15b428e01a035106dbf\u002Fprojects\u002Fcore\u002Fsrc\u002Fmain\u002Fjava\u002Fdan200\u002Fcomputercraft\u002Fcore\u002Fapis\u002Fhttp\u002Foptions\u002FAddressPredicate.java#L112-L157)\n\n**Sources:**\n\n- [IANA IPv4 Special Registry](https:\u002F\u002Fwww.iana.org\u002Fassignments\u002Fiana-ipv4-special-registry\u002Fiana-ipv4-special-registry.xhtml)\n- [IANA IPv6 Special Registry](https:\u002F\u002Fwww.iana.org\u002Fassignments\u002Fiana-ipv6-special-registry\u002Fiana-ipv6-special-registry.xhtml)\n\n## Semgrep Rules\n\n[Semgrep](https:\u002F\u002Fsemgrep.dev\u002F) is a command-line tool for offline static analysis. Use pre-built or custom rules to enforce code and security standards in your codebase.\nExplore the [Semgrep rules](https:\u002F\u002Fsemgrep.dev\u002Fr?q=ssrf) for SSRF to effectively identify and investigate potential SSRF vulnerabilities.\n\n## Tools and code used for schemas\n\n- [Mermaid Online Editor](https:\u002F\u002Fmermaidjs.github.io\u002Fmermaid-live-editor) and [Mermaid documentation](https:\u002F\u002Fmermaidjs.github.io\u002F).\n- [Draw.io Online Editor](https:\u002F\u002Fwww.draw.io\u002F).\n\nMermaid code for SSRF common flow (printscreen are used to capture PNG image inserted into this cheat sheet):\n\n```text\nsequenceDiagram\n    participant Attacker\n    participant VulnerableApplication\n    participant TargetedApplication\n    Attacker->>VulnerableApplication: Crafted HTTP request\n    VulnerableApplication->>TargetedApplication: Request (HTTP, FTP...)\n    Note left of TargetedApplication: Use payload included\u003Cbr>into the request to\u003Cbr>VulnerableApplication\n    TargetedApplication->>VulnerableApplication: Response\n    VulnerableApplication->>Attacker: Response\n    Note left of VulnerableApplication: Include response\u003Cbr>from the\u003Cbr>TargetedApplication\n```\n\nDraw.io schema XML code for the \"[case 1 for network layer protection about flows that we want to prevent](..\u002Fassets\u002FServer_Side_Request_Forgery_Prevention_Cheat_Sheet_Case1_NetworkLayer_PreventFlow.xml)\" schema (printscreen are used to capture PNG image inserted into this cheat sheet).\n\n## References\n\n- [CWE-918: Server-Side Request Forgery (SSRF)](https:\u002F\u002Fcwe.mitre.org\u002Fdata\u002Fdefinitions\u002F918)\n- [WHATWG URL Standard: URL Parsing](https:\u002F\u002Furl.spec.whatwg.org\u002F#url-parsing)\n- [Amazon EC2: Access Instance Metadata](https:\u002F\u002Fdocs.aws.amazon.com\u002FAWSEC2\u002Flatest\u002FUserGuide\u002Finstancedata-data-retrieval.html)\n",{},"main","5d0a87185c0e172c3901e2a6e5916b6d544b8b94","skills\u002Fserver-side-request-forgery-prevention-cheat-sheet","https:\u002F\u002Fgithub.com\u002FJunMystery\u002FAgent-Guidance","https:\u002F\u002Fgithub.com\u002FJunMystery","AI is the typist, I am the architect. Building efficient, secure, and user-centric products through disciplined AI prompt engineering.","2026-11-13 17:41:25",{"source":7,"skillName":5,"commands":30,"prompt":40,"zipAvailable":41},[31,34,37],{"tool":32,"command":33},"npx","npx skills add JunMystery\u002FAgent-Guidance --skill server-side-request-forgery-prevention-cheat-sheet",{"tool":35,"command":36},"bunx","bunx skills add JunMystery\u002FAgent-Guidance --skill server-side-request-forgery-prevention-cheat-sheet",{"tool":38,"command":39},"pnpm","pnpm dlx skills add JunMystery\u002FAgent-Guidance --skill server-side-request-forgery-prevention-cheat-sheet","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FJunMystery\u002FAgent-Guidance\u002Fblob\u002Fmain\u002Fskills\u002Fserver-side-request-forgery-prevention-cheat-sheet\u002FSKILL.md",true,[],[44,50,56,62,68,74],{"skillNo":45,"name":46,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":47,"categories":48,"keywords":49,"publishedAt":18},"01M79QR1E309VJ6CTQKB32AWWA","zero-trust-architecture-cheat-sheet","https:\u002F\u002Fgithub.com\u002FJunMystery\u002FAgent-Guidance\u002Fblob\u002Fmain\u002Fskills\u002Fzero-trust-architecture-cheat-sheet\u002FSKILL.md",[],[],{"skillNo":51,"name":52,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":53,"categories":54,"keywords":55,"publishedAt":18},"01M79QQV7GHYR02DYGN41S23WN","xss-filter-evasion-cheat-sheet","https:\u002F\u002Fgithub.com\u002FJunMystery\u002FAgent-Guidance\u002Fblob\u002Fmain\u002Fskills\u002Fxss-filter-evasion-cheat-sheet\u002FSKILL.md",[],[],{"skillNo":57,"name":58,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":59,"categories":60,"keywords":61,"publishedAt":18},"01M79QQNSN7YB8XB61FFSCS3WH","xs-leaks-cheat-sheet","https:\u002F\u002Fgithub.com\u002FJunMystery\u002FAgent-Guidance\u002Fblob\u002Fmain\u002Fskills\u002Fxs-leaks-cheat-sheet\u002FSKILL.md",[],[],{"skillNo":63,"name":64,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":65,"categories":66,"keywords":67,"publishedAt":18},"01M79QQHZZJKFDY9132G5A65C2","xpath-injection-prevention-cheat-sheet","https:\u002F\u002Fgithub.com\u002FJunMystery\u002FAgent-Guidance\u002Fblob\u002Fmain\u002Fskills\u002Fxpath-injection-prevention-cheat-sheet\u002FSKILL.md",[],[],{"skillNo":69,"name":70,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":71,"categories":72,"keywords":73,"publishedAt":18},"01M79QQB9ZSD3DJG8658EHQXR1","xml-security-cheat-sheet","https:\u002F\u002Fgithub.com\u002FJunMystery\u002FAgent-Guidance\u002Fblob\u002Fmain\u002Fskills\u002Fxml-security-cheat-sheet\u002FSKILL.md",[],[],{"skillNo":75,"name":76,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":77,"categories":78,"keywords":79,"publishedAt":18},"01M79QQ60WKV7PFMXWT9CW8PM6","xml-external-entity-prevention-cheat-sheet","https:\u002F\u002Fgithub.com\u002FJunMystery\u002FAgent-Guidance\u002Fblob\u002Fmain\u002Fskills\u002Fxml-external-entity-prevention-cheat-sheet\u002FSKILL.md",[],[]]