description = [[
Gets favicons and makes a database with their frequencies.
Quick start:
Save this script to the scripts subdirectory of your nmap working directory and
cd into it.
mkdir ~/favicon
mkdir ~/favicon/hash ~/favicon/icon
Then run a command like this:
./nmap --datadir . -n -PN -d --max-retries 2 --script=favicon --script-args hashdir=/home/user/favicon/hash,icondir=/home/user/favicon/icon -p 80 -iR 20000 -oN favicon-%Y%m%d-%H%M%S.nmap
The script downloads each favicon it finds and stores it in a file in ICON_DIR
named after the MD5 hash of the favicon.
$ cd ~/favicon
$ ls icon
17F03417CBF92B80992B7CA7A566FB0C.ico C89ECD7675567625E5755A7A9C31632D.ico
379A65BEB4D412765FCF9FBBDEECD416.ico C8BFCB5728998AC6C3DA90EA5CD2340A.ico
7131EF7073ED685BF2987B9061C65D36.ico CB5AA723DDDB0734CEC459F2B9C3B1C4.ico
88733EE53676A47FC354A61C32516E82.ico D16A0DA12074DAE41980A6918D33F031.ico
A3C7BE1BCF382EA413C30453A4ACF638.ico D41D8CD98F00B204E9800998ECF8427E.ico
A8FE5B8AE2C445A33AC41B33CCC9A120.ico D4DA62A788942AAB81D033C9E49D57CB.ico
B6141EFEE8D8E64DBC23539F99F7238E.ico ECF508711C226CCDA02D58853B31D7A7.ico
C3FB27F0BF8AC3171C8105726D61380A.ico
It also stores the host name and port of each host that had a favicon in a file
in HASH_DIR, also named with the MD5 sum.
$ cd ~/favicon
$ ls hash
17F03417CBF92B80992B7CA7A566FB0C C89ECD7675567625E5755A7A9C31632D
379A65BEB4D412765FCF9FBBDEECD416 C8BFCB5728998AC6C3DA90EA5CD2340A
7131EF7073ED685BF2987B9061C65D36 CB5AA723DDDB0734CEC459F2B9C3B1C4
88733EE53676A47FC354A61C32516E82 D16A0DA12074DAE41980A6918D33F031
A3C7BE1BCF382EA413C30453A4ACF638 D41D8CD98F00B204E9800998ECF8427E
A8FE5B8AE2C445A33AC41B33CCC9A120 D4DA62A788942AAB81D033C9E49D57CB
B6141EFEE8D8E64DBC23539F99F7238E ECF508711C226CCDA02D58853B31D7A7
C3FB27F0BF8AC3171C8105726D61380A
Each of these files contains a list of each host and port that had that
particular favicon, one per line.
$ cd ~/favicon
$ cat hash/D16A0DA12074DAE41980A6918D33F031
190.166.207.187:80
125.25.91.250:80
You can see which are the most common icons with wc.
$ cd ~/favicon
$ wc -l hash/* | sort -n
1 hash/17F03417CBF92B80992B7CA7A566FB0C
1 hash/379A65BEB4D412765FCF9FBBDEECD416
1 hash/7131EF7073ED685BF2987B9061C65D36
1 hash/88733EE53676A47FC354A61C32516E82
1 hash/A3C7BE1BCF382EA413C30453A4ACF638
1 hash/B6141EFEE8D8E64DBC23539F99F7238E
1 hash/C3FB27F0BF8AC3171C8105726D61380A
1 hash/C89ECD7675567625E5755A7A9C31632D
1 hash/C8BFCB5728998AC6C3DA90EA5CD2340A
1 hash/CB5AA723DDDB0734CEC459F2B9C3B1C4
1 hash/D4DA62A788942AAB81D033C9E49D57CB
1 hash/ECF508711C226CCDA02D58853B31D7A7
2 hash/D16A0DA12074DAE41980A6918D33F031
4 hash/D41D8CD98F00B204E9800998ECF8427E
19 hash/A8FE5B8AE2C445A33AC41B33CCC9A120
37 total
]]
author = "David Fifield "
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"discovery", "safe"}
require("bin")
require("http")
require("nmap")
require("openssl")
require("shortport")
require("stdnse")
require("url")
portrule = shortport.port_or_service({80, 443, 8080, 8443},
{"http", "https", "http-alt", "https-alt"})
-- You must create hash and icon directories and provide hashdir and icondir
-- script arguments. Run
-- mkdir ~/favicon
-- mkdir ~/favicon/hash ~/favicon/icon
-- Then use --script-args hashdir=/home/user/favicon/hash,icondir=/home/user/favicon/icon.
-- The icondir should be common to all scans. The hashdir may be different to
-- segregate the results of different target sets.
local HASH_DIR = nmap.registry.args.hashdir
local ICON_DIR = nmap.registry.args.icondir
if not ICON_DIR or not HASH_DIR then
error("\nUse --script-args hashdir=.../favicon/hash,icondir=.../favicon/icon.")
end
local REDIRECT_LIMIT = 5
local MAX_SIZE = 10000000
local parse_url_relative
local http_get_redirected, root_doc, favicon_link_url
action = function(host, port)
local root_host, root_port, root_path, body
local favicon_url, favicon_body
local target, hash
local _
local ip, name = host.ip, host.targetname or ""
host = host.targetname or host.ip
port = port.number
root_host, root_port, root_path, body = http_get_redirected(host, port,
"/", name, ip, REDIRECT_LIMIT)
if body then
stdnse.print_debug("favicon: %s %s redirects led " ..
"to %s:%d%s", name, ip, root_host, root_port, root_path)
favicon_url = favicon_link_url(body)
end
if favicon_url then
stdnse.print_debug("favicon: %s %s " ..
"favicon from %s", name, ip, favicon_url)
local scheme, favicon_host, favicon_port, favicon_path =
parse_url_relative(favicon_url, root_host, root_port, root_path)
if scheme == "http" or scheme == "https" then
_, _, _, favicon_body = http_get_redirected(favicon_host, favicon_port, favicon_path, name, ip, REDIRECT_LIMIT)
end
target = root_host .. ":" .. tostring(root_port)
else
stdnse.print_debug("favicon: %s %s no ", name, ip)
end
if not favicon_body then
stdnse.print_debug("favicon: %s %s default " ..
"favicon from %s:%d/favicon.ico", name, ip, host, port)
_, _, _, favicon_body = http_get_redirected(host, port, "/favicon.ico", name, ip, REDIRECT_LIMIT)
target = host .. ":" .. tostring(port)
end
if not favicon_body then
return
elseif #favicon_body > MAX_SIZE then
stdnse.print_debug("favicon: %s %s oversized (%d)",
name, ip, #favicon_body, favicon_url)
return
else
_, hash = bin.unpack("H16", openssl.md5(favicon_body))
end
local f, line
local f = io.open(HASH_DIR .. "/" .. hash, "r")
if f then
-- We've seen this hash already.
for line in f:lines() do
if line == target then
-- Already seen this host too.
return hash .. ": " .. target .. " already present."
end
end
f:close()
-- Append the target to the list.
f = assert(io.open(HASH_DIR .. "/" .. hash, "a"))
f:write(target .. "\n")
f:close()
return hash .. ": added " .. target .. "."
else
-- New hash. Save the favicon.
f = assert(io.open(ICON_DIR .. "/" .. hash .. ".ico", "wb"))
f:write(favicon_body)
f:close()
-- Append the target to the list.
f = assert(io.open(HASH_DIR .. "/" .. hash, "a"))
f:write(target .. "\n")
f:close()
return hash .. ": new with " .. target .. "."
end
end
function dirname(path)
local dir
dir = string.match(path, "^(.*)/")
return dir or ""
end
-- Return a URL's scheme, host, port, and path, filling in the results with the
-- given host, port, and path if the URL is relative. Return nil if the scheme
-- is not "http" or "https".
function parse_url_relative(u, host, port, path)
local defaultport, scheme, abspath
u = url.parse(u)
scheme = u.scheme or "http"
if scheme == "http" then
defaultport = 80
elseif scheme == "https" then
defaultport = 443
else
return nil
end
abspath = u.path or ""
if not string.find(abspath, "^/") then
abspath = dirname(path) .. "/" .. abspath
end
return scheme, u.host or host, u.port or defaultport, abspath
end
-- Do an HTTP GET, following at most limit redirections. Return the final host,
-- port, path, and body; or nil if any of the redirections failed.
function http_get_redirected(host, port, path, name, ip, limit)
if not limit then
limit = 0
end
local count = 0
while count <= limit do
stdnse.print_debug("favicon: %s %s link %d/%d get %s:%d%s",
name, ip, count, limit, host, port, path)
local response = http.get(host, port, path, {bypass_cache = true, no_cache = true, no_cache_body = true, max_content_length = MAX_SIZE})
if not response.status then
stdnse.print_debug("favicon: %s %s link %d/%d failed %s:%d%s",
name, ip, count, limit, host, port, path)
break
end
stdnse.print_debug("favicon: %s %s link %d/%d received %s:%d%s",
name, ip, count, limit, host, port, path)
local status = tonumber(response.status)
if status == 200 then
return host, port, path, response.body
elseif status >= 300 and status < 400 and response.header.location then
local scheme
scheme, host, port, path = parse_url_relative(response.header.location, host, port, path)
stdnse.print_debug("favicon: %s %s link %d/%d redirects to %s:%d%s",
name, ip, count, limit, host, port, path)
if not (scheme == "http" or scheme == "https") then
break
end
else
break
end
count = count + 1
end
return nil
end
-- grep an HTML body for an element of the form
--
--
-- and return the href.
function favicon_link_url(body)
local _, i, j
local rel, href, word
-- Loop through link elements.
i = 0
while i do
_, i = string.find(body, "<%s*[Ll][Ii][Nn][Kk]%s", i + 1)
if not i then
return nil
end
-- Loop through attributes.
j = i
while true do
local name, quote, value
_, j, name, quote, value = string.find(body, "^%s*(%w+)%s*=%s*([\"'])(.-)%2", j + 1)
if not j then
break
end
if string.lower(name) == "rel" then
rel = value
elseif string.lower(name) == "href" then
href = value
end
end
for word in string.gmatch(rel or "", "%S+") do
if string.lower(word) == "icon" then
return href
end
end
end
end