# 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