diff --git a/scripts/http-favicon.nse b/scripts/http-favicon.nse index 11b0dcdd4..b7ea93896 100644 --- a/scripts/http-favicon.nse +++ b/scripts/http-favicon.nse @@ -1,5 +1,19 @@ description = [[ -Obtains the favicon.ico from the root of a web service (or with the html link rel attribute if that fails) and tries to identify its source (such as a certain web application) using a database lookup. +Gets the favicon ("favorites icon") from a web page 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 arg 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. +Obtains the favicon.ico from the root of a web service (or with the html link +rel attribute if that fails) and tries to identify its source (such as a +certain web application) using a database lookup. + +If a favicon points to a different host or port, it is +ignored. ]] --- @@ -56,35 +70,39 @@ action = function(host, port) end if(nmap.registry.args['favicon.uri']) then + -- If we got a script arg URI, always use that. answer = http.get( host, port, root .. "/" .. nmap.registry.args['favicon.uri']) stdnse.print_debug( 4, "Using URI %s", nmap.registry.args['favicon.uri']) else - answer = http.get( host, port, root .. "/favicon.ico" ) - stdnse.print_debug( 4, "Using default URI.") - end - - -- if we didn't find a correct favicon, let's parse the first page and search for one! - if answer.status ~= 200 then - stdnse.print_debug( 1, "No favicon found on root of web server, parsing initial page for favicon.") + -- Otherwise, first try parsing the home page. index = http.get( host, port, root .. "/" ) - -- if we get the first page 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 - -- check if the path is in './' format, what means that we must replace it by the root directory - if string.match(icon, "^%.") then - icon = string.gsub(icon, "^%.", root, 1) + stdnse.print_debug(1, "Got icon URL %s.", icon) + local icon_host, icon_port, icon_path = parse_url_relative(icon, host, port, 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 - -- request the favicon - answer = http.get( host, port, icon ) 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 @@ -104,33 +122,56 @@ action = function(host, port) return result end -function parseIcon( body ) - local icon, absolute_icon, parsed_icon - local tag_start, tag_end, tag - local tags = {} - - -- separate tags - tag_start, tag_end = string.find(body,'(<.->)') - while tag_start do - tag = string.sub(body, tag_start, tag_end) - body = string.sub(body, tag_end) - tags[#tags+1] = tag - tag_start, tag_end = string.find(body,'(<.->)') - end - - -- check each tag for our favicon tag - for k, v in ipairs(tags) do - icon = string.match( v, '<(%s-link.-rel%s-=%s-".-icon".-/?)>') - if icon then - icon = string.match( icon, 'href%s*=%s*"(.-)"') - -- if favicon is in absolute format, we need to parse it! - absolute_icon = string.match(icon, '^http://') - if absolute_icon then - parsed_icon = url.parse(icon) - icon = parsed_icon.path - end - break - end - end - return icon +-- 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