Lucene search
+L

Pi-hole Ad-Blocker Detection (HTTP)

🗓️ 17 Feb 2018 00:00:00Reported by Copyright (C) 2018 Greenbone AGType 
openvas
 openvas
🔗 plugins.openvas.org👁 51 Views

Pi-hole Ad-Blocker Detection (HTTP) - Detection of the Pi-hole Ad-Blocker through HTTP protoco

Code
# SPDX-FileCopyrightText: 2018 Greenbone AG
# Some text descriptions might be excerpted from (a) referenced
# source(s), and are Copyright (C) by the respective right holder(s).
#
# SPDX-License-Identifier: GPL-2.0-only

if(description)
{
  script_oid("1.3.6.1.4.1.25623.1.0.108342");
  script_version("2025-11-12T05:40:18+0000");
  script_tag(name:"cvss_base", value:"0.0");
  script_tag(name:"cvss_base_vector", value:"AV:N/AC:L/Au:N/C:N/I:N/A:N");
  script_tag(name:"last_modification", value:"2025-11-12 05:40:18 +0000 (Wed, 12 Nov 2025)");
  script_tag(name:"creation_date", value:"2018-02-17 15:43:37 +0100 (Sat, 17 Feb 2018)");

  script_tag(name:"qod_type", value:"remote_banner");

  script_name("Pi-hole Ad-Blocker Detection (HTTP)");

  script_category(ACT_GATHER_INFO);

  script_copyright("Copyright (C) 2018 Greenbone AG");
  script_family("Product detection");
  script_dependencies("find_service.nasl", "httpver.nasl", "global_settings.nasl");
  script_require_ports("Services/www", 80);
  script_exclude_keys("Settings/disable_cgi_scanning");

  script_tag(name:"summary", value:"HTTP based detection of the Pi-hole Ad-Blocker.");

  script_add_preference(name:"Pi-hole Web UI Password", value:"", type:"password", id:1);

  exit(0);
}

include("http_func.inc");
include("http_keepalive.inc");
include("misc_func.inc");
include("port_service_func.inc");

port = http_get_port( default:80 );

# nb:
# - "/admin/" is/was the default one in Web Interface (Previously AdminLTE) < 5.14
# - "/admin/login.php" is used since Web Interface (Previously AdminLTE) 5.14
# - "/admin/login" is used since Web Interface (Previously AdminLTE) 6.x (The LUA script rewrite of the Web GUI)
# - a few have been seen on the top-level as well
# - Keep the /admin/ ones first as installs having the "/admin/" subfolder might be detected twice
#   otherwise because a few of the patterns below are matching as well (on purpose).
urls = make_list( "/admin/", "/admin/login.php", "/admin/login", "/", "/login.php" );

foreach url( urls ) {

  res = http_get_cache( item:url, port:port );

  # nb: Some systems are throwing the "Oops! Access denied." / "You don't have permission to access"
  #     with a 403 status code. This page also includes the "*  Pi-hole: A black hole for Internet"
  #     header part / comment
  if( res =~ "^HTTP/1\.[01] (200|403)" &&
      ( "<title>Pi-hole Admin Console</title>" >< res || # nb: Only in older versions
        egrep( string:res, pattern:"<title>Pi-hole - [^<]+</title>", icase:FALSE ) || # Web Interface (Previously AdminLTE) 5.3.1+ has <title>Pi-hole - $hostname</title>
        '<a href="http://pi-hole.net" class="logo"' >< res ||
        '<script src="scripts/pi-hole/js/footer.js"></script>' >< res ||
        "<!-- Pi-hole: A black hole for Internet advertisements" >< res ||
        "*  Pi-hole: A black hole for Internet advertisements" >< res ||
        "<p>After installing Pi-hole for the first time, a password is generated and displayed" >< res ||
        ( "Open Source Ad Blocker" >< res && "<small>Designed For Raspberry Pi</small>" >< res ) ) ) {
    found = TRUE;
    concludedUrl = "    " + http_report_vuln_url( port:port, url:url, url_only:TRUE );
    break; # nb: We only want to detect it once...
  }
}

