Overview
Censys continuously scans the entire internet and indexes every reachable host with detailed information about open ports, TLS/SSL certificates, service banners, and configurations. Censys is particularly strong for certificate-based discovery — it indexes certificate transparency logs and lets you pivot from certificate subject names to IP addresses and vice versa. This makes it excellent for finding unknown infrastructure tied to a target organization.
Requires: Censys API key (free account at censys.io gives 250 queries/month).
Instructions
Step 1: Install and configure
pip install censys
import os
from censys.search import CensysHosts, CensysCerts
from censys.common.exceptions import CensysRateLimitExceededException, CensysNotFoundException
import json
import time
# Set credentials via environment variables (recommended)
# export CENSYS_API_ID="your-api-id"
# export CENSYS_API_SECRET="your-api-secret"
# Or pass directly
CENSYS_API_ID = os.getenv("CENSYS_API_ID", "YOUR_API_ID")
CENSYS_API_SECRET = os.getenv("CENSYS_API_SECRET", "YOUR_API_SECRET")
# Initialize clients
h = CensysHosts(api_id=CENSYS_API_ID, api_secret=CENSYS_API_SECRET)
# Check account quota
account = h.account()
print(f"Quota: {account.get('quota', {})}")
Step 2: Search for hosts by query
def search_hosts(query, max_results=100, fields=None):
"""
Search Censys for hosts matching a query.
Common query examples:
- services.tls.certificates.leaf_data.subject.common_name: "example.com"
- services.port: 3389 and autonomous_system.name: "Company"
- services.http.response.html_title: "Dashboard"
- services.service_name: "REDIS" and not ip: "10.0.0.0/8"
"""
if fields is None:
fields = ["ip", "services.port", "services.service_name", "autonomous_system.name",
"autonomous_system.asn", "location.country", "services.tls.certificates.leaf_data.subject.common_name"]
print(f"Searching: {query}")
results = []
try:
for hit in h.search(query, fields=fields, pages=max_results // 100 + 1):
results.append(hit)
if len(results) >= max_results:
break
time.sleep(0.1) # Gentle rate limiting
except CensysRateLimitExceededException:
print("Rate limit reached. Results so far:")
print(f"Found {len(results)} hosts")
for r in results[:20]:
ip = r.get("ip")
services = r.get("services", [])
ports = [str(s.get("port", "?")) for s in services]
asn_name = r.get("autonomous_system", {}).get("name", "N/A")
country = r.get("location", {}).get("country", "N/A")
print(f" {ip:<20} ports: {','.join(ports):<20} {asn_name} ({country})")
return results
# Find hosts serving TLS certs for a domain
search_hosts('services.tls.certificates.leaf_data.subject.common_name: "*.example.com"')
# Find exposed Redis servers
search_hosts('services.service_name: "REDIS"', max_results=50)
# Find hosts in a specific org
search_hosts('autonomous_system.name: "Example Corporation" and services.port: 443')
Step 3: Look up a specific IP address
def lookup_host(ip_address):
"""Get detailed information about a specific IP from Censys."""
try:
host = h.view(ip_address)
print(f"\n=== Censys Host View: {ip_address} ===")
print(f"IP: {host.get('ip')}")
asn = host.get("autonomous_system", {})
print(f"ASN: {asn.get('asn')} — {asn.get('name')} ({asn.get('country_code')})")
loc = host.get("location", {})
print(f"Location: {loc.get('city')}, {loc.get('country')}")
print(f"\nServices:")
for service in host.get("services", []):
port = service.get("port")
svc_name = service.get("service_name", "unknown")
transport = service.get("transport_protocol", "tcp")
banner = service.get("banner", "")[:80]
tls = service.get("tls", {})
cert_cn = ""
if tls:
leaf = tls.get("certificates", {}).get("leaf_data", {})
cert_cn = leaf.get("subject", {}).get("common_name", "")
print(f" {port}/{transport} — {svc_name}", end="")
if cert_cn:
print(f" | cert: {cert_cn}", end="")
if banner:
print(f" | banner: {banner}", end="")
print()
return host
except CensysNotFoundException:
print(f"Host {ip_address} not found in Censys.")
return None
lookup_host("8.8.8.8")
Step 4: Certificate-based discovery
def find_hosts_by_domain_cert(domain, include_subdomains=True):
"""
Find all IP addresses serving TLS certificates for a domain.
This is highly effective for finding unknown/shadow infrastructure.
"""
if include_subdomains:
query = f'services.tls.certificates.leaf_data.names: "{domain}"'
else:
query = f'services.tls.certificates.leaf_data.subject.common_name: "{domain}"'
fields = [
"ip",
"services.port",
"services.tls.certificates.leaf_data.subject.common_name",
"services.tls.certificates.leaf_data.names",
"services.tls.certificates.leaf_data.issuer.common_name",
"autonomous_system.name",
"location.country",
]
print(f"Finding hosts with TLS certs for: {domain}")
results = []
for hit in h.search(query, fields=fields, pages=5):
results.append(hit)
print(f"\n{len(results)} hosts found:\n")
for r in results:
ip = r.get("ip")
asn = r.get("autonomous_system", {}).get("name", "?")
country = r.get("location", {}).get("country", "?")
services = r.get("services", [])
for svc in services:
tls = svc.get("tls", {})
if tls:
leaf = tls.get("certificates", {}).get("leaf_data", {})
cn = leaf.get("subject", {}).get("common_name", "")
names = leaf.get("names", [])
issuer = leaf.get("issuer", {}).get("common_name", "")
port = svc.get("port")
print(f" {ip}:{port} | CN: {cn} | SAN: {names[:3]} | Issuer: {issuer} | {asn} ({country})")
return results
find_hosts_by_domain_cert("example.com")
Step 5: Aggregation queries
def aggregate_query(query, field, num_buckets=10):
"""
Aggregate Censys results to get a distribution overview.
Useful for understanding what products/versions/countries/orgs are common.
"""
result = h.aggregate(query, field, num_buckets=num_buckets)
print(f"\nAggregation: {query}")
print(f"Field: {field}")
print(f"Total matching: {result.get('total', 0):,}")
print(f"\nTop {num_buckets} values:")
for bucket in result.get("buckets", []):
print(f" {bucket['key']:<50} {bucket['count']:>10,}")
# Distribution of countries for Apache servers on port 80
aggregate_query("services.http.response.headers.Server: Apache", "location.country", 15)
# Distribution of services for a specific ASN
aggregate_query("autonomous_system.asn: 15169", "services.service_name", 20)
# TLS version distribution
aggregate_query("services.tls: *", "services.tls.version_selected", 10)
Step 6: Export results to file
def export_hosts_to_json(query, output_file, max_results=500):
"""Export Censys search results to a JSON file for offline analysis."""
print(f"Exporting up to {max_results} hosts for query: {query}")
results = []
for hit in h.search(query, pages=max_results // 100 + 1):
results.append(hit)
if len(results) >= max_results:
break
with open(output_file, "w") as f:
json.dump(results, f, indent=2)
print(f"Exported {len(results)} records to {output_file}")
return results
# Export all exposed Elasticsearch instances
export_hosts_to_json(
'services.service_name: "ELASTICSEARCH" and services.elasticsearch.indices_count > 0',
"exposed_elasticsearch.json",
max_results=200
)
Censys Query Language Reference
| Query | Description |
|---|---|
services.port: 443 | Hosts with port 443 open |
services.service_name: "HTTP" | Hosts running HTTP |
services.tls.certificates.leaf_data.subject.common_name: "*.example.com" | Wildcard cert for domain |
services.tls.certificates.leaf_data.names: "example.com" | Any cert naming the domain |
autonomous_system.name: "Amazon" | Hosts in Amazon ASN |
autonomous_system.asn: 16509 | Hosts in ASN 16509 |
location.country: "Germany" | Hosts in Germany |
ip: "8.8.8.0/24" | Hosts in CIDR range |
services.http.response.html_title: "Kibana" | Exposed Kibana instances |
labels: "cloud" | Cloud-hosted infrastructure |
Guidelines
- Certificate pivoting: The most powerful Censys use case is pivoting from a known domain → certificate → IPs → more domains. This often reveals shadow IT and forgotten assets.
- Quota management: Free accounts have 250 queries/month. Use aggregation queries to preview result counts before pulling full data.
- Combine with Shodan: Censys and Shodan index different things. Use both for complete coverage. Censys is stronger on TLS/certificate data; Shodan is stronger on IoT and raw service banners.
- Historical data: Censys Search 2.0 does not provide historical data by default. Use the Censys Data platform for historical snapshots.
- SDK vs REST: The Python SDK handles authentication, pagination, and rate limiting automatically. Prefer it over raw REST calls.