🚀 Executive Summary

TL;DR: Security teams often spend significant time manually sifting through Certificate Transparency (CT) logs to identify lookalike domains for phishing. This article provides a Python script to automate monitoring CT logs for suspicious domains and send Slack alerts, saving time and providing an early warning system against potential threats.

🎯 Key Takeaways

  • A Python script can automate querying `crt.sh` to fetch newly issued SSL certificates for subdomains of a target domain.
  • Suspicious domains are identified by filtering certificate common names for the base domain combined with phishing-related keywords like ‘login’, ‘secure’, or ‘account’.
  • Secure configuration management using `config.env` and `python-dotenv` is crucial for storing sensitive data like Slack webhooks and target domains, preventing hardcoding and accidental exposure.

Track SSL Certificate Transparency Logs for Phishing Domains

Track SSL Certificate Transparency Logs for Phishing Domains

Hey everyone, Darian Vance here. Let’s talk about a task that used to be a real time-sink for me: hunting for lookalike domains targeting our company. I’d manually sift through Certificate Transparency (CT) logs, trying to spot phishing attempts before they went live. It felt like necessary work, but it easily ate up two hours every week. I eventually automated the whole process, and it’s been a game-changer. It not only saves me time but also gives our security team a critical head-start on potential threats. I want to walk you through the script I built so you can set up something similar.

Prerequisites

Before we dive in, make sure you have a few things ready:

  • Python 3 installed on your system.
  • A Slack workspace where you can create an Incoming Webhook for alerts.
  • A target domain you want to monitor (e.g., ‘techresolve.com’).
  • Basic comfort with running Python scripts and managing environment variables.

The Guide: Step-by-Step

Step 1: Project Setup

First, create a new directory for this project. Inside that directory, we’ll need two files: our Python script, which I’ll call monitor_certs.py, and a configuration file named config.env to store our secrets securely. I’ll skip the standard virtualenv setup since you likely have your own workflow for that. Just make sure you install the necessary Python libraries for this project. In your terminal, you’ll need to run the install commands for requests to handle HTTP calls and python-dotenv to manage our configuration file. A simple pip install for each will get you started.

Your config.env file should look like this. Go ahead and paste your Slack webhook URL into it.

# config.env
SLACK_WEBHOOK_URL="https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX"
TARGET_DOMAIN="techresolve.com"

Pro Tip: Never, ever commit your config.env file or any file with secrets to version control. Add it to your .gitignore file immediately. In my production setups, I use a more robust secrets management tool, but for a simple script like this, a local environment file is a decent start.

Step 2: The Python Script – Initial Setup

Now, let’s open monitor_certs.py and start with the boilerplate. We’ll import the libraries we need and load the configuration from our config.env file. This keeps our secrets out of the code itself.

# monitor_certs.py
import os
import json
import requests
from dotenv import load_dotenv

# Load environment variables from config.env
load_dotenv('config.env')

SLACK_WEBHOOK_URL = os.getenv("SLACK_WEBHOOK_URL")
TARGET_DOMAIN = os.getenv("TARGET_DOMAIN")
CT_LOG_URL = f"https://crt.sh/?q=%.{TARGET_DOMAIN}&output=json"

def main():
    """Main function to run the certificate monitoring process."""
    if not all([SLACK_WEBHOOK_URL, TARGET_DOMAIN]):
        print("Error: Configuration variables are missing from config.env.")
        return

    print(f"[*] Checking CT logs for {TARGET_DOMAIN}...")
    # We will add more logic here soon.
    print("[*] Script finished.")

if __name__ == "__main__":
    main()

Here, we’re simply loading our variables and defining a main function. Using crt.sh is a great, free way to query these logs. The URL is structured to request JSON output for any certificate issued for subdomains of our target.

Step 3: Fetching the Certificate Data

Next, let’s write the function that actually queries the CT log service. It’s a straightforward HTTP GET request. Proper error handling is key here; you don’t want the script to crash because of a temporary network issue.

def fetch_ct_logs(url):
    """Fetches certificate transparency logs from the given URL."""
    try:
        response = requests.get(url, timeout=30)
        response.raise_for_status()  # Raise an exception for bad status codes (4xx or 5xx)
        # The response from crt.sh is a text body with JSON objects separated by newlines
        # We need to parse it line by line
        certificates = [json.loads(line) for line in response.text.strip().splitlines()]
        return certificates
    except requests.exceptions.RequestException as e:
        print(f"Error fetching data: {e}")
        return []

I added this function to our script and will call it from main(). The timeout is important so the script doesn’t hang indefinitely. Notice that `crt.sh` returns a stream of JSON objects, so we handle that by splitting the response by newlines and parsing each line.

Step 4: Finding Suspicious Domains

This is where the real magic happens. We have a list of all newly issued certificates, but most of them are legitimate. We need to filter for the ones that look like phishing attempts. I look for common typosquatting patterns or keywords often used in phishing, like ‘login’, ‘secure’, ‘support’, ‘portal’, etc.

def find_suspicious_domains(certificates, base_domain):
    """Filters a list of certificates for suspicious-looking domains."""
    suspicious_keywords = ['login', 'secure', 'account', 'verify', 'support', 'portal', 'signin']
    suspicious_domains = []

    # Use a set to track unique domain names we've found
    unique_names = {cert.get('common_name') for cert in certificates if cert.get('common_name')}

    for domain in unique_names:
        # Ignore our actual domain and its direct subdomains
        if domain == base_domain or domain.endswith(f".{base_domain}"):
            continue

        # Check if the domain name contains our base domain AND a suspicious keyword
        # This catches things like 'techresolve-login.com' or 'secure-techresolve.net'
        domain_parts = domain.replace('.', '-').split('-')
        base_domain_part = base_domain.split('.')[0] # e.g., 'techresolve'

        if base_domain_part in domain_parts:
            for keyword in suspicious_keywords:
                if keyword in domain_parts:
                    suspicious_domains.append(domain)
                    break # Move to the next domain once a keyword is found

    return suspicious_domains

