Open-source project
assetnote/surf avatar
assetnote/surf

surf: turning a subdomain list into SSRF candidates

Escalate your SSRF vulnerabilities on Modern Cloud Environments. `surf` allows you to filter a list of hosts, returning a list of viable SSRF candidates.

761 stars58 forksGoLicense varies

At a glance

What is it?
surf is a small Go tool that takes a list of hosts, probes each one over HTTP, and splits the results into externally and internally resolving hosts so you know which ones are worth testing against a suspected server side request forgery. It is a triage helper for authorised testing, and it deliberately does no exploitation of its own.
Who is it for?
Use surf when you already hold a subdomain list for a target you are authorised to test and you need to decide which hosts are worth trying against a suspected SSRF endpoint, since it does the probing and the classification for you. Do not expect it to find the SSRF itself, and do not point it at infrastructure you have no permission to test, because the tool sends live HTTP requests to every host in the list.
Can I use it commercially?
Not without permission. GitHub finds no licence file in the repository, and without a licence all rights are reserved by default: you may read the code but not reuse it. Check the README, or ask the authors, before using it.
Is it still maintained?
Yes. The repository last received commits 107 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 25, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A host list in, a candidate list out

surf is a small Go program that filters a list of hosts and returns the ones that are viable SSRF candidates. The workflow it is built for is short: you have a set of subdomains, you believe a target application has a server side request forgery, and you need to know which of those hosts is worth pointing the vulnerable parameter at.

The tool description is explicit about the method. surf sends an HTTP request from your machine to each host in the list, collects all the hosts that did not respond, and then filters them into a list of externally facing hosts and a list of internally facing hosts.

The reasoning behind the technique is in the README and it is worth understanding before using the output. Most SSRF filters focus on internal or restricted IP ranges, so a host that resolves to a public address slips past those filters. The README says you can then attempt these hosts wherever an SSRF vulnerability may be present, and that you will be pleasantly surprised when you get SSRF on an external IP that is not accessible over HTTP or HTTPS from your machine.

The other half of the argument is about large companies with cloud environments, which often have external IP addresses pointing at internal web applications. Traditional filters do not capture those, unless the specific host has been added to a blacklist, which the README notes they usually never are.

So the output is a prioritised shortlist, not a verdict. The README is explicit that the external target list has higher chances of being viable than the internal list.

The mechanism: httpx errors plus resolution analysis

Under the hood the tool leans on httpx from ProjectDiscovery, and the README says it captures the errors returned from httpx and then performs some basic analysis to determine the most viable candidates.

That two-stage design explains the two output categories. A host that does not answer an HTTP request from your machine may be doing so for a mundane reason, such as refusing connections from outside its network, and that is exactly the situation that makes an external host interesting for SSRF: the SSRF server, sitting inside that network, can reach it while you cannot. A host that resolves to an internal address is a different case, since the ordinary internal-range filters would usually already block it, which makes it a weaker candidate even though the address is clearly private.

The module file confirms the shape of the implementation. The Go module is github.com/assetnote/surf and it requires github.com/projectdiscovery/httpx at v1.3.2, alexflint/go-arg for the command line parsing, and schollz/progressbar/v3 for a progress bar, which is what you would expect for a tool that sends thousands of requests with a concurrency setting.

The command line is handled by go-arg, and the help text in the README shows a plain, short option set with no hidden behaviour: hosts file, concurrency, timeout, retries, and two switches that turn parts of the pipeline off.

The repository itself is small. Apart from the README and the GitHub workflow configuration, it has a cmd directory for the entry point, a pkg directory for the logic, a gh-docs directory for the image used at the top of the page, a .goreleaser.yml, and the go module files.

Installing with go install and running against a list

Installation is a single Go install, and the README notes that the tool requires Go 1.19 or above because it relies on httpx for the HTTP probing.

bash
go install github.com/assetnote/surf/cmd/surf@latest

The README then walks through the case it was written for. You have subdomains for a domain in a file named bigcorp.txt, and you want to find the SSRF candidates among them. The basic invocation passes the list file with -l:

bash
# find all ssrf candidates (including external IP addresses via HTTP probing)
surf -l bigcorp.txt

Three variations follow in the README, and each one removes a stage of the pipeline. Adding timeout and concurrency settings keeps the probing bounded:

bash
# find all ssrf candidates (including external IP addresses via HTTP probing) with timeout and concurrency settings
surf -l bigcorp.txt -t 10 -c 200

The -d flag prints the hosts and nothing else, which is what you want when the output is being piped into another tool:

bash
# find all ssrf candidates (including external IP addresses via HTTP probing), and just print all hosts
surf -l bigcorp.txt -d

And -x skips the HTTP probing entirely, leaving only the hosts that resolve to an internal or private address:

bash
# find all hosts that point to an internal/private IP address (no HTTP probing)
surf -l bigcorp.txt -x

The -x mode is much faster and much less noisy, and it is the right first pass when the list is large, because it gives you the internal set quickly and without sending a request to anything.

The two output files and the settings that shape them

