August 6, 2026
The Lesser-Known Art of Recon Using Favicon Hashes
A practical guide to fingerprinting technology stacks at scale — and why one tiny, forgotten image file can leak more about a target’s…
By Nothing
7 min read
A practical guide to fingerprinting technology stacks at scale — and why one tiny, forgotten image file can leak more about a target's infrastructure than an entire afternoon of manual poking.
Disclaimer
Everything in this post is written for authorized security testing only — bug bounty programs you're enrolled in, CTFs, labs, or engagements with signed scope. Running these techniques against assets you don't have permission to test is illegal. This content is part of the Offensive & Defensive Security Fundamentals series and is intended to build recon literacy for SOC analysts, VAPT learners, and ethical hacking beginners — always with a defensive lens alongside the offensive one.
Introduction: What Is a Favicon, Really?
Every time your browser loads a website, it quietly fires off a request to https://target.com/favicon.ico and renders whatever comes back as the tiny icon in your browser tab. It's such a low-priority cosmetic asset that most developers set it once, during initial project setup, and never think about it again.
That's exactly what makes it useful for recon.
Frameworks, CMSs, and self-hosted tools — Spring Boot, Jenkins, Grafana, GitLab, phpMyAdmin, Elasticsearch dashboards, countless IoT/router admin panels — all ship with a default favicon. If a dev team never replaces it (which is the norm, not the exception), that default icon stays live on production, quietly announcing what's running underneath.
The Core Idea: Hashing the Favicon
You can't practically "search the internet for this exact image," but you can hash it and search for the hash. The convention (established by Shodan, and widely adopted since) is:
- Fetch the favicon bytes
- Base64-encode them
- Run the base64 string through MurmurHash3 (mmh3)
- That output integer is the favicon hash
Two servers running the same software with an untouched default icon will produce the identical hash, regardless of domain name, IP, or geography. That single integer becomes a fingerprint for the underlying technology.
# Simplified favicon-hash.py logic (Python 3)
import mmh3
import codecs
import requests
response = requests.get("https://target.com/favicon.ico")
favicon_base64 = codecs.encode(response.content, "base64")
hash_value = mmh3.hash(favicon_base64)
print(hash_value)# Simplified favicon-hash.py logic (Python 3)
import mmh3
import codecs
import requests
response = requests.get("https://target.com/favicon.ico")
favicon_base64 = codecs.encode(response.content, "base64")
hash_value = mmh3.hash(favicon_base64)
print(hash_value)As an example, the default Spring Boot favicon consistently hashes to 116323821 — a value now well known enough in the community that it's practically a signature.
Favicon Hashes + Shodan: Searching the Whole Internet at Once
This is where the technique goes from "mildly interesting" to genuinely powerful. Shodan indexes favicon hashes as a searchable field:
http.favicon.hash:116323821http.favicon.hash:116323821Scope it to a specific organization:
org:"Target" http.favicon.hash:116323821org:"Target" http.favicon.hash:116323821Or pull it via the CLI for scripting:
shodan search org:"Target" http.favicon.hash:116323821 \
--fields ip_str,port --separator " " | awk '{print $1":"$2}'shodan search org:"Target" http.favicon.hash:116323821 \
--fields ip_str,port --separator " " | awk '{print $1":"$2}'The payoff here isn't just convenience — it's coverage. Shodan's index isn't limited to what you personally enumerated through DNS. It surfaces IPs with no DNS record pointing to them at all: forgotten staging servers, shadow IT, orphaned cloud instances, internal tools that were briefly exposed and forgotten. These are usually the least-maintained, highest-value targets, precisely because nobody's watching an asset they don't know exists.
Building a Recon Methodology Around It
Favicon hashing isn't a standalone trick — it's a phase that slots into a larger pipeline:
Subdomain Enum → Live Host Probing → Favicon Fingerprinting → Tech-Specific ExploitationSubdomain Enum → Live Host Probing → Favicon Fingerprinting → Tech-Specific ExploitationHere's the full asset-to-fingerprint flow visualized — starting from a single target domain and ending with a prioritized, fingerprint-sorted result set:
Reading it left to right: every asset type derived from example.com — subdomains, IPs/CIDR ranges, and domains surfaced through horizontal correlation — feeds into the same favicon-hashing stage. Those hashes get checked against a library of known fingerprints, matched against your own discovered assets, and the final output is a clean, prioritized list sorted by which fingerprint each asset matched.
Step 1 — Build the asset surface first
Standard recon: passive + active subdomain enumeration, CIDR range discovery, horizontal correlation, then filter everything down to confirmed live hosts using something like httpx.
cat cidr_ranges.txt | naabu -p 80,443,8080,8443 -silent | httpx -silent -o live_ips.txtcat cidr_ranges.txt | naabu -p 80,443,8080,8443 -silent | httpx -silent -o live_ips.txtNote the separation here — domains and raw IPs should be treated as two distinct input sets. Domains tell you what's running on assets you already know belong to the org via DNS. IPs (especially from a CIDR sweep) tell you what's running on infrastructure that has no DNS trail whatsoever — often the more interesting half.
Step 2 — Fetch and hash at scale
Feed both lists into a favicon-hashing tool. FavFreak (built specifically for this workflow) takes a list of URLs on stdin, fetches /favicon.ico for each, computes the hash, and sorts everything by hash value:
cat live_subdomains.txt live_ips.txt | python3 favfreak.py -o outputcat live_subdomains.txt live_ips.txt | python3 favfreak.py -o outputThis single pass — one GET request per host — is fast, low-noise, and indistinguishable from ordinary browser traffic, which makes it far stealthier than active banner-grabbing or aggressive tech-detection scans.
Step 3 — Match against a fingerprint dictionary
Every calculated hash gets checked against a curated fingerprints.json:
{
"116323821": "Spring Boot",
"-1220698868": "Jenkins",
"-297069493": "GitLab"
}{
"116323821": "Spring Boot",
"-1220698868": "Jenkins",
"-297069493": "GitLab"
}A match instantly tells you what known-issue playbook to reach for — no more blind, generic testing across every asset equally.
Step 4 — Prioritize by exploit potential
Not every match is equally interesting. Weight results by how exploitable the underlying tech typically is:
- High priority — admin panels and dev/ops tooling: Jenkins, GitLab, Grafana, Spring Boot Actuator
- Lower priority — generic CMS defaults, CDN boilerplate, static-site generators
Step 5 — Pivot outward via Shodan
Once you have a confirmed hash from your own recon, search it globally or scoped to the target's CIDR block to catch anything your enumeration missed:
shodan search net:"<target CIDR>" http.favicon.hash:116323821 --fields ip_str,portshodan search net:"<target CIDR>" http.favicon.hash:116323821 --fields ip_str,portStep 6 — Apply tech-specific playbooks
Once fingerprinted, stop guessing and go straight to known weak points:
Technology Known checkpoints Spring Boot /actuator/env, /actuator/heapdump, /actuator/health Jenkins Script console (/script), exposed build logs/credentials Grafana Default creds, /api/datasources credential leaks phpMyAdmin Default install artifacts, version-specific CVEs
This exact chain — fingerprint → Actuator endpoint check — is how one researcher [cited in the original inspiration for this technique] turned an exposed /heapdump and /env into a $4,300 bounty. The vulnerability itself wasn't novel; the favicon hash is what told them where to look first.
When the Fingerprint Comes Back "Unknown"
Not every hash will match your dictionary — plenty of custom or lesser-known stacks won't have a documented default favicon yet. When that happens, shift from targeted testing to a structured generic methodology instead of guessing randomly.
1. Manual identification first (before any brute-forcing):
- View source, check for JS framework signatures or leftover comments
- Inspect response headers —
Server,X-Powered-By, session cookie names (JSESSIONID→ Java,PHPSESSID→ PHP,laravel_session→ Laravel) - Run
whatwebor Wappalyzer — sometimes these succeed where favicon hashing doesn't - Check
/robots.txtand/sitemap.xmlfor framework-specific paths
2. Generic content discovery, if the stack still can't be identified:
ffuf -u https://sub.target.com/FUZZ -w common.txt -mc 200,301,302,403 -fs 0 -t 50ffuf -u https://sub.target.com/FUZZ -w common.txt -mc 200,301,302,403 -fs 0 -t 503. Target common sensitive files regardless of stack:
.env, .env.bak, .git/config, .git/HEAD, config.php, settings.py, application.properties, backup.zip, dump.sql, .DS_Store, docker-compose.yml, .htaccess, web.config, swagger.json — these leak across almost any deployment because they're artifacts of human habit, not framework convention.
4. Extension-aware fuzzing:
ffuf -u https://sub.target.com/FUZZ -w common.txt -e .php,.bak,.old,.zip,.sql,.env,.json,.yml,.config -mc 200ffuf -u https://sub.target.com/FUZZ -w common.txt -e .php,.bak,.old,.zip,.sql,.env,.json,.yml,.config -mc 2005. Re-fingerprint the moment anything surfaces. A single .git/config or config.php filename hit can retroactively identify the stack — feed that back into a targeted playbook.
Beyond Directory Discovery: The Full Attack Surface
An unknown-fingerprint target isn't a dead end — it's an invitation to run the broader web application methodology systematically:
- Recon extensions — JS file analysis for hardcoded endpoints/keys, parameter discovery (
arjun,x8), API surface mapping (/api/,/graphql,/swagger) - Authentication attacks — default creds, username enumeration via error/timing differences, weak password-reset flows, session token predictability
- Access control — IDOR via ID manipulation, vertical/horizontal privilege escalation, forced browsing to unlinked admin routes
- Injection testing — SQLi (including headers like
X-Forwarded-For), command injection on any server-side processing feature, XSS across every input surface, SSTI ({{7*7}}probes), XXE on XML-accepting endpoints - SSRF hunting — any feature that fetches a URL server-side (webhooks, "import from URL," PDF/image generators) tested against cloud metadata endpoints
- File upload abuse — disguised file types, polyglots, path traversal in filenames
- Business logic flaws — race conditions, price/coupon manipulation, workflow step-skipping, missing rate limits on OTP/login
- CORS misconfiguration — reflected
Originheader combined withAccess-Control-Allow-Credentials: true - Security misconfiguration — verbose stack traces (which can also retroactively unmask the "Unknown" fingerprint), missing security headers, directory listing, leftover debug endpoints
- Subdomain takeover — dangling CNAMEs pointing to unclaimed cloud services
Suggested sequencing: recon/JS/param discovery → manual functionality mapping → access control & IDOR → injection testing → SSRF checks → business logic last, since it requires actually understanding what the app does.
Troubleshooting: Zero Results on a Shodan Favicon Search
A hash query returning nothing doesn't necessarily mean the technique failed. Common causes:
- Indexing gaps — Shodan crawls on its own schedule; the asset may simply not have been crawled recently, especially on non-standard ports.
- Calculation error — verify the hash pipeline is base64-encoding the favicon before hashing, not hashing raw bytes. Test against a favicon with a known, already-indexed hash to confirm your script's output matches expectations.
- Not internet-facing in a discoverable way — behind Cloudflare (Shodan often only sees Cloudflare's edge IP, not the origin), VPN-gated, or genuinely internal.
- Query syntax slip — a stray typo in
org:ornet:filters silently zeroes the result set.
Workflow to isolate the cause:
# 1. Recalculate manually and compare
curl -s https://sub.target.com/favicon.ico -o fav.ico
python3 favicon-hash.py fav.ico
# 2. Drop all filters, confirm the hash exists anywhere in Shodan's index
shodan search http.favicon.hash:116323821 --limit 5# 1. Recalculate manually and compare
curl -s https://sub.target.com/favicon.ico -o fav.ico
python3 favicon-hash.py fav.ico
# 2. Drop all filters, confirm the hash exists anywhere in Shodan's index
shodan search http.favicon.hash:116323821 --limit 5If it still returns nothing globally, the hash/tech pairing likely just isn't well represented in Shodan's current index — that's a Shodan coverage limitation, not a flaw in the technique. Censys is worth trying as a second data source, since its crawl schedule and index don't perfectly overlap with Shodan's. And critically: a zero-result Shodan search doesn't invalidate assets you already found through direct favicon fetching in your own recon — that data stands on its own regardless of what Shodan's index shows.
Why This Technique Matters
Pulling it all together, favicon hashing earns its place in a recon methodology for a few concrete reasons:
- Low noise — a single
GET /favicon.icois indistinguishable from normal browser behavior, unlike aggressive active tech-detection. - Speed at scale — one request per host, hashed instantly, parallelizable across thousands of assets.
- Prioritization — turns an undifferentiated list of live hosts into a ranked list of "check this first" targets tied to known playbooks.
- Surface expansion — Shodan/Censys pivoting surfaces infrastructure with zero DNS footprint, often the least-monitored and highest-risk part of an organization's attack surface.
- Leaks through obfuscation — even a hardened app with stripped headers and minified JS can still leak its stack through a favicon nobody thought to change.
That last point is really the whole story: favicon.ico is treated as cosmetic, and cosmetic things don't get security review. That gap is precisely what makes it worth checking.
This post is part of my ongoing work building recon methodology and tooling for the security community — pairing offensive fingerprinting techniques with the defensive mindset needed to catch them.