mirror of
https://github.com/nmap/nmap.git
synced 2026-08-03 22:29:06 +00:00
o [NSE] Added the mobilme library and the scripts http-icloud-findmyiphone and
http-icloud-sendmsg, that finds the location of iOS devices and provides functionality to send them messages. [Patrik Karlsson]
This commit is contained in:
parent
59294eff19
commit
8ca252235e
5 changed files with 419 additions and 0 deletions
|
|
@ -1,5 +1,9 @@
|
|||
# Nmap Changelog ($Id$); -*-text-*-
|
||||
|
||||
o [NSE] Added the mobilme library and the scripts http-icloud-findmyiphone and
|
||||
http-icloud-sendmsg, that finds the location of iOS devices and provides
|
||||
functionality to send them messages. [Patrik Karlsson]
|
||||
|
||||
o [NSE] Added gps library and the gpsd-info script that collects GPS data
|
||||
from the gpsd daemon. [Patrik Karlsson]
|
||||
|
||||
|
|
|
|||
223
nselib/mobileme.lua
Normal file
223
nselib/mobileme.lua
Normal file
|
|
@ -0,0 +1,223 @@
|
|||
---
|
||||
-- A MobileMe web service client that allows discovering Apple devices
|
||||
-- using the "find my iPhone" functionality.
|
||||
--
|
||||
-- @author "Patrik Karlsson <patrik@cqure.net>"
|
||||
--
|
||||
module(... or "mobileme", package.seeall)
|
||||
|
||||
local http = require('http')
|
||||
local json = require('json')
|
||||
|
||||
MobileMe = {
|
||||
|
||||
-- headers used in all requests
|
||||
headers = {
|
||||
["Content-Type"] = "application/json; charset=utf-8",
|
||||
["X-Apple-Find-Api-Ver"] = "2.0",
|
||||
["X-Apple-Authscheme"] = "UserIdGuest",
|
||||
["X-Apple-Realm-Support"] = "1.0",
|
||||
["User-Agent"] = "Find iPhone/1.3 MeKit (iPad: iPhone OS/4.2.1)",
|
||||
["X-Client-Name"] = "iPad",
|
||||
["X-Client-UUID"] = "0cf3dc501ff812adb0b202baed4f37274b210853",
|
||||
["Accept-Language"] = "en-us",
|
||||
["Connection"] = "keep-alive"
|
||||
},
|
||||
|
||||
-- Creates a MobileMe instance
|
||||
-- @param username string containing the Apple ID username
|
||||
-- @param password string containing the Apple ID password
|
||||
-- @return o new instance of MobileMe
|
||||
new = function(self, username, password)
|
||||
local o = {
|
||||
host = "fmipmobile.icloud.com",
|
||||
port = 443,
|
||||
username = username,
|
||||
password = password
|
||||
}
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
return o
|
||||
end,
|
||||
|
||||
-- Sends a message to an iOS device
|
||||
-- @param devid string containing the device id to which the message should
|
||||
-- be sent
|
||||
-- @param subject string containing the messsage subject
|
||||
-- @param message string containing the message body
|
||||
-- @param alarm boolean true if alarm should be sounded, false if not
|
||||
-- @return status true on success, false on failure
|
||||
-- @return err string containing the error message (if status is false)
|
||||
sendMessage = function(self, devid, subject, message, alarm)
|
||||
local data = '{"clientContext":{"appName":"FindMyiPhone","appVersion":"1.3","buildVersion":"145","deviceUDID":"0000000000000000000000000000000000000000","inactiveTime":5911,"osVersion":"3.2","productType":"iPad1,1","selectedDevice":"%s","shouldLocate":false},"device":"%s","serverContext":{"callbackIntervalInMS":3000,"clientId":"0000000000000000000000000000000000000000","deviceLoadStatus":"203","hasDevices":true,"lastSessionExtensionTime":null,"maxDeviceLoadTime":60000,"maxLocatingTime":90000,"preferredLanguage":"en","prefsUpdateTime":1276872996660,"sessionLifespan":900000,"timezone":{"currentOffset":-25200000,"previousOffset":-28800000,"previousTransition":1268560799999,"tzCurrentName":"Pacific Daylight Time","tzName":"America/Los_Angeles"},"validRegion":true},"sound":%s,"subject":"%s","text":"%s"}'
|
||||
data = data:format(devid, devid, tostring(alarm), subject, message)
|
||||
|
||||
local url = ("/fmipservice/device/%s/sendMessage"):format(self.username)
|
||||
local auth = { username = self.username, password = self.password }
|
||||
|
||||
local response = http.post(self.host, self.port, url, { header = self.headers, auth = auth, timeout = 10000 }, nil, data)
|
||||
|
||||
if ( response.status == 200 ) then
|
||||
local status, resp = json.parse(response.body)
|
||||
if ( not(status) ) then
|
||||
stdnse.print_debug(2, "Failed to parse JSON response from server")
|
||||
return false, "Failed to parse JSON response from server"
|
||||
end
|
||||
|
||||
if ( resp.statusCode ~= "200" ) then
|
||||
stdnse.print_debug(2, "Failed to send message to server")
|
||||
return false, "Failed to send message to server"
|
||||
end
|
||||
end
|
||||
return true
|
||||
end,
|
||||
|
||||
-- Updates location information for all devices controlled by the Apple ID
|
||||
-- @return status true on success, false on failure
|
||||
-- @return json parsed json table or string containing an error message on
|
||||
-- failure
|
||||
update = function(self)
|
||||
|
||||
local auth = {
|
||||
username = self.username,
|
||||
password = self.password
|
||||
}
|
||||
|
||||
local url = ("/fmipservice/device/%s/initClient"):format(self.username)
|
||||
local data= '{"clientContext":{"appName":"FindMyiPhone","appVersion":"1.3","buildVersion":"145","deviceUDID":"0000000000000000000000000000000000000000","inactiveTime":2147483647,"osVersion":"4.2.1","personID":0,"productType":"iPad1,1"}}'
|
||||
|
||||
local retries = 2
|
||||
|
||||
local response
|
||||
repeat
|
||||
response = http.post(self.host, self.port, url, { header = self.headers, auth = auth }, nil, data)
|
||||
if ( response.header["x-apple-mme-host"] ) then
|
||||
self.host = response.header["x-apple-mme-host"]
|
||||
end
|
||||
|
||||
if ( response.status == 401 ) then
|
||||
return false, "Authentication failed"
|
||||
elseif ( response.status ~= 200 and response.status ~= 330 ) then
|
||||
return false, "An unexpected error occured"
|
||||
end
|
||||
|
||||
retries = retries - 1
|
||||
until ( 200 == response.status or 0 == retries)
|
||||
|
||||
if ( response.status ~= 200 ) then
|
||||
return false, "Received unexpected response from server"
|
||||
end
|
||||
|
||||
local status, parsed_json = json.parse(response.body)
|
||||
|
||||
if ( not(status) or parsed_json.statusCode ~= "200" ) then
|
||||
return false, "Failed to parse JSON response from server"
|
||||
end
|
||||
|
||||
-- cache the parsed_json.content as devices
|
||||
self.devices = parsed_json.content
|
||||
|
||||
return true, parsed_json
|
||||
end,
|
||||
|
||||
-- Get's a list of devices
|
||||
-- @return devices table containing a list of devices
|
||||
getDevices = function(self)
|
||||
if ( not(self.devices) ) then
|
||||
self:update()
|
||||
end
|
||||
return self.devices
|
||||
end
|
||||
}
|
||||
|
||||
|
||||
Helper = {
|
||||
|
||||
|
||||
-- Creates a Helper instance
|
||||
-- @param username string containing the Apple ID username
|
||||
-- @param password string containing the Apple ID password
|
||||
-- @return o new instance of Helper
|
||||
new = function(self, username, password)
|
||||
local o = {
|
||||
mm = MobileMe:new(username, password)
|
||||
}
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
o.mm:update()
|
||||
return o
|
||||
end,
|
||||
|
||||
-- Get's the geolocation from each device
|
||||
--
|
||||
-- @return status true on success, false on failure
|
||||
-- @return result table containing a table of device locations
|
||||
-- the table is indexed based on the name of the device and
|
||||
-- contains a location table with the following fields:
|
||||
-- * <code>longitude</code> - the GPS longitude
|
||||
-- * <code>latitude</code> - the GPS latitude
|
||||
-- * <code>accuracy</code> - the location accuracy
|
||||
-- * <code>timestamp</code> - the time the location was acquired
|
||||
-- * <code>postype</code> - the position type (GPS or WiFi)
|
||||
-- * <code>finished</code> -
|
||||
-- or string containing an error message on failure
|
||||
getLocation = function(self)
|
||||
-- do 3 tries, with a 5 second timeout to allow the location to update
|
||||
-- there are two attributes, locationFinished and isLocating that seem
|
||||
-- to be good candidates to monitor, but so far, I haven't had any
|
||||
-- success with that.
|
||||
local tries, timeout = 3, 5
|
||||
local result = {}
|
||||
|
||||
repeat
|
||||
local status, response = self.mm:update()
|
||||
|
||||
if ( not(status) or not(response) ) then
|
||||
return false, "Failed to retrieve response from server"
|
||||
end
|
||||
for _, device in ipairs(response.content) do
|
||||
if ( device.location ) then
|
||||
result[device.name] = {
|
||||
longitude = device.location.longitude,
|
||||
latitude = device.location.latitude,
|
||||
accuracy = device.location.horizontalAccuracy,
|
||||
timestamp = device.location.timeStamp,
|
||||
postype = device.location.positionType,
|
||||
finished = device.location.locationFinished,
|
||||
}
|
||||
end
|
||||
end
|
||||
tries = tries - 1
|
||||
if ( tries > 0 ) then
|
||||
stdnse.sleep(timeout)
|
||||
end
|
||||
until( tries == 0 )
|
||||
return true, result
|
||||
end,
|
||||
|
||||
-- Gets a list of names and ids of devices associated with the Apple ID
|
||||
-- @return status true on success, false on failure
|
||||
-- @return table of devices containing the following fields:
|
||||
-- <code>name</code> and <code>id</code>
|
||||
getDevices = function(self)
|
||||
local devices = {}
|
||||
for _, dev in ipairs(self.mm:getDevices()) do
|
||||
table.insert(devices, { name = dev.name, id = dev.id })
|
||||
end
|
||||
return true, devices
|
||||
end,
|
||||
|
||||
-- Send a message to an iOS Device
|
||||
--
|
||||
-- @param devid string containing the device id to which the message should
|
||||
-- be sent
|
||||
-- @param subject string containing the messsage subject
|
||||
-- @param message string containing the message body
|
||||
-- @param alarm boolean true if alarm should be sounded, false if not
|
||||
-- @return status true on success, false on failure
|
||||
-- @return err string containing the error message (if status is false)
|
||||
sendMessage = function(self, ...)
|
||||
return self.mm:sendMessage(...)
|
||||
end
|
||||
|
||||
}
|
||||
84
scripts/http-icloud-findmyiphone.nse
Normal file
84
scripts/http-icloud-findmyiphone.nse
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
description = [[
|
||||
Retrieves the locations of all "Find my iPhone" enabled iOS devices by querying
|
||||
the MobileMe web service.
|
||||
]]
|
||||
|
||||
---
|
||||
-- @usage
|
||||
-- nmap -sn -Pn --script http-icloud-findmyiphone --script-args='username=<user>,password=<pass>'
|
||||
--
|
||||
-- @output
|
||||
-- Pre-scan script results:
|
||||
-- | http-icloud-findmyiphone:
|
||||
-- | name location accuracy date type
|
||||
-- | Patrik Karlsson's MacBook Air -,- - - -
|
||||
-- | Patrik Karlsson's iPhone 40.690,-74.045 65 04/10/12 16:56:37 Wifi
|
||||
-- |_ Mac mini 40.690,-74.045 65 04/10/12 16:56:36 Wifi
|
||||
--
|
||||
-- @args http-icloud-findmyiphone.username the Apple Id username
|
||||
-- @args http-icloud-findmyiphone.password the Apple Id password
|
||||
--
|
||||
|
||||
author = "Patrik Karlsson"
|
||||
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
|
||||
categories = {"discovery", "safe"}
|
||||
|
||||
local mobileme = require('mobileme')
|
||||
local tab = require('tab')
|
||||
|
||||
local arg_username = stdnse.get_script_args(SCRIPT_NAME .. ".username")
|
||||
local arg_password = stdnse.get_script_args(SCRIPT_NAME .. ".password")
|
||||
|
||||
prerule = function() return true end
|
||||
|
||||
-- decode basic UTF8 encoded strings
|
||||
-- iOS devices are commonly named after the user eg:
|
||||
-- * Patrik Karlsson's Macbook Air
|
||||
-- * Patrik Karlsson's iPhone
|
||||
--
|
||||
-- This function decodes the single quote as a start and should really
|
||||
-- be replaced with a proper UTF-8 decoder in the future
|
||||
local function decodeString(str)
|
||||
return str:gsub("\226\128\153", "'")
|
||||
end
|
||||
|
||||
local function fail(err) return ("\n ERROR: %s"):format(err or "") end
|
||||
|
||||
action = function()
|
||||
|
||||
if ( not(arg_username) or not(arg_password) ) then
|
||||
return fail("No username or password was supplied")
|
||||
end
|
||||
|
||||
local mobileme = mobileme.Helper:new(arg_username, arg_password)
|
||||
local status, response = mobileme:getLocation()
|
||||
|
||||
if ( not(status) ) then
|
||||
stdnse.print_debug(2, "%s: %s", SCRIPT_NAME, response)
|
||||
return fail("Failed to retrieve location information")
|
||||
end
|
||||
|
||||
local output = tab.new(4)
|
||||
tab.addrow(output, "name", "location", "accuracy", "date", "type")
|
||||
for name, info in pairs(response) do
|
||||
local loc
|
||||
if ( info.latitude and info.longitude ) then
|
||||
loc = ("%.3f,%.3f"):format(
|
||||
tonumber(info.latitude) or "-",
|
||||
tonumber(info.longitude) or "-")
|
||||
else
|
||||
loc = "-,-"
|
||||
end
|
||||
local ts
|
||||
if ( info.timestamp and 1000 < info.timestamp ) then
|
||||
ts = os.date("%x %X", info.timestamp/1000)
|
||||
else
|
||||
ts = "-"
|
||||
end
|
||||
tab.addrow(output, decodeString(name), loc, info.accuracy or "-", ts, info.postype or "-")
|
||||
end
|
||||
|
||||
if ( 1 < #output ) then
|
||||
return stdnse.format_output(true, tab.dump(output))
|
||||
end
|
||||
end
|
||||
106
scripts/http-icloud-sendmsg.nse
Normal file
106
scripts/http-icloud-sendmsg.nse
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
description = [[
|
||||
]]
|
||||
---
|
||||
-- @usage
|
||||
-- nmap -sn -Pn --script http-icloud-sendmsg --script-args="username=<user>,password=<pass>,http-icloud-sendmsg.listdevices"
|
||||
-- nmap -sn -Pn --script http-icloud-sendmsg --script-args="username=<user>,password=<pass>,deviceindex=1,subject='subject',message='hello world.',sound=false"
|
||||
--
|
||||
-- @output
|
||||
-- Pre-scan script results:
|
||||
-- | http-icloud-sendmsg:
|
||||
-- |_ Message was successfully sent to "Patrik Karlsson's iPhone"
|
||||
--
|
||||
-- @args http-icloud-sendmsg.username the Apple ID username
|
||||
-- @args http-icloud-sendmsg.password the Apple ID password
|
||||
-- @args http-icloud-sendmsg.listdevices list the devices managed by the
|
||||
-- specified Apple ID.
|
||||
-- @args http-icloud-sendmsg.deviceindex the device index to which the message
|
||||
-- should be sent (@see http-icloud-sendmsg.listdevices)
|
||||
-- @args http-icloud-sendmsg.subject the subject of the message to send to the
|
||||
-- device.
|
||||
-- @args http-icloud-sendmsg.message the body of the message to send to the
|
||||
-- device.
|
||||
-- @args http-icloud-sendmsg.sound boolean specifying if a loud sound should be
|
||||
-- played while displaying the message. (default: true)
|
||||
|
||||
author = "Patrik Karlsson"
|
||||
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
|
||||
categories = {"discovery", "safe"}
|
||||
|
||||
local mobileme = require('mobileme')
|
||||
local tab = require('tab')
|
||||
|
||||
local arg_username = stdnse.get_script_args(SCRIPT_NAME .. ".username")
|
||||
local arg_password = stdnse.get_script_args(SCRIPT_NAME .. ".password")
|
||||
local arg_listdevices = stdnse.get_script_args(SCRIPT_NAME .. ".listdevices")
|
||||
local arg_deviceindex = tonumber(stdnse.get_script_args(SCRIPT_NAME .. ".deviceindex"))
|
||||
local arg_subject = stdnse.get_script_args(SCRIPT_NAME .. ".subject")
|
||||
local arg_message = stdnse.get_script_args(SCRIPT_NAME .. ".message")
|
||||
local arg_sound = stdnse.get_script_args(SCRIPT_NAME .. ".sound") or true
|
||||
|
||||
|
||||
prerule = function() return true end
|
||||
|
||||
-- decode basic UTF8 encoded strings
|
||||
-- iOS devices are commonly named after the user eg:
|
||||
-- * Patrik Karlsson's Macbook Air
|
||||
-- * Patrik Karlsson's iPhone
|
||||
--
|
||||
-- This function decodes the single quote as a start and should really
|
||||
-- be replaced with a proper UTF-8 decoder in the future
|
||||
local function decodeString(str)
|
||||
return str:gsub("\226\128\153", "'")
|
||||
end
|
||||
|
||||
local function listDevices(mm)
|
||||
local status, devices = mm:getDevices()
|
||||
if ( not(status) ) then
|
||||
return fail("Failed to get devices")
|
||||
end
|
||||
|
||||
local output = tab.new(2)
|
||||
tab.addrow(output, "id", "name")
|
||||
for i=1, #devices do
|
||||
local name = decodeString(devices[i].name or "")
|
||||
tab.addrow(output, i, name)
|
||||
end
|
||||
|
||||
if ( 1 < #output ) then
|
||||
return stdnse.format_output(true, tab.dump(output))
|
||||
end
|
||||
end
|
||||
|
||||
local function fail(err) return ("\n ERROR: %s"):format(err or "") end
|
||||
|
||||
action = function()
|
||||
if ( not(arg_username) or not(arg_password) ) then
|
||||
return fail("No username or password was supplied")
|
||||
end
|
||||
|
||||
if ( not(arg_deviceindex) and not(arg_listdevices) ) then
|
||||
return fail("No device ID was specificed")
|
||||
end
|
||||
|
||||
if ( 1 == tonumber(arg_listdevices) or "true" == arg_listdevices ) then
|
||||
local mm = mobileme.Helper:new(arg_username, arg_password)
|
||||
return listDevices(mm)
|
||||
elseif ( not(arg_subject) or not(arg_message) ) then
|
||||
return fail("Missing subject or message")
|
||||
else
|
||||
local mm = mobileme.Helper:new(arg_username, arg_password)
|
||||
local status, devices = mm:getDevices()
|
||||
|
||||
if ( not(status) ) then
|
||||
return fail("Failed to get devices")
|
||||
end
|
||||
|
||||
if ( status and arg_deviceindex <= #devices ) then
|
||||
local status = mm:sendMessage( devices[arg_deviceindex].id, arg_subject, arg_message, arg_sound)
|
||||
if ( status ) then
|
||||
return ("\n Message was successfully sent to \"%s\""):format(decodeString(devices[arg_deviceindex].name or ""))
|
||||
else
|
||||
return "\n Failed to send message"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -130,6 +130,8 @@ Entry { filename = "http-generator.nse", categories = { "default", "discovery",
|
|||
Entry { filename = "http-google-malware.nse", categories = { "discovery", "external", "malware", "safe", } }
|
||||
Entry { filename = "http-grep.nse", categories = { "discovery", "safe", } }
|
||||
Entry { filename = "http-headers.nse", categories = { "discovery", "safe", } }
|
||||
Entry { filename = "http-icloud-findmyiphone.nse", categories = { "discovery", "safe", } }
|
||||
Entry { filename = "http-icloud-sendmsg.nse", categories = { "discovery", "safe", } }
|
||||
Entry { filename = "http-iis-webdav-vuln.nse", categories = { "intrusive", "vuln", } }
|
||||
Entry { filename = "http-joomla-brute.nse", categories = { "brute", "intrusive", } }
|
||||
Entry { filename = "http-litespeed-sourcecode-download.nse", categories = { "exploit", "intrusive", "vuln", } }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue