← blog

Reverse Proxies, Reverse Tunnels, and Egress Abuse

"Proxy" and "tunnel" get used interchangeably, and the result is that people defend against the wrong thing. The distinction matters because an attacker pivoting out of a network and a webhook hitting your laptop run on nearly the same plumbing — the difference is who opened the connection, and which direction the request flows over it.

Forward proxy vs reverse proxy vs reverse tunnel

A forward proxy sits in front of clients. Your machines send their outbound requests to it, and it talks to the internet on their behalf. The destination server sees the proxy, not you. Corporate web gateways and anonymizing proxies are forward proxies.

A reverse proxy sits in front of servers. Clients on the internet hit it, and it forwards their requests to one of the origin servers behind it. The client never sees the origin. This is the CDN / WAF / load-balancer pattern — TLS terminates here, the real server IP stays hidden, and traffic is filtered or balanced before it reaches anything that matters. Cloudflare's reverse proxy explainer puts it cleanly: a forward proxy ensures no origin server talks directly to the client, a reverse proxy ensures no client talks directly to the server.

A reverse tunnel is the odd one out. It is not really a proxy role — it is a way to publish a service that has no public address. The machine you want to reach makes an outbound connection to a relay, and that connection is held open. When a request later arrives at the relay's public URL, it is pushed back down the existing connection. ngrok, Cloudflare Tunnel, and SSH remote forwarding all work this way. Nothing inbound is ever opened on the private host.

Why reverse tunnels exist: NAT and firewalls

Most machines cannot accept inbound connections. They sit behind NAT, behind a firewall, or simply have no routable IP. But almost every network lets outbound connections leave, especially on 80 and 443. A reverse tunnel turns that asymmetry into a feature: the private host dials out, and the relay reuses that channel for inbound traffic. It is the same mechanism Wikipedia describes for reverse connections — firewalls block inbound on closed ports but rarely block outbound, so you reverse who initiates.

The legitimate uses are everywhere: receiving webhooks from a service that can only POST to a public URL, demoing a localhost build to someone elsewhere, testing OAuth callbacks, or giving a CI job somewhere to send results. Our tunnel tool does exactly this — a temporary public URL on exl.ink that relays plain HTTP to your machine, no port-forwarding and no account. It is HTTP-only and short-lived by design, so it suits a callback you need to catch this afternoon, not a service you intend to keep running.

The same plumbing, weaponized

Here is why blue teams care: a reverse tunnel and a reverse shell are the same trick with different payloads. A reverse shell is a compromised host dialing out to an attacker and exposing a command line over that connection — preferred precisely because outbound is rarely restricted. Wrap that channel in HTTP or HTTPS and you have command-and-control that blends into normal web traffic.

MITRE ATT&CK splits this into two techniques worth knowing by number. T1090 (Proxy) covers using an intermediary to relay C2 traffic so victims never connect to attacker infrastructure directly — including chaining multiple hops and abusing CDN routing. T1572 (Protocol Tunneling) covers encapsulating one protocol inside another — SSH port forwarding, or tunneling RDP or SMB over HTTPS — to evade filtering and reach systems that should be unreachable. ngrok itself shows up in that technique's documented procedures.

The two combine. An attacker on a locked-down host runs an ngrok-style relay outbound, then forwards internal services back through it, pivoting deeper into a network that never permitted inbound access. Domain fronting (T1090.004) pushes this further by putting one domain in the TLS SNI and a different one in the HTTP Host header, so traffic appears destined for an innocuous CDN-hosted site while the CDN routes it to the real target. A "domainless" variant leaves the SNI blank to survive CDNs that try to match the two fields.

The defining question is never the protocol. It is which side opened the socket, and whether the channel carries what the protocol claims to carry.

How blue teams detect it

Detection starts with egress filtering — deciding what is allowed to leave, not just what is allowed in. A network that blocks direct outbound and forces web traffic through an inspecting proxy denies reverse shells and tunneling clients their easy exit. Default-deny outbound is the single highest-leverage control here; everything below is what you watch for once the policy is in place.

  • Long-lived outbound connections that stay open with low, steady chatter — the heartbeat of a held-open tunnel rather than a normal request/response.
  • Asymmetric data flow: small requests out and large responses back over a channel that should look like ordinary browsing, or the reverse during exfiltration.
  • Connections to relay and tunneling providers (ngrok-style services), or to freshly registered domains and raw IPs with no DNS history.
  • TLS where the SNI and the HTTP Host disagree, or the SNI is blank — the domain-fronting tell.
  • Endpoint signals: processes like ssh, plink, socat, or netsh making outbound connections they have no reason to make.
  • DNS lookups for a domain with no following HTTP request, which often means egress filtering stopped a tunnel attempt — and is itself worth alerting on.

Testing your own egress, honestly

If you want to know whether your network actually contains outbound traffic, the test is to try to phone home and watch whether the call lands. A request bin gives you a neutral public endpoint that logs every request it receives — headers, body, source — so you can fire one from inside a segment and see whether it escaped. You can spin one up at bin, point a curl or an app at the capture URL, and check what arrives. The same out-of-band pattern underpins blind-SSRF detection, as in PortSwigger's out-of-band SSRF lab: send a unique URL into the target, then watch a collector for the callback.

curl -fsS https://exl.ink/api/bin/new   # returns a captureUrl; POST to it from inside the segment, then check the bin

Be honest about what this proves. A request landing in a bin tells you that path is open; silence does not prove the network is sealed, only that this one attempt was blocked. Our tunnel and bin are deliberately limited — ephemeral, rate-limited, HTTP-only, best-effort — which makes them fine for a quick egress check or a webhook, and unsuitable as permanent ingress or anything you would build infrastructure on. That is the point: open the page, do the thing, close the tab. If we do not keep it, it cannot show up later.

Further reading