#!/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.
"""
Clickjacking Attack Test Agent — AUTHORIZED TESTING ONLY
Tests web applications for clickjacking (UI redressing) vulnerabilities by
checking frame-busting headers and generating proof-of-concept pages.
WARNING: Only use with explicit written authorization for the target application.
"""
import sys
from datetime import datetime, timezone
from urllib.parse import urlparse
import requests
def check_frame_headers(url: str) -> dict:
"""Check X-Frame-Options and CSP frame-ancestors headers."""
try:
resp = requests.get(url, timeout=15, allow_redirects=True, verify=True)
except requests.RequestException as e:
return {"url": url, "error": str(e)}
xfo = resp.headers.get("X-Frame-Options", "").upper()
csp = resp.headers.get("Content-Security-Policy", "")
frame_ancestors = ""
if "frame-ancestors" in csp.lower():
for directive in csp.split(";"):
if "frame-ancestors" in directive.lower():
frame_ancestors = directive.strip()
break
vulnerable = True
protections = []
if xfo in ("DENY", "SAMEORIGIN"):
vulnerable = False
protections.append(f"X-Frame-Options: {xfo}")
elif xfo:
protections.append(f"X-Frame-Options: {xfo} (non-standard)")
if frame_ancestors:
if "'none'" in frame_ancestors or "'self'" in frame_ancestors:
vulnerable = False
protections.append(f"CSP: {frame_ancestors}")
return {
"url": url,
"status_code": resp.status_code,
"x_frame_options": xfo if xfo else "MISSING",
"csp_frame_ancestors": frame_ancestors if frame_ancestors else "MISSING",
"protections": protections,
"vulnerable": vulnerable,
"severity": "HIGH" if vulnerable else "NONE",
}
def check_multiple_endpoints(base_url: str, paths: list[str]) -> list[dict]:
"""Check multiple endpoints for clickjacking protection."""
results = []
for path in paths:
url = f"{base_url.rstrip('/')}/{path.lstrip('/')}"
result = check_frame_headers(url)
results.append(result)
return results
def generate_poc_html(target_url: str, action_description: str = "Click here") -> str:
"""Generate clickjacking proof-of-concept HTML page."""
parsed = urlparse(target_url)
return f"""
Clickjacking PoC - {parsed.hostname}
Clickjacking Proof of Concept
Target: {target_url}
"""
def check_javascript_frame_busting(url: str) -> dict:
"""Check for JavaScript-based frame-busting code."""
try:
resp = requests.get(url, timeout=15)
except requests.RequestException as e:
return {"error": str(e)}
body = resp.text.lower()
frame_busting_patterns = [
"top.location", "self.location", "window.top",
"parent.frames", "top !== self", "top != self",
"window.self !== window.top",
]
found_patterns = [p for p in frame_busting_patterns if p in body]
return {
"url": url,
"has_js_frame_busting": len(found_patterns) > 0,
"patterns_found": found_patterns,
"note": "JS frame-busting can be bypassed with sandbox attribute on iframe" if found_patterns else "",
}
def generate_report(results: list[dict], js_checks: list[dict]) -> str:
"""Generate clickjacking test report."""
lines = [
"CLICKJACKING VULNERABILITY TEST REPORT — AUTHORIZED TESTING ONLY",
"=" * 65,
f"Date: {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M UTC')}",
"",
f"Endpoints Tested: {len(results)}",
f"Vulnerable: {sum(1 for r in results if r.get('vulnerable', False))}",
f"Protected: {sum(1 for r in results if not r.get('vulnerable', True))}",
"",
"RESULTS:",
"-" * 50,
]
for r in results:
status = "VULNERABLE" if r.get("vulnerable") else "PROTECTED"
lines.append(f" [{status}] {r['url']}")
lines.append(f" X-Frame-Options: {r.get('x_frame_options', 'N/A')}")
lines.append(f" CSP frame-ancestors: {r.get('csp_frame_ancestors', 'N/A')}")
if js_checks:
lines.extend(["", "JAVASCRIPT FRAME-BUSTING:"])
for jc in js_checks:
has_js = "YES" if jc.get("has_js_frame_busting") else "NO"
lines.append(f" {jc.get('url', 'N/A')}: JS frame-busting: {has_js}")
return "\n".join(lines)
if __name__ == "__main__":
print("[!] CLICKJACKING TEST — AUTHORIZED TESTING ONLY\n")
if len(sys.argv) < 2:
print(f"Usage: {sys.argv[0]} [additional_paths...]")
sys.exit(1)
target_url = sys.argv[1]
extra_paths = sys.argv[2:] if len(sys.argv) > 2 else [
"/", "/login", "/settings", "/account", "/admin",
]
print(f"[*] Testing {target_url} for clickjacking vulnerabilities...")
results = check_multiple_endpoints(target_url, extra_paths)
js_checks = []
for r in results:
if r.get("vulnerable"):
jc = check_javascript_frame_busting(r["url"])
js_checks.append(jc)
report = generate_report(results, js_checks)
print(report)
vulnerable = [r for r in results if r.get("vulnerable")]
if vulnerable:
poc = generate_poc_html(vulnerable[0]["url"])
poc_file = "clickjacking_poc.html"
with open(poc_file, "w") as f:
f.write(poc)
print(f"\n[*] PoC saved to {poc_file}")