-
Notifications
You must be signed in to change notification settings - Fork 20
/
google-people-enum.nse
213 lines (184 loc) · 7.39 KB
/
google-people-enum.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
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
206
207
208
209
210
211
212
213
local openssl = require "openssl"
local http = require "http"
local shortport = require "shortport"
local stdnse = require "stdnse"
local string = require "string"
local json = require "json"
local unpwdb = require "unpwdb"
description = [[
Attempts to enumerate valid email addresses using Google's Internal People API. If a valid email address is found, it
also grabs the display name and photo from the profile.
This script uses 'unpwdb' for username guessing but you can provide your own list (--script-args userdb=/tmp/user.lst).
A valid Google account must be provided to communicate with the API.
References:
https://developers.google.com/people/api/rest/
TODO:
* Implement OAUTH to replace username and password.
]]
---
-- @usage
-- nmap -sn --script google-people-enum --script-args='username=<username>,password=<password>' <domain>
-- @usage
-- nmap -sn --script google-people-enum --script-args='username=<username>,password=<password>,domain=<domain>' <target>
--
-- @output
-- Host script results:
-- | google-people-enum:
-- | users:
-- |
-- | [email protected]:
-- | photo: https://lh3.googleusercontent.com/XXXXXXXXXXXXX/photo.jpg
-- | name: User 1
-- |
-- | [email protected]:
-- |_ photo: https://lh3.googleusercontent.com/XXXXXXXXXXXXXXX/photo.jpg
--
-- @xmloutput
-- <table key="users">
-- <table>
-- <table key="[email protected]">
-- <elem key="photo">https://XXXXXX/photo.jpg</elem>
-- <elem key="name">User 1</elem>
-- </table>
-- </table>
-- <table>
-- <table key="[email protected]">
-- <elem key="photo">https://XXXXXX/photo.jpg</elem>
-- </table>
-- </table>
-- </table>
--
-- @args google-people-enum.username Username to authenticate to Google's People API
-- @args google-people-enum.password Password to authenticate to Google's People API
-- @args google-people-enum.domain Domain name.
---
categories = {"discovery", " external"}
author = {'Aaron Velasco <[email protected]>','Paulino Calderon <[email protected]>'}
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
hostrule = function() return true end
local ORIGIN = 'https://hangouts.google.com'
local function google_login(username, password)
local options = {}
options['header'] = {}
options['header']['Content-Type'] = 'application/x-www-form-urlencoded'
options['cookies'] = 'GAPS=1:9Gh-W5SRMzgYa850L3DJBw5vAD6uOQ:SCrej40XbCRKHuDY'
local path = string.format("/signin/challenge/sl/password?gxf=AFoagUU7fJ86otMHTVv_nGnqUI8ZQW9V9Q%%3A1480734358179&Email=%s&Passwd=%s", username, password)
local response = http.generic_request('accounts.google.com', '443', 'POST', path, options)
return response
end
local function get_cookie(response)
local cookie = ""
local ids = {["APISID"]=34, ["HSID"]=17,["SAPISID"]=34,["SID"]=71,["SSID"]=17}
for id,length in pairs(ids) do
local s = string.find(response.header['set-cookie'], id)
local e = s + string.len(id) + length + 1
local sub = string.sub(response.header['set-cookie'], s, e)
cookie = cookie .. sub
end
return(cookie)
end
local function sha1(message)
local hash = ""
local digest = openssl.sha1(message)
for i=1,string.len(digest) do
if string.byte(digest, i) > 15 then
hash = hash .. string.format("%x", string.byte(digest, i))
else
hash = hash .. string.format("0%x", string.byte(digest, i))
end
end
return hash
end
local function get_hash(cookie)
local s = string.find(cookie, "SAPISID") + 8
local e = s + 33
local ts = os.time()
return string.format("SAPISIDHASH %s_%s", ts, sha1(string.format("%s %s %s", ts, string.sub(cookie, s, e), ORIGIN)))
end
local function get_opts(cookie)
local options = {}
options['header'] = {}
options['header']['Authorization'] = get_hash(cookie)
options['header']['X-HTTP-Method-Override'] = 'GET'
options['header']['Content-Type'] = 'application/x-www-form-urlencoded'
options['header']['origin'] = ORIGIN
options['cookies'] = cookie
return options
end
local function lookup(email, options)
local path = string.format("/v2/people/lookup?id=%s&type=EMAIL&matchType=EXACT&requestMask.includeField.paths=person.email"..
"&requestMask.includeField.paths=person.gender&requestMask.includeField.paths=person.in_app_reachability"..
"&requestMask.includeField.paths=person.metadata&requestMask.includeField.paths=person.name"..
"&requestMask.includeField.paths=person.phone&requestMask.includeField.paths=person.photo"..
"&requestMask.includeField.paths=person.read_only_profile_info&extensionSet.extensionNames=HANGOUTS_ADDITIONAL_DATA"..
"&extensionSet.extensionNames=HANGOUTS_OFF_NETWORK_GAIA_LOOKUP&extensionSet.extensionNames=HANGOUTS_PHONE_DATA"..
"&coreIdParams.useRealtimeNotificationExpandedAcls=true&key=AIzaSyAfFJCeph-euFSwtmqFZi0kaKk-cZ5wufM", email)
local response = http.generic_request('people-pa.clients6.google.com', '443', 'POST', path, options)
local userdata = {}
if http.response_contains(response, email) then
local status, person = json.parse(response.body)
local lookupId = person['matches'][1]['lookupId']
local personId = person['matches'][1]['personId'][1]
local displayName
local photo
userdata[lookupId] = {}
if person['people'][personId]['name'] then
displayName = person['people'][personId]['name'][1]['displayName']
stdnse.debug1("Display name:%s", displayName)
userdata[lookupId].name = displayName
end
if person['people'][personId]['photo'] then
photo = person['people'][personId]['photo'][1]['url']
stdnse.debug1("Photo:%s", photo)
userdata[lookupId].photo = photo
end
return true, userdata
else
stdnse.debug2("User '%s' wasn't found.", email)
return false, 'No match'
end
end
local function google_logout(cookie)
local options = {}
options['cookies'] = cookie
local response = http.generic_request('accounts.google.com', '443', 'GET', '/Logout', options)
return
end
action = function(host, port)
local username = stdnse.get_script_args(SCRIPT_NAME .. ".username") or nil
local password = stdnse.get_script_args(SCRIPT_NAME .. ".password") or nil
local target = stdnse.get_script_args(SCRIPT_NAME .. ".domain") or nil
local output = stdnse.output_table()
if not(target) then
if host.name then
target = host.name
else
stdnse.debug1("Target not specified and Nmap couldn't resolve hostname.")
return "[ERROR] Please set a target with the script argument google-people-enum.domain."
end
end
if not(username) or not(password) then
return "[ERROR] This script needs a valid Google username (google-people-enum.username) and password (google-people-enum.password)."
end
local response = google_login(username, password)
if http.response_contains(response, "CheckCookie") then
cookie = get_cookie(response)
options = get_opts(cookie)
local tmp = {}
local try = nmap.new_try()
local usernames = try(unpwdb.usernames())
for username in usernames do
stdnse.debug1("Checking if user '%s@%s' exists", username, target)
local status, result = lookup(string.format("%s@%s", username, target), options)
if status then
stdnse.debug1("User '%s' exists! Display name:%s Photo:%s", username, result.name, result.photo)
table.insert(tmp, result)
end
end
google_logout(cookie)
if #tmp>0 then
output.users = tmp
return output
end
end
end