-
Notifications
You must be signed in to change notification settings - Fork 33
/
gitlab_version.nse
158 lines (126 loc) · 4.45 KB
/
gitlab_version.nse
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
local stdnse = require "stdnse"
local shortport = require "shortport"
local http = require "http"
local string = require "string"
local table = require "table"
local json = require "json"
description =
[[
Try to guess GitLab version using a dictionary of static assets hashes.
Works for versions >= 9.x, can return multiple versions for a give instance.
]]
---
-- @usage nmap <target> -p PORT --script gitlab_version.nse [--script-args="showcves"]
---
categories = {"safe", "version"}
author = "Luciano Righetti"
license = "Apache License 2.0"
portrule = shortport.service({"http", "https"})
local cve_meta = {
__tostring = function(me)
return ("%s\t%s\t%s"):format(me.id, me.cvss, me.href)
end
}
local function get_hashes_map()
local response = http.get_url("https://raw.githubusercontent.com/righel/gitlab-version-nse/main/gitlab_hashes.json", {max_body_size = -1})
if response.status == 200 then
_, hashes = json.parse(response.body)
return hashes
end
return nil
end
action = function(host, port)
local options = {scheme = port.service, max_body_size = -1}
manifest_url = "/assets/webpack/manifest.json"
if stdnse.get_script_args("subdir") then
manifest_url = stdnse.get_script_args("subdir") .. manifest_url
end
local response = http.generic_request(host.targetname or host.ip, port, "GET", manifest_url, options)
local manifest_hash = string.match(response["rawbody"], '"hash": "([%w]*)"')
login_url = "/users/sign_in"
if stdnse.get_script_args("subdir") then
login_url = stdnse.get_script_args("subdir") .. login_url
end
local response = http.generic_request(host.targetname or host.ip, port, "GET", login_url, options)
local commit_hash = string.match(response["rawbody"], 'gon.revision="([%w]*)"')
if manifest_hash == nil and commit_hash == nil then
return "ERROR: GitLab instance not found or running version < 9.x"
end
local banner = get_banner(manifest_hash, commit_hash)
if banner == nil then
return "ERROR: GitLab hash not found in map: webpack_hash:" .. manifest_hash .. ", commit_hash:" .. commit_hash
end
local build = banner["build"]
local versions = banner["versions"]
local edition = "*"
if (build == "gitlab-ce") then
edition = "community"
end
if (build == "gitlab-ee") then
edition = "enterprise"
end
local output = {}
if manifest_hash ~= nil then
for _, version in ipairs(versions) do
local cpe = ("cpe:/a:gitlab:gitlab:%s:*:*:*:%s"):format(version, edition)
r = {
version = version,
edition = edition
}
if stdnse.get_script_args("showcves") then
r["cves"] = get_vulners_results(build, version)
end
output[cpe] = r
end
end
return output
end
function get_banner(manifest_hash, commit_hash)
local manifest_hashes_map = get_hashes_map()
if manifest_hashes_map == nil then
return nil
end
-- search for commit hash
for key, value in pairs(manifest_hashes_map) do
if type(key) == "string" and key:sub(1, #commit_hash) == commit_hash then
return value
end
end
-- search for webpack manifest hash
local banner = manifest_hashes_map[manifest_hash]
if banner == nil then
return nil
end
return banner
end
function get_vulners_results(build, version)
local api_version = "1.7"
local option = {
header = {
["User-Agent"] = string.format("Vulners NMAP Plugin %s", api_version),
["Accept-Encoding"] = "gzip, deflate"
},
any_af = true
}
local api_endpoint = "https://vulners.com/api/v3/burp/software/"
response =
http.get_url(("%s?software=%s&version=%s&type=%s"):format(api_endpoint, "gitlab", version, "software"), option)
_, vulns = json.parse(response.body)
if not vulns or not vulns.data or not vulns.data.search then
return
end
local output = {}
for _, vuln in ipairs(vulns.data.search) do
if vuln._source.type == "cve" then
local v = {
id = vuln._source.id,
type = vuln._source.type,
href = vuln._source.href,
cvss = vuln._source.cvss.score
}
setmetatable(v, cve_meta)
output[#output + 1] = v
end
end
return output
end