Python 100 project #36: Cisco CSR Reverse Shell

Sometimes there are a few occasions that edge network devices are not accessible from remote.  The reason varies from security reasons to environmental reasons(no global ip address is assigned and no access to the ISP router etc). In my previous project, I used code from Python Blackhat to show how Windows PC becomes a victim to expose its command access. In this time, I use this similar code for good.

This can be used to deploy periodically or can be triggered by syslog messages via EEM.

 

Output Example:

[ In the Server ]

# python3 cisco_revshell_server.py $(hostname -I) 2022
[+] Listening for connection ...
[+] Got a connection!
[+] Authenticated!
connected to us-east-1_e-rtr-01 
us-east-1_e-rtr-01(guestshell)# ls -l /bootflash     
total 406928
drwxr-xr-x  2      65534      65534      4096 May 28 06:20 CRDU
drwxr-xr-x  3      65534      65534      4096 May 28 06:19 core
-rw-r-----  1      65534      65534 377189336 Jan 17 19:31 csr1000v-mono-universalk9.16.07.01a.SPA.pkg
-rw-r--r--  1      65534      65534  38986404 Jan 17 19:31 csr1000v-rpboot.16.07.01a.SPA.pkg
-rw-r--r--  1      65534      65534       157 Jun  3 20:51 csrlxc-cfg.log
-rw-r--r--  1      65534      65534         0 Jun  3 20:51 cvac.log
drwxrwxr-x  2 guestshell guestshell      4096 Jun  3 22:57 gs_scripts
-rw-r--r--  1      65534      65534       174 Jun  3 20:50 iid_check.log
drwxr-xr-x 18      65534      65534      4096 Jun  3 20:51 iox
drwx------  2      65534      65534     16384 Jan 17 19:30 lost+found
-rw-r--r--  1      65534      65534        16 May 28 06:20 ovf-env.xml.md5
-rw-r--r--  1      65534      65534      1967 Jan 17 19:31 packages.conf
drwxr-xr-x  2      65534      65534      4096 Jun  3 22:30 syslog
-rw-r--r--  1      65534      65534        30 Jun  3 20

us-east-1_e-rtr-01(guestshell)# python -c "import cli;cli.executep('show ip route')"
Codes: L - local, C - connected, S - static, R - RIP, M - mobile, B - BGP
       D - EIGRP, EX - EIGRP external, O - OSPF, IA - OSPF inter area 
       N1 - OSPF NSSA external type 1, N2 - OSPF NSSA external type 2
       E1 - OSPF external type 1, E2 - OSPF external type 2
       i - IS-IS, su - IS-IS summary, L1 - IS-IS level-1, L2 - IS-IS level-2
       ia - IS-IS inter area, * - candidate default, U - per-user static route
       o - ODR, P - periodic downloaded static route, H - NHRP, l - LISP
       a - application route
       + - replicated route, % - next hop override, p - overrides from PfR
Gateway of last resort is 172.31.1.1 to network 0.0.0.0
S*    0.0.0.0/0 [1/0] via 172.31.1.1, GigabitEthernet1
      1.0.0.0/32 is subnetted, 1 subnets
C        1.1.1.1 is directly connected, Loopback0
      10.0.0.0/8 is variably subnetted, 4 subnets, 2 masks
C        10.0.0.0/24 is directly connected, VirtualPortGroup0
L        10.0.0.254/32 is directly connected, VirtualPortGroup0
C        10.10.10.0/24 is dir
us-east-1_e-rtr-01(guestshell)# exit
exiting
[-] Caught exception: exit

 

Here is the code:

[ In Cisco Box ] cisco_revshell_client.py

import sys

import paramiko
import subprocess

def ssh_command(ip, user, passwd, port, command):
    client = paramiko.SSHClient()
    client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    client.connect(ip, username=user, password=passwd, port=port)
    ssh_session = client.get_transport().open_session()
    if ssh_session.active:
        ssh_session.send(command)
        print(ssh_session.recv(1024) + b'%s' % ip)
        while True:
            command = ssh_session.recv(1024)
            try:
                cmd_output = subprocess.check_output(command, shell=True)
                ssh_session.send(cmd_output)
            except Exception as e:
                ssh_session.send(str(e))
        client.close()
    return

if __name__ == "__main__":
    if len(sys.argv) != 2:
        print "Usage: %s 'hostname'" % sys.argv[0]
        sys.exit(0)
    ssh_command('XX.XX.XX.XX', 'cisco-rtr', 'passw0rd', 2022, sys.argv[1])

 

[ In the Server ] cisco_revshell_server.py

import socket
import threading
import paramiko
import sys

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

    def check_auth_password(self, username, password):
        if (username == 'cisco-rtr') and (password == 'passw0rd'):
            return paramiko.AUTH_SUCCESSFUL
        return paramiko.AUTH_FAILED


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 as e:
    print(f"[-] Listen failed: {str(e)}")
    sys.exit(1)

print("[+] Got a connection!")

try:
    bhSession = paramiko.Transport(client)
    bhSession.add_server_key(host_key)
    server = Server()
    try:
        bhSession.start_server(server=server)
    except paramiko.SSHException as x:
        print("[-] SSH negotiation failed.")
    chan = bhSession.accept(20)
    print("[+] Authenticated!")
    hostname = str(chan.recv(1024), 'utf-8')
    print(f"connected to {hostname}.")
    chan.send('remote connection is created to ')
    while True:
        try:
            command = input(f"{hostname}# ")
            if command != 'exit':
                chan.send(command)
                print(str(chan.recv(1024),'utf-8'))
            else:
                chan.send('exit')
                print('exiting')
                bhSession.close()
                raise Exception('exit')
        except KeyboardInterrupt:
            bhSession.close()
except Exception as e:
    print(f"[-] Caught exception: {str(e)}")
    try:
        bhSession.close()
    except:
        pass
    sys.exit(1)