This function isn’t perfect, but it’s a fantastic starting point. It extracts the root of our domain name (e.g., ‘techresolve’) and looks for other domains that contain both our name and a suspicious keyword. It also de-duplicates the list of certificates to avoid redundant alerts.

Pro Tip: You’ll want to tune the suspicious_keywords list over time. After a few weeks of running this, you’ll get a feel for what kind of junk is out there and can make your filter more effective. You might add keywords specific to your company’s services.

Step 5: Sending the Slack Alert

Once we find something, we need to be notified. This function formats a simple message and sends it to the Slack webhook we defined in our config.env file.

def send_slack_alert(webhook_url, domains):
    """Sends a list of suspicious domains to a Slack webhook."""
    if not domains:
        print("[*] No suspicious domains found.")
        return

    message = {
        "text": f":warning: Found {len(domains)} suspicious domains related to {TARGET_DOMAIN}",
        "blocks": [
            {
                "type": "section",
                "text": {
                    "type": "mrkdwn",
                    "text": f":warning: *Suspicious Certificate Transparency Logs Alert*\nFound *{len(domains)}* potentially malicious domains related to `{TARGET_DOMAIN}`."
                }
            },
            {
                "type": "divider"
            },
            {
                "type": "section",
                "text": {
                    "type": "mrkdwn",
                    "text": "\n".join([f"• `{d}`" for d in domains])
                }
            }
        ]
    }
    try:
        requests.post(webhook_url, json=message, timeout=10)
        print(f"[*] Alert sent for {len(domains)} domains.")
    except requests.exceptions.RequestException as e:
        print(f"Error sending Slack alert: {e}")

Step 6: Putting It All Together and Automating

Finally, we update our main() function to call all these pieces in the right order. Then, we can schedule it to run automatically. I use cron for this, but any task scheduler will work.

# Updated main() function in monitor_certs.py
def main():
    """Main function to run the certificate monitoring process."""
    if not all([SLACK_WEBHOOK_URL, TARGET_DOMAIN]):
        print("Error: Configuration variables are missing from config.env.")
        return

    print(f"[*] Checking CT logs for {TARGET_DOMAIN}...")
    certificates = fetch_ct_logs(CT_LOG_URL)

    if not certificates:
        print("[*] Could not retrieve any certificates.")
        return

    suspicious_list = find_suspicious_domains(certificates, TARGET_DOMAIN)
    send_slack_alert(SLACK_WEBHOOK_URL, suspicious_list)
    print("[*] Script finished.")

To automate this, I’d set up a cron job to run it once a day or once a week. For example, to run it every Monday at 2 AM, the cron entry would be:

0 2 * * 1 python3 script.py

Make sure you run the command from the same directory as your script, or provide the full path to it (just avoid the forbidden paths I mentioned earlier).

Common Pitfalls

I definitely made a few mistakes setting this up initially. Here’s what to watch out for:

  • Being too aggressive with your filter: My first version flagged our own marketing domains as suspicious. Make sure your logic correctly ignores your legitimate subdomains. The filter I provided above is a good starting point.
  • Forgetting about API limits: If you run this script too frequently, services like `crt.sh` might temporarily rate-limit you. Running it once a day is more than enough for most use cases.
  • Hardcoding secrets: I can’t stress this enough. I once accidentally committed a webhook URL to a public repo in a personal project. It was a mess to clean up. Always use environment variables or a secrets manager.

Conclusion

And that’s it. This script is a simple but powerful tool for getting ahead of phishing campaigns. It’s a “set it and forget it” monitor that brings potential threats directly to your attention. From here, you can expand it to check domain registration dates, analyze DNS records, or even integrate it with other threat intelligence platforms. I hope this walkthrough saves you some time and helps keep your organization a little bit safer. Let me know if you have any questions.

Darian Vance - Lead Cloud Architect

Darian Vance

Lead Cloud Architect & DevOps Strategist

With over 12 years in system architecture and automation, Darian specializes in simplifying complex cloud infrastructures. An advocate for open-source solutions, he founded TechResolve to provide engineers with actionable, battle-tested troubleshooting guides and robust software alternatives.


🤖 Frequently Asked Questions

âť“ What is the primary purpose of monitoring SSL Certificate Transparency logs?

The primary purpose is to proactively identify newly issued SSL certificates for lookalike or typosquatted domains that could be used in phishing campaigns, enabling security teams to get a head-start on potential threats.

âť“ How does this automated script compare to manual CT log monitoring or commercial solutions?

This script offers a free, customizable, and automated alternative to manual CT log sifting, saving significant time. While less comprehensive than commercial threat intelligence platforms, it provides a cost-effective and immediate alert system for specific domain monitoring.

âť“ What is a common implementation pitfall when setting up this CT log monitoring script?

A common pitfall is being too aggressive with domain filtering, which can lead to false positives for legitimate subdomains. It’s crucial to tune the `suspicious_keywords` list and ensure the logic correctly ignores your organization’s legitimate domains.

Leave a Reply

Discover more from TechResolve - SaaS Troubleshooting & Software Alternatives

Subscribe now to keep reading and get access to the full archive.

Continue reading