# hakoriginfinder: locating the origin server behind a proxy

> hakoriginfinder compares the response of a hostname against the responses of candidate IP addresses and flags the ones that look like the origin. It helps a team check whether its own infrastructure is reachable around a WAF, and it is a blunt instrument that produces a similarity score, not proof.

**hakluke/hakoriginfinder** — Tool for discovering the origin host behind a reverse proxy. Useful for bypassing cloud WAFs!

- Repository: https://github.com/hakluke/hakoriginfinder
- Stars: 1,107 · Forks: 141
- Language: Go
- License: not declared
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/hakluke-hakoriginfinder

## The question behind the tool: can the origin be reached directly

A site fronted by a reverse proxy or a web application firewall is only as protected as the addresses the origin answers on. If the origin server also accepts direct connections at a predictable address, then every rule the proxy enforces can be sidestepped by pointing a client at that address instead, with a Host header naming the real site.

hakoriginfinder is written to answer exactly that question for your own estate. Its stated purpose is discovering the origin host behind a reverse proxy, and the README is explicit that it is useful for bypassing WAFs and other reverse proxies. The intended reading is a self test: you have a deployment, you want to know whether a determined client can find the address behind it, and this tool does the searching for you.

The implementation is a single Go file, `hakoriginfinder.go`, alongside a `go.mod` and a README. The module declares Go 1.18, which is a low floor and means the tool builds with any reasonably current toolchain. The author is Luke Stephens of Haksec, and the README points to related Haksec businesses covering penetration testing, social engineering, OSINT, content marketing and bug bounty triage.

## Fetch the target once, then replay the Host header everywhere else

The mechanism has three steps and none of them is clever, which is exactly why it works.

First, the tool makes a single HTTP request to the hostname or URL you provide and stores the response. That stored response is the reference. Second, it makes a request to every IP address you supply, over HTTP on port 80 and HTTPS on port 443 by default, with the Host header set to the original host and port. The point of the Host header is that the candidate server receives a request that looks like the legitimate one, so it answers with the real content rather than a generic refusal.

Third, each candidate response is compared with the stored one using the Levenshtein algorithm, and if the response is similar enough it is deemed a match.

The comparison is on the whole response, which the output example makes visible. Every candidate that returns an unrelated body scores 56290 in the sample run, while the two lines flagged as MATCH score 0, meaning the response was identical. That the default threshold is 5 means the tool accepts responses that differ by up to five edit steps, which covers a missing trailing newline or a slightly different header ordering without accepting a genuinely different site.

## The whole command line surface is four options

The interface is small enough to learn in a minute, and every option exists to control the size or the strictness of the search.

The target is set with `-h`, and there is no default. The candidate list arrives on standard input rather than as an argument, which is what lets it scale to a whole network range. The ports to try for those addresses are set with `-p`, defaulting to 80,443. The Levenshtein threshold is `-l`, default 5, where a lower number requires the matches to be more similar before they count. The number of threads is `-t`, default 32, which is what makes a scan of a large range practical.

The canonical invocation pipes a prefix list generator into the tool:

```
prips 93.184.216.0/24 | hakoriginfinder -h https://example.com:443/foo
```

The project documentation does not describe the `prips` helper, so treat it as the example it is: something that prints IP addresses, one per line, on standard input. Any tool that produces that will work, including a list you generated yourself. A wider port list works the same way, as in the sample output where 80,443,8080 and 8443 are all probed.

## Reading the three column output without over-reading it

The output format is three space separated columns. The first is either MATCH or NOMATCH depending on whether the Levenshtein threshold was reached. The second is the URL being tested. The third is the score.

The sample run in the documentation shows what a normal result looks like. Almost every candidate against a public resolver returns NOMATCH with a high score, and the address that actually serves the site returns MATCH with a score of 0 on both the 443 and 8443 probes. The redirect lines at the top of the sample, a 301 and then a 308, show the tool following the hostname to its canonical form before it starts comparing.

The score is a distance, not a percentage, and the raw number can be large. A score of 56290 means the candidate body differs from the reference by that many edit operations, which is what you would expect when comparing a real site against a default error page from a completely different server. Two candidates with the same large score, in this example, are almost certainly the same generic response, and a run against a range where most addresses answer with a stock landing page would show a band of identical high scores rather than a single outlier.

That is the practical reason to treat MATCH as a lead, not a verdict. The tool tells you where to look next.

## Building and installing from source with the Go toolchain

The installation instruction is short: install Go, then run the install command for the module path.

```
go install github.com/hakluke/hakoriginfinder@latest
```

