local datafiles = require "datafiles" local http = require "http" local nmap = require "nmap" local shortport = require "shortport" local stdnse = require "stdnse" local string = require "string" local url = require "url" local openssl = stdnse.silent_require "openssl" description = [[ Gets the favicon ("favorites icon") from a web page and matches it against a database of the icons of known web applications. If there is a match, the name of the application is printed; otherwise the MD5 hash of the icon data is printed. If the script argument favicon.uri is given, that relative URI is always used to find the favicon. Otherwise, first the page at the root of the web server is retrieved and parsed for a element. If that fails, the icon is looked for in /favicon.ico. If a favicon points to a different host or port, it is ignored. ]] --- -- @args favicon.uri URI that will be requested for favicon. -- @args favicon.root Web server path to search for favicon. -- -- @usage -- nmap --script=http-favicon.nse \ -- --script-args favicon.root=,favicon.uri= -- @output -- |_ http-favicon: Socialtext -- HTTP default favicon enumeration script -- rev 1.2 (2009-03-11) -- Original NASL script by Javier Fernandez-Sanguino Pena author = "Vlatko Kosturjak" license = "Same as Nmap--See http://nmap.org/book/man-legal.html" categories = {"default", "discovery", "safe"} portrule = shortport.http action = function(host, port) local md5sum,answer local match local status, favicondb local result local favicondbfile="nselib/data/favicon-db" local index, icon local root = "" status, favicondb = datafiles.parse_file( favicondbfile, {["^%s*([^%s#:]+)[%s:]+"] = "^%s*[^%s#:]+[%s:]+(.*)"}) if not status then stdnse.print_debug( 1, "Could not open file: %s", favicondbfile ) return end if(stdnse.get_script_args('favicon.root')) then root = stdnse.get_script_args('favicon.root') end local favicon_uri = stdnse.get_script_args("favicon.uri") if(favicon_uri) then -- If we got a script arg URI, always use that. answer = http.get( host, port, root .. "/" .. favicon_uri) stdnse.print_debug( 4, "Using URI %s", favicon_uri) else -- Otherwise, first try parsing the home page. index = http.get( host, port, root .. "/" ) if index.status == 200 or index.status == 503 then -- find the favicon pattern icon = parseIcon( index.body ) -- if we find a pattern if icon then local hostname = host.targetname or (host.name ~= "" and host.name) or host.ip stdnse.print_debug(1, "Got icon URL %s.", icon) local icon_host, icon_port, icon_path = parse_url_relative(icon, hostname, port.number, root) if (icon_host == host.ip or icon_host == host.targetname or icon_host == (host.name ~= '' and host.name)) and icon_port == port.number then -- request the favicon answer = http.get( icon_host, icon_port, icon_path ) else answer = nil end else answer = nil end end -- If that didn't work, try /favicon.ico. if not answer or answer.status ~= 200 then answer = http.get( host, port, root .. "/favicon.ico" ) stdnse.print_debug( 4, "Using default URI.") end end --- check for 200 response code if answer and answer.status == 200 then md5sum=string.upper(stdnse.tohex(openssl.md5(answer.body))) match=favicondb[md5sum] if match then result = match else if nmap.verbosity() > 0 then result = "Unknown favicon MD5: " .. md5sum end end else stdnse.print_debug( 1, "No favicon found.") return end --- status == 200 return result end local function dirname(path) local dir dir = string.match(path, "^(.*)/") return dir or "" end -- Return a URL's 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 u.host or host, u.port or defaultport, abspath end function parseIcon( 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