# nb: If anything goes wrong still try to detect it from the HTTP headers
if( ! found ) {

  # nb: We need to try a few URLs as the banner below depends a little on the queried URL...
  foreach url( urls ) {

    banner = http_get_remote_headers( port:port, file:url );
    # X-Pi-hole: A black hole for Internet advertisements.
    # X-Pi-hole: The Pi-hole Web interface is working!
    if( banner && concl = egrep( string:banner, pattern:"^X-Pi-hole\s*:\s*(A black hole for Internet advertisements\.|The Pi-hole Web interface is working!)", icase:FALSE ) ) {
      found = TRUE;
      banner_concluded = chomp( concl );
      concludedUrl = "    " + http_report_vuln_url( port:port, url:url, url_only:TRUE );
      break;
    }
  }
}

if( found ) {
  pihole_version = "unknown";
  web_version    = "unknown";
  ftl_version    = "unknown";

  # nb:
  # - To tell http_can_host_asp and http_can_host_php from http_func.inc that the service is NOT
  #   supporting these
  # - This is only done for the "/admin/login" URL which is the "LUA script rewrite" of the Web GUI
  #   done in version 6.x+
  # - Previous versions of the product was based / had used lighttpd/PHP by default
  if( concludedUrl =~ "/admin/login$" ) {
    replace_kb_item( name:"www/" + port + "/can_host_php", value:"no" );
    replace_kb_item( name:"www/" + port + "/can_host_asp", value:"no" );
  }

  set_kb_item( name:"pi-hole/detected", value:TRUE );
  set_kb_item( name:"pi-hole/http/detected", value:TRUE );
  set_kb_item( name:"pi-hole/http/port", value:port );

  # <b>Pi-hole Version </b> <span id="piholeVersion">v2.13.1</span>
  # <b>Pi-hole Version </b> v3.2.1
  # newer versions (5.1+) have:
  # <strong>Pi-hole</strong>
  # <a href="https://github.com/pi-hole/pi-hole/releases/v5.1.1" rel="noopener" target="_blank">v5.1.1</a>
  pihole_vers = eregmatch( string:res, pattern:'(<b>Pi-hole Version ?</b> ?|<strong>Pi-hole</strong>[^>]+>|"piholeVersion">)v([0-9.]+)' );
  if( pihole_vers[2] ) {
    pihole_version = pihole_vers[2];
    set_kb_item( name:"pi-hole/http/" + port + "/pihole_concluded", value:pihole_vers[0]);
  }

  # <b>Web Interface Version </b> <span id="webVersion">v2.5.1</span>
  # <b>Web Interface Version </b>v3.2.1
  # newer versions (5.1+) have:
  # <strong>Web Interface</strong>
  # <a href="https://github.com/pi-hole/AdminLTE/releases/v5.1" rel="noopener" target="_blank">v5.1</a>
  web_vers = eregmatch( string:res, pattern:'(<b>Web Interface Version ?</b> ?|<strong>Web Interface</strong>[^>]+>|"webVersion">)v([0-9.]+)' );
  if( web_vers[2] ) {
    web_version = web_vers[2];
    set_kb_item( name:"pi-hole/http/" + port + "/web_concluded", value:web_vers[0]);
  }

  # <b>FTL Version </b> vDev (v2.13.2, v2.13.2
  # <b>FTL Version </b> v3.0
  # newer versions (5.1+) have:
  # <strong>FTL</strong>
  # <a href="https://github.com/pi-hole/FTL/releases/v5.1" rel="noopener" target="_blank">v5.1</a>
  ftl_vers = eregmatch( string:res, pattern:"(<b>FTL Version ?</b> ?(vDev \()?|<strong>FTL</strong>[^>]+>)v([0-9.]+)" );
  if( ftl_vers[3] ) {
    ftl_version = ftl_vers[3];
    set_kb_item( name:"pi-hole/http/" + port + "/ftl_concluded", value:ftl_vers[0]);
  }

  if( pihole_version == "unknown" || web_version == "unknown" || ftl_version == "unknown" ) {
    pass = script_get_preference( "Pi-hole Web UI Password", id:1 );

    if( ! pass ) {
      extra += "  Note: No password for web authentication was provided. This could be provided for extended version extraction.";
    } else {
      url = "/api/auth";

      headers = make_array( "Content-Type", "application/json",
                            "X-Requested-With", "XMLHttpRequest" );

      data = '{"password":"' + pass + '"}';

      req = http_post_put_req( port:port, url:url, data:data, add_headers:headers );
      res = http_keepalive_send_recv( port:port, data:req );

      if( res =~ "^HTTP/1\.[01] 200" && res =~ '"sid"\\s*:\\s*"[^"]+"' ) {
        sid = eregmatch( pattern:'"sid"\\s*:\\s*"([^"]+)"', string:res );
        csrf = eregmatch( pattern:'"csrf"\\s*:\\s*"([^"]+)"', string:res );

        if( ! isnull( sid[1] ) && ! isnull( csrf[1] ) ) {
          url = "/api/info/version";

          headers = make_array( "Cookie", "sid=" + sid[1],
                                "X-Csrf-Token", csrf[1],
                                "X-Requested-With", "XMLHttpRequest" );

          req = http_get_req( port:port, url:url, add_headers:headers );
          res = http_keepalive_send_recv( port:port, data:req, bodyonly:TRUE );

          # e.g.:
          #
          # {"version":{"core":{"local":{"branch":"master","version":"v6.2.2","hash":"1837b754"},"remote":{"version":"v6.2.2","hash":"1837b754"}},"web":{"local":{"branch":"master","version":"v6.3","hash":"62c55dcf"},"remote":{"version":"v6.3","hash":"62c55dcf"}},"ftl":{"local":{"hash":"a0186fd4","branch":"master","version":"v6.3.3","date":"2025-11-04 20:18:48 +0100"},"remote":{"version":"v6.3.3","hash":"a0186fd4"}},"docker":{"local":null,"remote":null}},"took":4.3630599975585938e-05}
          #
          # or:
          #
          # {"version":{"core":{"local":{"version":"v6.2.2","branch":"master","hash":"1837b754"},"remote":{"version":"v6.2.2","hash":"1837b754"}},"web":{"local":{"version":"v6.3","branch":"master","hash":"62c55dcf"},"remote":{"version":"v6.3","hash":"62c55dcf"}},"ftl":{"local":{"hash":"a0186fd4","branch":"master","version":"v6.3.3","date":"2025-11-04 20:18:48 +0100"},"remote":{"version":"v6.3.3","hash":"a0186fd4"}},"docker":{"local":"2025.11.0","remote":"2025.11.0"}},"took":7.724761962890625e-05}
          #
          # nb:
          # - The "remote" seems to be for a check if the system is up2date and doesn't reflect the
          #   local installed version
          # - In some cases the "branch" and "hash" location differs / is mixed up (Maybe depends if
          #   installed via Docker or not) so the regex calls below have been made more generic
          vers = eregmatch( pattern:'"core"\\s*:\\s*\\{\\s*"local"\\s*:\\s*\\{[^}]*"version"\\s*:\\s*"v([0-9.]+)"',
                            string:res );
          if( ! isnull( vers[1] ) ) {
            pihole_version = vers[1];
            set_kb_item( name:"pi-hole/http/" + port + "/pihole_concluded", value:vers[0] );
            add_concl_url = TRUE;
          }

          vers = eregmatch( pattern:'"web"\\s*:\\s*\\{\\s*"local"\\s*:\\s*\\{[^}]*"version"\\s*:\\s*"v([0-9.]+)"',
                            string:res );
          if( ! isnull( vers[1] ) ) {
            web_version = vers[1];
            set_kb_item( name:"pi-hole/http/" + port + "/web_concluded", value:vers[0] );
            add_concl_url = TRUE;
          }

          vers = eregmatch( pattern:'"ftl"\\s*:\\s*\\{\\s*"local"\\s*:\\s*\\{[^}]*"version"\\s*:\\s*"v([0-9.]+)"',
                            string:res );
          if( ! isnull( vers[1] ) ) {
            ftl_version = vers[1];
            set_kb_item( name:"pi-hole/http/" + port + "/ftl_concluded", value:vers[0] );
            add_concl_url = TRUE;
          }

          if( add_concl_url )
            concludedUrl += '\n    ' + http_report_vuln_url( port:port, url:url, url_only:TRUE );
        }
      } else {
        extra += "  Note: Password was provided but authentication failed.";
      }
    }
  }

  set_kb_item( name:"pi-hole/http/" + port + "/pihole_version", value:pihole_version );
  set_kb_item( name:"pi-hole/http/" + port + "/web_version", value:web_version );
  set_kb_item( name:"pi-hole/http/" + port + "/ftl_version", value:ftl_version );
  set_kb_item( name:"pi-hole/http/" + port + "/concludedUrl", value:concludedUrl );
  if( banner_concluded )
    set_kb_item( name:"pi-hole/http/" + port + "/concludedBanner", value:banner_concluded );
  if( extra )
    set_kb_item( name:"pi-hole/http/" + port + "/error", value:extra );
}

exit( 0 );

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

12 Nov 2025 00:00Current
7High risk
Vulners AI Score7
51