That is the whole procedure, and it works because the repository is a normal Go module with a `go.mod` naming `github.com/hakluke/hakoriginfinder` and a language version of 1.18. The `@latest` suffix resolves the newest tagged or pseudo-version available to the module proxy, so the binary lands in the Go binary directory, normally `GOBIN` or `$GOPATH/bin`, and has to be on your PATH.

There is no other build story documented. No Makefile, no container recipe, and no prebuilt binaries are mentioned, so installing from a checkout with `go build` in the repository root is the fallback for anyone who wants to pin a specific commit rather than whatever latest resolves to.

The repository is unusually small for a security tool: `.gitignore`, `README.md`, `go.mod` and `hakoriginfinder.go`. There is no test directory and no CI configuration in the top level entries, so the correctness of the matching logic rests on the algorithm rather than on an accessible test suite.

## No releases, no stated licence, and a recent last push

Two facts about the project's packaging are worth knowing before you build a process around it.

The first is that the repository has no GitHub releases. There are no tagged binaries to download, so `go install` is not just the documented path, it is the only path the documentation describes. Version pinning therefore means pinning a commit, and the `@latest` form in the install command deliberately does not do that.

The second is the licence, which is reported as unknown. There is no LICENSE file among the top level entries, which is `.gitignore`, `README.md`, `go.mod` and `hakoriginfinder.go`. That is an unusual position for a tool people are asked to point at their own infrastructure, and it is a concrete thing to check before you redistribute the binary or vendor the source into anything you ship. This article does not give legal advice, and the answer is to ask the author or to look for the terms on the author's own site.

On activity, the last push to the main branch was on 2026-08-05, which is recent enough that the tool is not an abandoned script. The README carries a link to the author's blog and the Haksec companies, and the author writes about the security industry. What is missing is any sign of releases, changelog or issue history in the repository layout.

## Where this approach stops working

The method has clear boundaries, and they are worth stating because the tool's simplicity hides them.

The comparison is only as good as the reference response. A single stored response is compared against every candidate, so any per-request variation in the origin, a rotating nonce, a timestamp, a random banner, an A/B test bucket, or a load balancer that picks different backends, pushes the score up for the true origin as well. A strict deployment behind a WAF that injects a unique request identifier into the body will score badly against itself.

The Host header trick also fails in a specific case. If the origin validates the header and rejects requests that do not arrive through the proxy, the candidate returns an error rather than the content, and the score tells you nothing. A site that binds to a specific source network, or that requires a client certificate, will look like a mismatch.

Finally, the input is a list of addresses, so the tool cannot find an origin that is not in that list. An origin on a different network, behind another layer, or on a provider that uses anycast addressing will not be covered by a scan of one range. A different approach is needed for those cases, and the tool does not attempt one.

## Conclusion

hakoriginfinder is worth running against infrastructure you own, because the direct question it answers, is the origin reachable if I know the address, is exactly the question a WAF deployment leaves open. Do not treat a MATCH line as proof, since the verdict is a Levenshtein threshold on a response body and a reverse proxy can emit a near identical error page for every request. Before you rely on the output, run the same comparison with `-l` tightened to a smaller threshold and confirm the candidate address by hand, and note that the repository has no release tags and no stated licence, so read the LICENSE situation yourself before redistributing the binary.

## FAQ

### How do I install hakoriginfinder?

Install Go, then run `go install github.com/hakluke/hakoriginfinder@latest`. The binary is placed in the Go binary directory, which has to be on your PATH.

### How do I run hakoriginfinder against a range of IP addresses?

Pipe a list of IP addresses into the tool on standard input and pass the target with the `-h` option, for example `prips 93.184.216.0/24 | hakoriginfinder -h https://example.com:443/foo`. The candidate list always comes from stdin, since there is no option for it.

### What does a MATCH line in hakoriginfinder output mean?

The first column is MATCH when the response from that address was similar enough to the reference response, using the Levenshtein distance threshold set by `-l`, which defaults to 5. The third column is the score itself, where 0 means the responses were identical.

### How do I change the ports and the strictness of a hakoriginfinder scan?

The `-p` option sets the ports to try for the addresses given on stdin, defaulting to 80,443, and `-l` sets the Levenshtein distance threshold, defaulting to 5, where a lower number requires closer similarity. The `-t` option sets the thread count, which defaults to 32.

## Sources

- [hakluke/hakoriginfinder on GitHub](https://github.com/hakluke/hakoriginfinder)
- [Issues](https://github.com/hakluke/hakoriginfinder/issues)
- [README](https://github.com/hakluke/hakoriginfinder/blob/main/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/hakluke-hakoriginfinder