surf prints the candidates to stdout, and it also writes two files into the directory it was run from, each with a timestamp in the name.

The first is external-{timestamp}.txt, which holds the hosts that resolve externally but that you could not send HTTP requests to from your machine. The second is internal-{timestamp}.txt, which holds the hosts that resolve internally and that you obviously could not send HTTP requests to from your machine.

The README says these two files contain the list of hosts that are ideal SSRF candidates to try on your target, and that the external list has higher chances of being viable. That ranking is the point of the tool, and it is why the tool separates the two categories rather than printing one merged list.

The settings that shape the result are all passed down to httpx, and the help text is explicit about the defaults. Concurrency defaults to 100 threads, timeout to 3 seconds, and retries to 2. Those are reasonable defaults for a scanner pointed at a list of a few thousand hosts, and they are also the knobs to reach for when a large list runs slowly, since a longer timeout and lower concurrency trade throughput for fewer false timeouts.

The two disable flags are the other thing to understand. --disablehttpx, short form -x, leaves only the hosts that resolve to an internal IP address. --disableanalysis, short form -d, leaves only the hosts, with no analysis applied, which is the raw list rather than the candidate list.

Where surf does not help and what it will not do

The first limitation is the obvious one and it is worth saying plainly: surf does not find server side request forgeries. It does not probe an application, does not look for a parameter that fetches a URL, and does not test whether that parameter validates its input. It takes a list of hosts you already have and ranks them.

The second is that the technique depends on the target's filter being the kind the README describes. If an application blocks every request that is not a URL it recognises, or if it resolves the hostname and then checks the resulting address, the ranking in the output does not matter. The tool optimises for one common failure mode, and against a well written filter it buys nothing.

The third is scope and manners. The tool sends live HTTP requests to every host in the list, from your machine, with a concurrency of 100 by default. Pointing it at a list of hosts you are not authorised to test is both a legal problem and a noisy one. The README also says the tool was created as a result of a live hacking event for HackerOne, which is the context in which it is meant to be used.

There is also a small inconsistency worth knowing about. The README states that the tool requires Go 1.19 or above, while the module file declares go 1.18. The dependency on httpx at v1.3.2 is the practical constraint, and a recent Go toolchain satisfies both.

Doing the same triage by hand, and what surf adds

The alternative is to do the work yourself with the same underlying tools. Resolve each host in the list, send a request to it with httpx or curl, note which ones fail, and separate the internal addresses from the external ones. That is exactly what surf does, and for a list of twenty hosts it is a perfectly reasonable afternoon.

The difference shows up at two points. The first is that surf captures the errors returned by httpx and classifies them, which removes the error-prone part of the exercise, the part where a timeout, a connection refusal and a TLS error all get read slightly differently by hand and end up mixed together. The second is that the classification is what the technique needs. Surf is explicitly built to surface the hosts that resolve externally yet refuse your requests, which is the specific shape that bypasses the internal-range filters the README describes.

There is also the operational difference. A tool with a progress bar, a timeout and a concurrency setting can be pointed at a list of thousands of hosts and left to run, and it writes the two classified lists to disk with a timestamp, so a run from last week is still there next to today's.

For an authorised assessment where the target's SSRF filter is known to be a simple range check, that is a real saving, and it is a narrow saving, which is what the tool is honest about.

Editorial conclusion

Use surf when you already hold a subdomain list for a target you are authorised to test and you need to decide which hosts are worth trying against a suspected SSRF endpoint, since it does the probing and the classification for you. Do not expect it to find the SSRF itself, and do not point it at infrastructure you have no permission to test, because the tool sends live HTTP requests to every host in the list. Before you run it, confirm the list contains only in-scope hosts, decide whether you want the HTTP probing or the resolution-only path with -x, and note the maintenance position, since the last release was v0.0.5 on 2023-06-17 and the last push to the repository was on 2026-07-10.

Frequently asked questions

What does the surf tool do?

surf filters a list of hosts and returns the viable SSRF candidates. It sends an HTTP request from your machine to each host, collects the hosts that did not respond, and filters them into externally facing and internally facing hosts.

How do I install surf and run it against a list of subdomains?

Install it with go install github.com/assetnote/surf/cmd/surf@latest, which requires Go 1.19 or above, then run surf -l bigcorp.txt with the file holding your subdomains. The -t and -c flags set the timeout and concurrency, -d prints only the hosts, and -x disables the HTTP probing.

What files does surf write?

It prints the candidates to stdout and writes two files in the directory it runs from: external-{timestamp}.txt for hosts that resolve externally but refused your HTTP requests, and internal-{timestamp}.txt for hosts that resolve internally. The README states the external list has higher chances of being viable.

Does surf exploit the server side request forgery it finds?

No. The README describes surf as producing a prioritised list of hosts to attempt wherever an SSRF vulnerability may be present, and the tool itself only performs HTTP probing and classification of the results.

Official sources

  1. assetnote/surf on GitHub
  2. Issues
  3. README
  4. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/assetnote-surf.svg)](https://hysenlabs.com/projects/assetnote-surf)

Was this useful?