Censys

security

Censys

Definition

Censys is a security intelligence platform that performs continuous internet-wide scans, cataloging hosts, services, certificates, and software across the public internet. Founded in 2013 by Zakir Durumeric, David Adrian, and J. Alex Halderman at the University of Michigan, Censys emerged from academic research on internet-wide network scanning and built upon the ZMap scanner -- a tool capable of scanning the entire IPv4 address space in under 45 minutes. This academic pedigree distinguishes Censys from commercial-first alternatives, bringing rigorous methodology and reproducible scanning practices to the security intelligence domain.

Censys differentiates itself through its deep focus on TLS/SSL certificate transparency, its structured and strongly-typed data model, and its comprehensive coverage of the X.509 certificate ecosystem. While Shodan excels at service and device discovery through banner grabbing, Censys provides superior visibility into the certificate landscape -- tracking certificate issuance, expiration, chain validity, and compliance with certificate transparency (CT) requirements. This complementary focus makes Censys an essential corroborating data source for multi-signal security assessments.

The platform offers both a web interface for interactive exploration and a RESTful API for programmatic integration. Censys continuously refreshes its dataset through regular full-internet scans, maintaining a historical record of how the internet's infrastructure evolves over time. This longitudinal data enables trend analysis, tracking how an organization's attack surface changes across weeks, months, and years.

Scanning Methodology

Censys's scanning infrastructure is built on ZMap and ZGrab, providing a two-phase approach to internet-wide data collection:

PhaseToolPurposePerformance
Host DiscoveryZMapSYN scan across all IPv4 on target portsFull IPv4 in < 45 minutes
Service ProbingZGrab2Application-layer handshake and data collectionProtocol-specific deep inspection
Certificate CollectionCT Log MonitorsCertificate Transparency log aggregationContinuous, near-real-time
Data EnrichmentCensys PipelineASN, geolocation, WHOIS, reverse DNSAutomated post-processing

The two-phase approach first identifies live hosts through rapid TCP SYN scanning (ZMap), then performs detailed application-layer probes (ZGrab2) to collect service banners, TLS certificates, HTTP headers, and protocol-specific metadata. This separation allows Censys to scan efficiently without sacrificing depth of inspection.

ZMap (SYN scan)  -->  Responsive IPs  -->  ZGrab2 (service probes)
                                                  |
                                         +--------+--------+
                                         |        |        |
                                       HTTP     TLS    Protocol-
                                      Headers   Certs   Specific
                                                  |
                                         CT Log Integration
                                                  |
                                         Censys Search Index

ZMap Architecture

ZMap achieves its remarkable scanning speed through several design choices that differ fundamentally from traditional port scanners like Nmap. Rather than maintaining a state table of outstanding connections (which would consume gigabytes of memory at internet scale), ZMap uses a stateless scanning approach based on cyclic multiplicative groups. Each probe packet encodes its identity in the IP ID and TCP sequence number fields, allowing ZMap to correlate responses without maintaining per-connection state. This mathematical approach means ZMap's memory usage is constant regardless of the number of hosts being scanned.

The scanning pipeline operates in three concurrent stages: a packet generation stage that iterates through the IP address space in a pseudo-random order (ensuring even distribution across networks), a raw packet transmission stage that sends SYN packets at line rate, and a response collection stage that processes SYN-ACK responses. The pseudo-random iteration order is critical for avoiding concentrating scan traffic on any single network, which would trigger rate limiting or IDS alerts.

ZGrab2 Service Probing

ZGrab2 extends ZMap's host discovery with application-layer protocol analysis. For each responsive host, ZGrab2 initiates full protocol handshakes to extract detailed service information:

ProtocolData CollectedSecurity Relevance
HTTPSTLS version, cipher suites, certificate chain, HTTP headersEncryption strength, certificate validity
HTTPResponse headers, server software, redirect chainsSoftware versions, misconfiguration
SSHKey exchange algorithms, host key fingerprintWeak algorithms, key reuse
SMTPBanner, STARTTLS support, authentication methodsEmail security posture
FTPBanner, anonymous access, TLS supportSensitive file exposure
DNSRecursive resolution, zone transfer, DNSSECDNS infrastructure assessment
RDPSecurity negotiation, NLA supportRemote access exposure

