小编典典

Paramiko:将ssh会话嵌套到另一台计算机,同时保留paramiko功能(ProxyJump)

python

我正在尝试paramiko通过netcat弹回SSH会话:

 MyLocalMachine ----||----> MiddleMachine --(netcat)--> AnotherMachine
 ('localhost')  (firewall)   ('1.1.1.1')                 ('2.2.2.2')
  • 没有从MyLocalMachine到的 直接连接AnotherMachine
  • 开启的SSH服务器MiddleMachine将不接受任何尝试打开与之连接的Direct-tcpip通道的尝试AnotherMachine
  • 我不能使用SSH密钥 。我只能通过给定的用户名和密码进行连接。
  • 我不能用 sshpass
  • 我不能用 PExpect
  • 我想自动连接
  • 我想保留所有paramiko功能

我可以使用以下代码部分实现此目的:

cli = paramiko.SSHClient()
cli.set_missing_host_key_policy(paramiko.AutoAddPolicy())
proxy = paramiko.ProxyCommand('ssh user@1.1.1.1 nc 2.2.2.2 22')
cli.connect(hostname='2.2.2.2', username='user', password='pass', sock=proxy)

问题是,因为ProxyCommand使用subprocess.Popen运行给定的命令,它要求我给密码“特设”,从用户输入(也,它需要在操作系统MyLocalMachinessh安装-
这是情况并非总是如此)。

由于ProxyCommand的方法(recvsend)是对适当POpen方法的简单绑定,我想知道是否有可能欺骗paramiko客户端使用另一个客户端的会话作为代理?


阅读 219

收藏
2021-01-20

共1个答案

小编典典

更新15.05.18:添加了丢失的代码(复制粘贴的上帝对我不利)。

TL; DR:我设法通过简单的exec_command调用和一个伪装成a的类来做到这一点sock

总结一下:

  • 该解决方案不使用22以外的任何其他端口。如果您可以通过嵌套ssh客户端手动连接到计算机,则它将起作用。它不需要任何端口转发或配置更改。
  • 它无需提示输入密码即可工作(一切都是自动的)
  • 它嵌套ssh会话,同时保留paramiko功能。
  • 您可以根据需要嵌套会话多次
  • 它需要nc在代理主机上安装netcat() -尽管可以提供基本netcat功能(在套接字和stdin / stdout之间移动数据)的任何设备都可以使用。

因此,这里是解决方案:

假面舞会

以下代码定义了一个可以代替的类paramiko.ProxyCommand。它提供了标准socket对象执行的所有方法。此类的init方法采用exec_command()通常返回的3-tupple

注意:它已经由我进行了广泛的测试,但是您不应认为任何事情都是理所当然的。 这是一个hack。

import paramiko
import time
import socket     
from select import select


class ParaProxy(paramiko.proxy.ProxyCommand):                      
    def __init__(self, stdin, stdout, stderr):                             
        self.stdin = stdin                                                 
        self.stdout = stdout                                               
        self.stderr = stderr
        self.timeout = None
        self.channel = stdin.channel

    def send(self, content):                                               
        try:                                                               
            self.stdin.write(content)                                      
        except IOError as exc:                                             
            raise socket.error("Error: {}".format(exc))                                                    
        return len(content)

    def recv(self, size):                                                  
        try:
            buffer = b''
            start = time.time()

            while len(buffer) < size:
                select_timeout = self._calculate_remaining_time(start)
                ready, _, _ = select([self.stdout.channel], [], [],
                                     select_timeout)
                if ready and self.stdout.channel is ready[0]:
                      buffer += self.stdout.read(size - len(buffer))

        except socket.timeout:
            if not buffer:
                raise

        except IOError as e:
            return ""

        return buffer

    def _calculate_remaining_time(self, start):
        if self.timeout is not None:
            elapsed = time.time() - start
            if elapsed >= self.timeout:
                raise socket.timeout()
            return self.timeout - elapsed
        return None

    def close(self):                                                       
        self.stdin.close()                                                 
        self.stdout.close()                                                
        self.stderr.close()
        self.channel.close()

用法

下面显示了如何使用上述类解决问题:

# Connecting to MiddleMachine and executing netcat
mid_cli = paramiko.SSHClient()
mid_cli.set_missing_host_key_policy(paramiko.AutoAddPolicy())
mid_cli.connect(hostname='1.1.1.1', username='user', password='pass')
io_tupple = mid_cli.exec_command('nc 2.2.2.2 22')

# Instantiate the 'masquerader' class
proxy = ParaProxy(*io_tupple)

# Connecting to AnotherMachine and executing... anything...
end_cli = paramiko.SSHClient()
end_cli.set_missing_host_key_policy(paramiko.AutoAddPolicy())
end_cli.connect(hostname='2.2.2.2', username='user', password='pass', sock=proxy)
end_cli.exec_command('echo THANK GOD FINALLY')

等等。

2021-01-20