From 72a9c62d4a1509b28446e6047411ab73dc212184 Mon Sep 17 00:00:00 2001
From: adrien <adrien@malingrey.fr>
Date: Mon, 10 Apr 2023 21:02:42 +0200
Subject: [PATCH] nmap favicon url

---
 http-favicon-url.nse | 168 +++++++++++++++++++++++++++++++++++++++++++
 index.php            |  16 ++++-
 results.xsl          |  10 ++-
 scan_all.php         |  20 ++++--
 4 files changed, 204 insertions(+), 10 deletions(-)
 create mode 100644 http-favicon-url.nse

diff --git a/http-favicon-url.nse b/http-favicon-url.nse
new file mode 100644
index 0000000..9fd3145
--- /dev/null
+++ b/http-favicon-url.nse
@@ -0,0 +1,168 @@
+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 url ("favorites icon").
+
+If the script argument <code>favicon.uri</code> 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 <code><link rel="icon"></code>
+element. If that fails, the icon is looked for in <code>/favicon.ico</code>. If
+a <code><link></code> 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-url.nse \
+--    --script-args favicon.root=<root>,favicon.uri=<uri>
+-- @output
+-- |_ http-favicon: http://hostname:80/favicon.ico
+
+-- 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 https://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 = ""
+  local url
+  local hostname = host.targetname or (host.name ~= "" and host.name) or host.ip
+
+  status, favicondb = datafiles.parse_file( favicondbfile, {["^%s*([^%s#:]+)[%s:]+"] = "^%s*[^%s#:]+[%s:]+(.*)"})
+  if not status then
+    stdnse.debug1("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.debug4("Using URI %s", favicon_uri)
+    url = 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
+        stdnse.debug1("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 )
+          url = port.service.."://"..hostname..":"..port.number.."/"..root.."/"..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" )
+      url = port.service.."://"..hostname..":"..port.number.."/"..root.."favicon.ico"
+      stdnse.debug4("Using default URI.")
+    end
+  end
+
+  --- check for 200 response code
+  if answer and answer.status == 200 then
+    result = url
+  else
+    stdnse.debug1("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 scheme, abspath
+  u = url.parse(u)
+  scheme = u.scheme or "http"
+  if not (scheme == "http" or scheme == "https") then
+    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 url.get_default_port(scheme), 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
diff --git a/index.php b/index.php
index 1eb8c92..302449e 100644
--- a/index.php
+++ b/index.php
@@ -22,14 +22,24 @@
     </header>
     <div class="ui main text container">
       <div class="ui link selection list">
-<?php foreach (scandir("./site") as $file) {
-    if (strrpos($file, ".xml")) {
+<?php
+if (! function_exists('str_ends_with')) {
+  function str_ends_with(string $haystack, string $needle): bool
+  {
+      $needle_len = strlen($needle);
+      return ($needle_len === 0 || 0 === substr_compare($haystack, $needle, - $needle_len));
+  }
+}
+
+foreach (scandir("./site") as $file) {
+    if (str_ends_with($file, ".xml")) {
       $site = str_replace(".xml", "", $file);
       if (file_exists("scans/$site.xml")) {
         echo "          <a href='site/$site.xml' class='item'>$site</a>\n";
       }
     }
-} ?>
+}
+?>
       </div>
     </div>
   </body>
diff --git a/results.xsl b/results.xsl
index 760db18..643eefb 100644
--- a/results.xsl
+++ b/results.xsl
@@ -25,6 +25,7 @@
     padding: 1em;
 }
         </style>
+        <meta http-equiv="refresh" content="300" />
     </head>
     <body>
         <header class="ui fixed blue inverted menu">
@@ -70,7 +71,14 @@
                     <xsl:otherwise>ui fluid mini left icon action input error</xsl:otherwise>
                 </xsl:choose>
             </xsl:attribute>
-            <i class="icon"><img class="ui image" src="http://{$scannedHostAddress}/favicon.ico" alt="" /></i>
+            <xsl:choose>
+                <xsl:when test="$scannedHost/ports/port/script[@id='http-favicon-url']/@output">
+                    <i class="icon"><img class="ui image" src="{$scannedHost/ports/port/script[@id='http-favicon-url']/@output}" alt="" /></i>
+                </xsl:when>
+                <xsl:otherwise>
+                    <i class="server icon"></i>
+                </xsl:otherwise>
+            </xsl:choose>
             <input type="text" readonly="">
                 <xsl:attribute name="value">
                     <xsl:choose>
diff --git a/scan_all.php b/scan_all.php
index bb8f729..af03907 100644
--- a/scan_all.php
+++ b/scan_all.php
@@ -1,8 +1,16 @@
 <?php
+if (! function_exists('str_ends_with')) {
+    function str_ends_with(string $haystack, string $needle): bool
+    {
+        $needle_len = strlen($needle);
+        return ($needle_len === 0 || 0 === substr_compare($haystack, $needle, - $needle_len));
+    }
+}
+
 if (!file_exists("scans")) mkdir("scans");
 
-foreach (scandir("./site") as $file) {
-    if (strrpos($file, ".yaml")) {
+foreach (scandir("./site/") as $file) {
+    if (str_ends_with($file, ".yaml")) {
         $site = str_replace(".yaml", "", $file);
         $yaml = yaml_parse_file("site/$file");
 
@@ -33,12 +41,12 @@ foreach (scandir("./site") as $file) {
                 }
             }
         }
-
-        $targets = array_keys($targets);
-        $services = array_keys($services);
         $xml->asXML("site/$site.xml");
 
-        exec("nmap -v -Pn -p ".join($services, ",")." --script smb-enum-shares.nse -oX 'scans/$site.xml' ".join($targets, " ")."\n");
+        $targets = join(array_keys($targets), " ");
+        $services = join(array_keys($services), ",");
+
+        exec("nmap -v -Pn -p $services --script smb-enum-shares.nse,http-errors,./http-favicon-url.nse --script-args=httpspider.maxpagecount=1 -oX 'scans/$site.xml' $targets\n");
     }
 };