I'm trying to use paramiko to bounce an SSH session via netcat:
 MyLocalMachine ----||----> MiddleMachine --(netcat)--> AnotherMachine
 ('localhost')  (firewall)   ('1.1.1.1')                 ('2.2.2.2')
MyLocalMachine to
AnotherMachine
MiddleMachine will not accept any attempts to open a direct-tcpip channel connected to AnotherMachine
sshpass
PExpect
I can achieve this partially using the following code:
cli = paramiko.SSHClient()
cli.set_missing_host_key_policy(paramiko.AutoAddPolicy())
proxy = paramiko.ProxyCommand('ssh [email protected] nc 2.2.2.2 22')
cli.connect(hostname='2.2.2.2', username='user', password='pass', sock=proxy)
The thing is, that because ProxyCommand is using subprocess.Popen to run the given command, it is asking me to give the password "ad-hoc", from user input (also, it requires the OS on MyLocalMachine to have ssh installed - which isn't always the case). 
Since ProxyCommand's methods (recv, send) are a simple bindings to apropriate POpen methods, I was wondering if it would be possible to trick paramiko client into using another client's session as the proxy?
This example shows how to use paramiko to connect to [email protected] using the SSH key stored in ~/.ssh/id_ed25519, execute the command ls and print its output. Since paramiko is a pure Python implementation of SSH, this does not require SSH clients to be installed.
This should support most 2FA / MFA infrastructure approaches to SSH authentication. The keyboard-interactive authentication handler is injected, permitting easy integration with more advanced use cases. In this example, we use keyboard-interactive authentication on the Jump Host, and we tell Paramiko to 'auto add' (and accept) unknown Host Keys.
Paramiko is a Python module that implements the SSHv2 protocol. Paramiko is not part of Python’s standard library, although it’s widely used. This guide shows you how to use Paramiko in your Python scripts to authenticate to a server using a password and SSH keys. If you have not already done so, create a Linode account and Compute Instance.
If your system is configured to use Anaconda, you can use the following command to install Paramiko: This section shows you how to authenticate to a remote server with a username and password. To begin, create a new file named first_experiment.py and add the contents of the example file.
Update 15.05.18: added the missing code (copy-paste gods haven't been favorable to me).
TL;DR: I managed to do it using simple exec_command call and a class that pretends to be a sock. 
To summarize:
nc) installed on the proxy host - although anything that can provide basic netcat functionality (moving data between a socket and stdin/stdout) will work.So, here be the solution:
The following code defines a class that can be used in place of paramiko.ProxyCommand. It supplies all the methods that a standard socket object does. The init method of this class takes the 3-tupple that exec_command() normally returns:
Note: It was tested extensively by me, but you shouldn't take anything for granted. It is a 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()                                                                                                                            
The following shows how I used the above class to solve my problem:
# 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')
Et voila.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With