Data Model

Censys provides a well-structured data model organized around three primary entities:

EntityDescriptionKey Fields
HostsIndividual IP addresses with all observed servicesIP, ports, services, autonomous_system, location, operating_system
ServicesPort-level service instances on a hostport, transport_protocol, service_name, banner, software, tls
CertificatesX.509 certificates observed in the wildfingerprint, subject, issuer, validity, key_algorithm, ct_logs

Host Data Structure

{
  "ip": "93.184.216.34",
  "services": [
    {
      "port": 443,
      "service_name": "HTTP",
      "transport_protocol": "TCP",
      "tls": {
        "version_selected": "TLSv1.3",
        "cipher_selected": "TLS_AES_256_GCM_SHA384",
        "certificates": {
          "leaf_fp_sha_256": "abc123...",
          "chain_fps_sha_256": ["def456...", "ghi789..."]
        }
      },
      "http": {
        "response": {
          "status_code": 200,
          "headers": {"server": "nginx/1.24.0"}
        }
      }
    }
  ],
  "autonomous_system": {
    "asn": 15133,
    "name": "EDGECAST",
    "bgp_prefix": "93.184.216.0/24"
  },
  "location": {
    "country_code": "US",
    "city": "Norwell"
  }
}

Service Fingerprinting

Censys applies sophisticated service fingerprinting to identify the software running on each service, going beyond simple banner matching. The fingerprinting engine analyzes multiple signals including HTTP response headers (Server, X-Powered-By, X-AspNet-Version), TLS implementation quirks (cipher suite ordering, extension support), protocol-specific behaviors (SSH key exchange algorithm preferences), and JARM fingerprints (active TLS server fingerprinting). This multi-signal approach achieves higher accuracy than any single identification method.

Certificate Transparency Integration

Censys has one of the most comprehensive Certificate Transparency (CT) datasets, aggregating certificates from all major CT logs. This is critical for security monitoring:

CT CapabilityDescriptionSecurity Value
Certificate inventoryAll certificates issued for a domainDetect unauthorized certificate issuance
Pre-certificate monitoringCertificates logged before issuanceEarly warning of suspicious certificate requests
Issuer analysisTrack which CAs issue certificates for a domainDetect CA compromise or policy violations
Expiration trackingMonitor certificate lifetimesPrevent service outages from expired certificates
SAN enumerationExtract Subject Alternative NamesDiscover subdomains and related infrastructure
Historical certificatesArchived expired and revoked certificatesInvestigate past infrastructure configurations

Certificate transparency data is particularly valuable for EASM because it reveals infrastructure that may not be discoverable through port scanning alone. A certificate issued for internal-staging.example.com reveals the existence of that subdomain even if the server is not currently accessible.

CT Log Processing Pipeline

Censys monitors all major Certificate Transparency logs in near-real-time, processing millions of certificates daily. The CT pipeline performs several enrichment steps on each certificate: extracting all Subject Alternative Names (SANs) to build a comprehensive domain-to-certificate mapping, computing certificate chain validity by walking the issuer chain to known root certificates, detecting wildcard certificates that may cover undiscovered subdomains, and identifying certificates with unusually long validity periods that may indicate misconfiguration or policy violations.

API Integration

Censys provides a RESTful Search API (v2) for programmatic access to its dataset:

