Potential Self-Signed TLS Certificate Recently Issued on External Connection
Description
Identifies completed outbound TLS connections to external destinations where the server presents a recently issued, likely self-signed certificate whose issuer and subject distinguished names are equal. C2 frameworks frequently use freshly generated self-signed certificates instead of publicly trusted CAs. This behavioral logic complements hash-based C2 certificate rules, such as default Cobalt Strike team-server certificates, by catching rotated or custom infrastructure that does not reuse default tooling certificates. Distinguished-name equality identifies self-issued certificates but does not cryptographically prove that the certificate signed itself. The rule does not cover private-CA signed certificates, where issuer and subject differ, or C2 that uses publicly trusted certificates such as Let's Encrypt.
Query · esql
from logs-network_traffic.tls-*
| where
network.protocol == "tls"
and network.transport == "tcp"
and tls.established == true
and source.ip is not null
and destination.ip is not null
and tls.server.x509.not_before is not null
and tls.server.x509.issuer.distinguished_name is not null
and tls.server.x509.subject.distinguished_name is not null
and tls.server.x509.issuer.distinguished_name == tls.server.x509.subject.distinguished_name
and tls.server.x509.not_before >= now() - 30 days
and tls.server.x509.not_before <= now()
and CIDR_MATCH(
source.ip,
"10.0.0.0/8",
"100.64.0.0/10",
"172.16.0.0/12",
"192.168.0.0/16",
"fc00::/7"
)
and not CIDR_MATCH(
destination.ip,
"0.0.0.0/8",
"10.0.0.0/8",
"100.64.0.0/10",
"127.0.0.0/8",
"169.254.0.0/16",
"172.16.0.0/12",
"192.0.0.0/24",
"192.0.2.0/24",
"192.168.0.0/16",
"192.175.48.0/24",
"192.31.196.0/24",
"192.52.193.0/24",
"192.88.99.0/24",
"198.18.0.0/15",
"198.51.100.0/24",
"203.0.113.0/24",
"224.0.0.0/4",
"240.0.0.0/4",
"::/128",
"::1/128",
"2001:db8::/32",
"fc00::/7",
"fe80::/10",
"ff00::/8"
)
| stats
Esql.event_count = COUNT(*),
Esql.first_seen = MIN(@timestamp),
Esql.last_seen = MAX(@timestamp),
Esql.destination_port_values = MV_SLICE(VALUES(destination.port), 0, 9),
Esql.tls_client_server_name_values = MV_SLICE(VALUES(tls.client.server_name), 0, 9),
Esql.tls_server_x509_subject_common_name_values = MV_SLICE(VALUES(tls.server.x509.subject.common_name), 0, 9),
Esql.tls_server_x509_serial_number_values = MV_SLICE(VALUES(tls.server.x509.serial_number), 0, 4),
Esql.tls_server_hash_sha1_values = MV_SLICE(VALUES(tls.server.hash.sha1), 0, 4),
Esql.tls_server_hash_sha256_values = MV_SLICE(VALUES(tls.server.hash.sha256), 0, 4),
Esql.tls_server_x509_not_after_values = MV_SLICE(VALUES(tls.server.x509.not_after), 0, 4),
Esql.network_community_id_values = MV_SLICE(VALUES(network.community_id), 0, 9),
Esql.host_name_values = MV_SLICE(VALUES(host.name), 0, 9),
Esql.observer_name_values = MV_SLICE(VALUES(observer.name), 0, 19)
by
source.ip,
destination.ip,
tls.server.x509.subject.distinguished_name,
tls.server.x509.not_before
| keep
source.ip,
destination.ip,
tls.server.x509.subject.distinguished_name,
tls.server.x509.not_before,
Esql.event_count,
Esql.first_seen,
Esql.last_seen,
Esql.destination_port_values,
Esql.tls_client_server_name_values,
Esql.tls_server_x509_subject_common_name_values,
Esql.tls_server_x509_serial_number_values,
Esql.tls_server_hash_sha1_values,
Esql.tls_server_hash_sha256_values,
Esql.tls_server_x509_not_after_values,
Esql.network_community_id_values,
Esql.host_name_values,
Esql.observer_name_values
Investigation fields
Pivot points the source recommends for triage.
source.ipdestination.iptls.server.x509.subject.distinguished_nametls.server.x509.not_beforeEsql.event_countEsql.first_seenEsql.last_seenEsql.destination_port_valuesEsql.tls_client_server_name_valuesEsql.tls_server_x509_subject_common_name_valuesEsql.tls_server_x509_serial_number_valuesEsql.tls_server_hash_sha1_valuesEsql.tls_server_hash_sha256_valuesEsql.tls_server_x509_not_after_valuesEsql.network_community_id_valuesEsql.host_name_valuesEsql.observer_name_values
Implementation guide
This rule requires TLS certificate metadata from the Elastic network_traffic integration
(logs-network_traffic.tls-*) with send_certificates enabled (the Packetbeat TLS default) so ECS fields under
tls.server.x509.* are populated, including issuer.distinguished_name, subject.distinguished_name, and
not_before.
Packetbeat calculates SHA-1 certificate fingerprints by default. To populate tls.server.hash.sha256, add sha256 to
the TLS protocol analyzer's fingerprints setting.
Resumed TLS sessions typically do not include certificate fields and will not match. Legacy packetbeat-* indices are
intentionally not queried: this rule uses CIDR-based internal-to-external directionality that should be validated per
source mapping before claiming Packetbeat coverage.
Known false positives
- Development servers, lab environments, newly stood-up self-hosted services on public IPs (VPS, homelab, NAS, cameras, and similar IoT), and some vendor appliances may use recently issued self-signed certificates. Exclude known internal development egress or validated vendor destinations after review.
Analyst notes
Disclaimer: This investigation guide was created using generative AI technology and has been reviewed to improve its accuracy and relevance. While every effort has been made to ensure its quality, we recommend validating the content and adapting it to suit your specific environment and operational needs.
Investigating Potential Self-Signed TLS Certificate Recently Issued on External Connection
Malware and post-exploitation C2 often ships with ephemeral, self-signed TLS certificates rather than CA-issued
credentials. This rule matches external, successfully established TLS sessions where the server certificate issuer
matches the subject (self-issued and commonly self-signed) and the not_before date falls between 30 days ago and the
current time. Matching sessions are aggregated by source IP, destination IP, subject DN, and certificate not_before
so repeated full handshakes in the same detection window collapse into one alert. Distinguished-name equality alone
does not cryptographically verify the certificate signature.
This logic does not detect private-CA signed leaves (issuer DN differs from subject DN) or publicly trusted certificates.
Hash-based default-certificate rules remain complementary coverage for known tooling certs that are often years old.
Resumed TLS sessions typically omit tls.server.x509.* fields, so only full handshakes are eligible.
Possible investigation steps
- Review
source.ip,destination.ip,Esql.destination_port_values,tls.server.x509.subject.distinguished_name,Esql.tls_server_x509_serial_number_values,tls.server.x509.not_before, andEsql.tls_client_server_name_values. Common names can be empty on self-signed certificates; prefer the distinguished name and serial. - Compare SNI (
Esql.tls_client_server_name_values) with the certificate subject. A mismatch is a useful pivot, not proof of malice. - Pivot on destination IP and certificate serial or available SHA-1/SHA-256 fingerprints across other internal sources:
esql FROM logs-network_traffic.tls-* | WHERE tls.established == true AND tls.server.x509.issuer.distinguished_name == tls.server.x509.subject.distinguished_name | STATS event_count = COUNT(*), hosts = MV_SLICE(VALUES(source.ip), 0, 99) BY destination.ip, tls.server.x509.serial_number, tls.server.hash.sha1, tls.server.hash.sha256 | SORT event_count DESC - Correlate with endpoint alerts, DNS anomalies, or prior commodity C2 detections on the source host, including the Default Cobalt Strike Team Server Certificate rule.
- Compare certificate age and validity window against expected vendor or ACME renewal patterns. ACME-issued public certificates should not match this rule because they are not self-signed.
False positive analysis
- Internal developers testing against staging servers with self-signed certs may appear if traffic hairpins through external IPs or if staging is hosted outside RFC1918 / ULA space.
- Newly published self-hosted services (Proxmox, NAS, cameras, small-business appliances) often generate a self-signed certificate on first boot and will match for 30 days. Exclude by destination after validation.
- Some appliance vendors ship with short-lived factory self-signed certificates; exclude by destination after validation.
- Repeated alerts for the same source, destination, and certificate across intervals are expected while the certificate
remains inside the 30-day
not_beforewindow. Add a destination or serial exception after the first review if the traffic is authorized.
Response and remediation
- Isolate the source host if the destination is unknown and no authorized workflow explains the session.
- Block the destination IP or domain at the perimeter pending investigation.
- Preserve the available certificate hashes,
Esql.tls_server_x509_serial_number_values, and the subject DN for threat intel sharing. Collect a PCAP or full TLS metadata sample when available.