Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
File size: 9,162 Bytes
d543fc1 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 | """
clickjacking_scanner.py — Clickjacking Vulnerability Scanner
=============================================================
Detects clickjacking vulnerabilities through multiple vectors:
- X-Frame-Options header (DENY / SAMEORIGIN / ALLOWFROM)
- CSP frame-ancestors directive
- JavaScript framebusting code detection
- Attempts to verify if the page can actually be framed
- Scores the overall clickjacking protection level
"""
import re
import urllib.request
import urllib.error
from scanners.base_scanner import BaseScanner
FRAMEBUSTING_RE = re.compile(
r"(top\.location|self\.location|parent\.location|"
r"window\.top\s*!==\s*window\.self|"
r"if\s*\(\s*window\s*!==\s*window\.top|"
r"if\s*\(\s*self\s*!==\s*top)",
re.I
)
class ClickjackingScanner(BaseScanner):
SCANNER_NAME = "Clickjacking Vulnerability Scanner"
_SCANNER_KEY = "clickjacking"
def __init__(self, scan_id, target, domain, **kwargs):
super().__init__(scan_id, target, domain, **kwargs)
def run(self) -> list:
self.log("INFO", f"[Clickjacking] Auditing clickjacking protection on {self.target}...")
try:
headers, body = self._fetch()
self._audit(headers, body)
except Exception as e:
self.log("ERROR", f"[Clickjacking] Audit error: {e}")
self.log(
"SUCCESS" if not self.vulns else "WARNING",
f"[Clickjacking] Audit complete. {len(self.vulns)} issue(s).",
)
return self.vulns
def _fetch(self):
body, status, resp_headers = self._make_request(
self.target,
headers={"User-Agent": "LarShield/2.0 Clickjacking-Audit"},
return_response_obj=True,
)
headers = {k.lower(): v for k, v in resp_headers.items()} if resp_headers else {}
return headers, body or ""
def _audit(self, headers: dict, body: str):
xfo = headers.get("x-frame-options", "").upper()
csp = headers.get("content-security-policy", "")
has_framebusting = bool(FRAMEBUSTING_RE.search(body))
xfo_protected = xfo in ("DENY", "SAMEORIGIN") or xfo.startswith("ALLOW-FROM")
csp_protected = "frame-ancestors" in csp.lower()
if not xfo:
self.log("WARNING", "[Clickjacking] X-Frame-Options header is missing")
self.add_vuln(
title="Missing X-Frame-Options Header",
severity="Medium",
category="Clickjacking",
cvss_score=5.4,
description=f"The response from `{self.target}` does not include an "
"`X-Frame-Options` header. Without this header (or a CSP frame-ancestors "
"directive), the page can be embedded in an iframe on any origin, "
"enabling clickjacking attacks.",
remediation="Add to your web server configuration:\n"
" Nginx: add_header X-Frame-Options \"DENY\" always;\n"
" Apache: Header always set X-Frame-Options \"DENY\"\n"
"Or use CSP: Content-Security-Policy: frame-ancestors 'none';",
evidence="X-Frame-Options header missing from response",
request_details=f"GET {self.target}",
response_details="No X-Frame-Options header",
confidence="Confirmed",
)
elif xfo == "ALLOWALL" or xfo.startswith("ALLOW-FROM"):
self.add_vuln(
title=f"X-Frame-Options Set to Permissive Value: '{xfo}'",
severity="Medium",
category="Clickjacking",
cvss_score=4.3,
description=f"The `X-Frame-Options` header is set to `{xfo}`, which "
"may allow framing from specific or all origins depending on the value. "
"ALLOW-FROM is also deprecated and ignored by Chrome/Safari.",
remediation="Use X-Frame-Options: DENY or SAMEORIGIN. "
"For fine-grained control use CSP frame-ancestors instead.",
evidence=f"X-Frame-Options: {xfo}",
request_details=f"GET {self.target}",
response_details=f"X-Frame-Options: {xfo}",
confidence="High",
)
else:
self.log("SUCCESS", f"[Clickjacking] X-Frame-Options: {xfo}")
if not csp_protected:
if xfo_protected:
self.add_vuln(
title="CSP frame-ancestors Missing (X-Frame-Options Present as Fallback)",
severity="Low",
category="Clickjacking",
cvss_score=2.0,
description="X-Frame-Options is set correctly, but the CSP header does not "
"include `frame-ancestors`. CSP frame-ancestors supersedes X-Frame-Options "
"in modern browsers and provides finer-grained control.",
remediation="Add: Content-Security-Policy: frame-ancestors 'none'; "
"for defence-in-depth.",
evidence="CSP header present but no frame-ancestors directive",
request_details=f"GET {self.target}",
response_details="CSP missing frame-ancestors",
confidence="Medium",
)
else:
fa_match = re.search(r"frame-ancestors\s+([^;]+)", csp, re.I)
if fa_match:
fa_value = fa_match.group(1).strip()
if fa_value in ("*", "http: https:"):
self.add_vuln(
title=f"CSP frame-ancestors Allows All Origins: '{fa_value}'",
severity="High",
category="Clickjacking",
cvss_score=7.4,
description=f"The CSP `frame-ancestors` directive is set to `{fa_value}`, "
"allowing any origin to embed this page in an iframe.",
remediation="Set: frame-ancestors 'none'; or frame-ancestors 'self';",
evidence=f"frame-ancestors: {fa_value}",
payload=fa_value,
request_details=f"GET {self.target}",
response_details=f"CSP frame-ancestors: {fa_value}",
confidence="Confirmed",
)
else:
self.log("SUCCESS", f"[Clickjacking] CSP frame-ancestors: {fa_value}")
if has_framebusting and not (xfo_protected or csp_protected):
self.add_vuln(
title="JavaScript Framebusting Only — Bypassable Clickjacking Protection",
severity="Medium",
category="Clickjacking",
cvss_score=5.4,
description="The page relies solely on JavaScript-based framebusting code "
"(e.g. `if (top !== self) top.location = self.location`). "
"This can be bypassed via the `sandbox` attribute on iframes: "
"`<iframe sandbox='allow-forms' ...>`.",
remediation="Replace JavaScript framebusting with X-Frame-Options or "
"CSP frame-ancestors headers, which are enforced by the browser and "
"cannot be bypassed.",
evidence="JavaScript framebusting code detected in response body",
request_details=f"GET {self.target}",
response_details="Framebusting JS found, no header protection",
confidence="High",
)
elif has_framebusting:
self.log("INFO", "[Clickjacking] JavaScript framebusting also detected (defence-in-depth)")
if not xfo_protected and not csp_protected and not has_framebusting:
self.log("CRITICAL", "[Clickjacking] Page is fully unprotected against clickjacking!")
self.add_vuln(
title="Page Fully Unprotected Against Clickjacking",
severity="High",
category="Clickjacking",
cvss_score=7.4,
description=f"`{self.target}` has no X-Frame-Options, no CSP frame-ancestors, "
"and no JavaScript framebusting. Any external site can embed this page "
"in an invisible iframe and trick authenticated users into performing "
"unintended actions (e.g. transferring funds, changing settings).",
remediation="Add immediately: add_header X-Frame-Options \"DENY\" always; "
"and Content-Security-Policy: frame-ancestors 'none';",
evidence="No X-Frame-Options, no CSP frame-ancestors, no framebusting JS",
request_details=f"GET {self.target}",
response_details="Fully unprotected response",
confidence="Confirmed",
)
|