# Censys API client for Prismatic EASM
defmodule PrismaticPerimeter.Sources.Censys do
  @moduledoc "Censys Search API v2 integration for attack surface discovery."

  @base_url "https://search.censys.io/api/v2"

  @type host_result :: %{
    ip: String.t(),
    services: [service()],
    autonomous_system: map(),
    location: map(),
    last_updated: DateTime.t()
  }

  @doc "Search Censys hosts index"
  @spec search_hosts(String.t(), keyword()) :: {:ok, [host_result()]} | {:error, term()}
  def search_hosts(query, opts \\ []) do
    params = %{
      q: query,
      per_page: Keyword.get(opts, :per_page, 50),
      cursor: Keyword.get(opts, :cursor, nil)
    }

    case authenticated_request(:get, "/hosts/search", params) do
      {:ok, %{status: 200, body: body}} ->
        {:ok, parse_host_results(body["result"]["hits"])}

      {:ok, %{status: 429}} ->
        {:error, :rate_limited}

      {:ok, %{status: status, body: body}} ->
        {:error, {:api_error, status, body}}

      {:error, reason} ->
        {:error, {:connection_error, reason}}
    end
  end

  @doc "Search for certificates matching a domain"
  @spec search_certificates(String.t()) :: {:ok, [map()]} | {:error, term()}
  def search_certificates(domain) do
    query = "parsed.names: #{domain}"

    case authenticated_request(:get, "/certificates/search", %{q: query}) do
      {:ok, %{status: 200, body: body}} ->
        {:ok, body["result"]["hits"]}

      {:error, reason} ->
        {:error, reason}
    end
  end

  @doc "Search for hosts associated with a domain"
  @spec domain_search(String.t()) :: {:ok, [host_result()]} | {:error, term()}
  def domain_search(domain) do
    search_hosts("services.tls.certificates.leaf_data.names: #{domain}")
  end

  @doc "Aggregate host data by autonomous system for a given domain"
  @spec aggregate_by_asn(String.t()) :: {:ok, [map()]} | {:error, term()}
  def aggregate_by_asn(domain) do
    params = %{
      q: "services.tls.certificates.leaf_data.names: #{domain}",
      field: "autonomous_system.asn",
      num_buckets: 50
    }

    case authenticated_request(:get, "/hosts/aggregate", params) do
      {:ok, %{status: 200, body: body}} ->
        {:ok, body["result"]["buckets"]}

      {:ok, %{status: status, body: body}} ->
        {:error, {:api_error, status, body}}

      {:error, reason} ->
        {:error, {:connection_error, reason}}
    end
  end

  defp authenticated_request(method, path, params) do
    headers = [
      {"Authorization", "Basic #{Base.encode64("#{api_id()}:#{api_secret()}")}"},
      {"Accept", "application/json"}
    ]

    HTTPClient.request(method, "#{@base_url}#{path}", headers: headers, params: params)
  end

  defp api_id, do: Application.get_env(:prismatic_perimeter, :censys_api_id)
  defp api_secret, do: Application.get_env(:prismatic_perimeter, :censys_api_secret)
end

Censys Search Language

Censys provides a structured query language for searching its dataset. Unlike Shodan's simple key:value filter syntax, the Censys Search Language supports boolean operators, nested field access, wildcards, and range queries:

Query TypeSyntaxExample
Exact matchfield: "value"services.http.response.headers.server: "nginx"
Boolean ANDfield1: val1 AND field2: val2location.country_code: "CZ" AND services.port: 443
Boolean ORfield1: val1 OR field2: val2services.port: 80 OR services.port: 443
NegationNOT field: valueNOT services.tls.certificates.leaf_data.issuer.organization: "Let's Encrypt"
Wildcardfield: value*services.tls.certificates.leaf_data.names: *.example.com
Rangefield: [min TO max]services.port: [8000 TO 9000]
Existencefield: *services.ssh: * (hosts with SSH)
defmodule PrismaticPerimeter.Sources.Censys.QueryBuilder do
  @moduledoc "Type-safe query builder for Censys Search Language."

  @type query :: String.t()

  @spec for_domain(String.t()) :: query()
  def for_domain(domain) do
    "services.tls.certificates.leaf_data.names: #{domain}"
  end

  @spec for_organization(String.t()) :: query()
  def for_organization(org) do
    "services.tls.certificates.leaf_data.subject.organization: \"#{org}\""
  end

  @spec with_port(query(), integer()) :: query()
  def with_port(base_query, port) do
    "#{base_query} AND services.port: #{port}"
  end

  @spec in_country(query(), String.t()) :: query()
  def in_country(base_query, country_code) do
    "#{base_query} AND location.country_code: \"#{country_code}\""
  end

  @spec excluding_cdn(query()) :: query()
  def excluding_cdn(base_query) do
    cdn_asns = ["AS13335", "AS20940", "AS16509"]
    exclusions = Enum.map_join(cdn_asns, " AND ", fn asn ->
      "NOT autonomous_system.asn: #{String.trim_leading(asn, "AS")}"
    end)
    "#{base_query} AND #{exclusions}"
  end
end

Context in Prismatic

Censys serves as a complementary data source alongside Shodan in the Prismatic Perimeter module. The platform queries Censys for certificate data, host services, and autonomous system information during attack surface discovery. As required by the Signal Plurality axiom, Censys findings corroborate or contradict Shodan data, providing the independent second signal needed for confident security assessments.

The integration leverages Censys's specific strengths in the multi-source discovery pipeline:

Use CaseCensys AdvantagePipeline Role
Certificate discoveryDeep CT integration, comprehensive cert databasePrimary source for certificate-based asset discovery
TLS assessmentDetailed cipher suite and protocol analysisCorroboration of Shodan TLS findings
Subdomain enumerationSAN field extraction from certificatesComplementary to DNS-based enumeration
Historical analysisLongitudinal data on host changesTime decay calibration
ASN mappingAccurate autonomous system attributionOrganization boundary definition

When Shodan and Censys agree on a finding (e.g., both detect an expired TLS certificate on the same host), the finding receives a high confidence score. When they disagree, the platform preserves both signals per the Contradiction Preservation axiom and flags the finding for analyst review.

Multi-Source Correlation Pipeline

The Prismatic Platform uses Censys data as part of a multi-source correlation pipeline that synthesizes findings from multiple OSINT providers:

defmodule PrismaticPerimeter.CorrelationPipeline do
  @moduledoc "Multi-source OSINT correlation integrating Censys with other providers."

  @spec correlate_findings(String.t()) :: {:ok, map()} | {:error, term()}
  def correlate_findings(domain) do
    # Execute all source queries in parallel
    tasks = %{
      censys: Task.async(fn -> PrismaticPerimeter.Sources.Censys.domain_search(domain) end),
      shodan: Task.async(fn -> PrismaticPerimeter.Sources.Shodan.domain_search(domain) end),
      greynoise: Task.async(fn -> PrismaticPerimeter.Sources.GreyNoise.ip_context(domain) end)
    }

    results = Map.new(tasks, fn {source, task} ->
      {source, Task.await(task, 30_000)}
    end)

    with {:ok, censys_data} <- results.censys,
         {:ok, shodan_data} <- results.shodan do
      correlated = merge_and_score(censys_data, shodan_data, results.greynoise)
      {:ok, correlated}
    end
  end

  defp merge_and_score(censys_hosts, shodan_hosts, greynoise_result) do
    # Group by IP for cross-source comparison
    censys_by_ip = Map.new(censys_hosts, &{&1.ip, &1})
    shodan_by_ip = Map.new(shodan_hosts, &{&1.ip, &1})

    all_ips = MapSet.union(
      MapSet.new(Map.keys(censys_by_ip)),
      MapSet.new(Map.keys(shodan_by_ip))
    )

    Enum.map(all_ips, fn ip ->
      censys_entry = Map.get(censys_by_ip, ip)
      shodan_entry = Map.get(shodan_by_ip, ip)

      %{
        ip: ip,
        sources: compute_sources(censys_entry, shodan_entry),
        confidence: compute_confidence(censys_entry, shodan_entry),
        services: merge_services(censys_entry, shodan_entry),
        noise_classification: classify_noise(ip, greynoise_result)
      }
    end)
  end
end

Censys vs. Shodan Comparison

DimensionCensysShodan
OriginAcademic (University of Michigan)Independent (John Matherly)
Scan engineZMap + ZGrab2 (open source)Proprietary crawlers
Certificate dataDeep (CT log integration, full chain analysis)Basic (certificate extraction only)
ICS/SCADAModerate coverageExtensive (Modbus, S7, BACnet, EtherNet/IP)
Data freshnessRegular full-internet scansContinuous crawling
Query languageCensys Search Language (structured)Filter-based (key:value)
AuthenticationAPI ID + Secret (HTTP Basic)API key
Historical dataYes (paid tiers)Yes (paid tiers)
Academic usageFree academic access programLimited free tier
Prismatic roleCertificate corroboration, TLS assessmentPrimary service/device discovery
IPv6 supportGrowing coverageLimited
Aggregate queriesBuilt-in aggregation APIManual post-processing

API Tiers and Rate Limits

TierQueries/MonthResults/QueryFeatures
Community250100Basic search, limited API
Solo25,0001,000Full API, historical data
Teams250,00010,000Team access, advanced analytics
EnterpriseCustomUnlimitedDedicated infrastructure, SLA

The Prismatic Platform implements rate limiting for Censys API calls using a sliding window algorithm calibrated to the subscription tier. Failed requests due to rate limiting are automatically retried with exponential backoff.

defmodule PrismaticPerimeter.Sources.Censys.RateLimiter do
  @moduledoc "Sliding window rate limiter for Censys API integration."

  use GenServer

  @type state :: %{
    window_ms: non_neg_integer(),
    max_requests: non_neg_integer(),
    requests: :queue.queue(integer())
  }

  @spec request_allowed?() :: boolean()
  def request_allowed? do
    GenServer.call(__MODULE__, :check_rate)
  end

  @spec record_request() :: :ok
  def record_request do
    GenServer.cast(__MODULE__, :record)
  end

  @impl true
  def handle_call(:check_rate, _from, state) do
    now = System.monotonic_time(:millisecond)
    cleaned = prune_old_requests(state.requests, now - state.window_ms)
    allowed = :queue.len(cleaned) < state.max_requests
    {:reply, allowed, %{state | requests: cleaned}}
  end

  @impl true
  def handle_cast(:record, state) do
    now = System.monotonic_time(:millisecond)
    {:noreply, %{state | requests: :queue.in(now, state.requests)}}
  end
end

Security Considerations

When integrating Censys data into security assessments, several considerations apply:

ConsiderationDescriptionMitigation
Data stalenessScan data may be hours or days oldCross-reference with active probing
Incomplete coverageNot all ports/protocols scanned every cycleSupplement with Shodan and active scanning
False positivesService identification may be inaccurateValidate findings with direct connection
IPv6 gapsIPv6 coverage is growing but incompleteCombine with DNS-based IPv6 enumeration
CDN maskingCDN IPs may not reflect origin infrastructureUse certificate SANs to identify origins
Geolocation accuracyIP geolocation is approximateUse ASN data for organizational attribution

Best Practices for Censys Integration

When building integrations with the Censys API, the following practices ensure reliable and efficient operation within the Prismatic Platform:

  1. Prefer certificate-based discovery over host scanning -- Censys's CT log integration provides the most unique value compared to other OSINT sources. Use Censys primarily for certificate intelligence and leverage Shodan for service discovery.

  2. Use aggregate queries for broad analysis -- The aggregation API provides statistical summaries without consuming per-result quota. Use aggregates to understand the distribution of services, ASNs, or software versions before drilling into individual hosts.

  3. Cache responses with TTL matching scan frequency -- Censys data refreshes on a schedule (not continuously). Caching responses for 6-12 hours avoids redundant API calls without missing significant changes.

  4. Implement circuit breakers for API failures -- Network issues and rate limiting can cause cascade failures in the discovery pipeline. Use circuit breaker patterns to gracefully degrade when Censys is unavailable.

  5. Always validate confidence with Signal Plurality -- Never rely on Censys alone for security assertions. Every finding must be corroborated by at least one independent source.

  • Shodan - Complementary internet scanning platform for service discovery
  • EASM - Attack surface management consuming Censys data
  • Signal Plurality - Axiom requiring Censys as corroborating source
  • Attack Surface - External infrastructure Censys helps discover
  • GreyNoise - Noise classification complementing Censys findings
  • TLS - Certificate and protocol analysis from Censys scans
  • Risk Score - Ratings informed by Censys-validated findings
  • Rate Limiting - API quota management for Censys integration
  • Time Decay - Historical Censys data calibrated for recency
  • Contradiction Preservation - Handling Shodan/Censys disagreements
  • Knowledge Graph - Graph database storing correlated Censys findings
  • Entity Resolution - Linking Censys host data to organizational entities

See Also

  • Architecture - EASM data source architecture
  • Apps - Prismatic Perimeter application
  • OSINT - Open-source intelligence collection methodology

Connect & Contribute

Created by Tomáš Korcak (korczis) | Open Source under GHL