#!/usr/bin/env python3 """ CVE-2026-56164 Vulnerability Scanner Microsoft SharePoint Server — Missing Authentication for Critical Function Detects SharePoint Server instances and determines if they are vulnerable to CVE-2026-56164 (unauthenticated privilege escalation via auth bypass in /_vti_bin/client.svc). """ import argparse import json import re import socket import ssl import sys import urllib.request import urllib.error from datetime import datetime, timezone from typing import Optional from payload_gen import ( AFFECTED_VERSIONS, BYPASS_HEADERS, build_detection_payload, build_http_request, build_version_check_payload, is_version_vulnerable, identify_sharepoint_edition, parse_sharepoint_version, ) # SharePoint fingerprint patterns SHAREPOINT_FINGERPRINTS = [ r"MicrosoftSharePointTeamServices", r"SharePoint", r"_vti_bin", r"client\.svc", r"SPWebPartManager", r"__REQUESTDIGEST", r"_layouts/15", r"SharePoint\.WebControls", r"Microsoft\.Office\.Server", r"suitebar", r"ms-core-menu", r"sp-js-ensure", ] SHAREPOINT_HEADERS = [ "MicrosoftSharePointTeamServices", "SPRequestGuid", "X-SharePointHealthScore", "X-SP-RequestGuid", "SPRequestDuration", ] class ScanResult: """Container for scan results.""" def __init__(self, target: str): self.target = target self.timestamp = datetime.now(timezone.utc).isoformat() self.reachable = False self.sharepoint_detected = False self.version = None self.edition = None self.vulnerable = False self.auth_bypass_confirmed = False self.ssl = False self.ssl_cert = None self.fingerprints = [] self.matched_headers = [] self.endpoints = [] self.error = None self.response_time_ms = 0 self.details = {} def to_dict(self) -> dict: return { "target": self.target, "timestamp": self.timestamp, "reachable": self.reachable, "sharepoint_detected": self.sharepoint_detected, "version": self.version, "edition": self.edition, "vulnerable": self.vulnerable, "auth_bypass_confirmed": self.auth_bypass_confirmed, "ssl": self.ssl, "fingerprints": self.fingerprints, "matched_headers": self.matched_headers, "endpoints": self.endpoints, "error": self.error, "response_time_ms": self.response_time_ms, "details": self.details, } def _make_request(url: str, headers: dict = None, data: bytes = None, timeout: int = 10, verify_ssl: bool = False) -> tuple: """Make an HTTP request and return (status, response_headers, body, error).""" try: req = urllib.request.Request(url, data=data, headers=headers or {}) ctx = ssl.create_default_context() if not verify_ssl: ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE import time start = time.time() with urllib.request.urlopen(req, timeout=timeout, context=ctx) as resp: elapsed = (time.time() - start) * 1000 body = resp.read().decode("utf-8", errors="replace") return resp.status, dict(resp.headers), body, None, elapsed except urllib.error.HTTPError as e: elapsed = 0 try: body = e.read().decode("utf-8", errors="replace") except Exception: body = "" return e.code, dict(e.headers) if hasattr(e, 'headers') else {}, body, None, elapsed except Exception as e: return 0, {}, "", str(e), 0 def _get_ssl_cert(host: str, port: int) -> dict: """Get SSL certificate information.""" try: ctx = ssl.create_default_context() ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE with socket.create_connection((host, port), timeout=10) as sock: with ctx.wrap_socket(sock, server_hostname=host) as ssock: cert = ssock.getpeercert() if cert: subject = dict(x[0] for x in cert.get("subject", [])) issuer = dict(x[0] for x in cert.get("issuer", [])) return { "subject": subject, "issuer": issuer, "notAfter": cert.get("notAfter"), "notBefore": cert.get("notBefore"), } except Exception: pass return None def detect_sharepoint(target: str, port: int = None) -> ScanResult: """ Detect if a target is running SharePoint Server. Probes common SharePoint endpoints and headers. """ result = ScanResult(target) # Determine protocol and ports protocols = ["https", "http"] ports_to_try = [port] if port else [443, 80, 8443, 8080] for proto in protocols: for p in ports_to_try: if port is None and proto == "https" and p not in (443, 8443): continue if port is None and proto == "http" and p not in (80, 8080): continue url = f"{proto}://{target}:{p}" status, headers, body, err, elapsed = _make_request(url, timeout=10) if err: continue result.reachable = True result.response_time_ms = elapsed result.ssl = (proto == "https") # Check SharePoint-specific headers for header_name in SHAREPOINT_HEADERS: for h_key, h_val in headers.items(): if h_key.lower() == header_name.lower(): result.matched_headers.append(f"{header_name}: {h_val}") result.sharepoint_detected = True # Extract version from MicrosoftSharePointTeamServices header if header_name == "MicrosoftSharePointTeamServices": result.version = h_val.strip() result.edition = identify_sharepoint_edition(h_val.strip()) # Check body for SharePoint fingerprints for pattern in SHAREPOINT_FINGERPRINTS: matches = re.findall(pattern, body, re.IGNORECASE) if matches: result.fingerprints.extend(set(matches)) result.sharepoint_detected = True # Check for version in body if not result.version: version_match = re.search( r'MicrosoftSharePointTeamServices["\s:]+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)', body, re.IGNORECASE ) if version_match: result.version = version_match.group(1) result.edition = identify_sharepoint_edition(result.version) # Check for _vti_bin endpoint if "_vti_bin" in body or "_layouts" in body: result.endpoints.append("/_vti_bin/") result.sharepoint_detected = True if result.sharepoint_detected: # Get SSL cert if HTTPS if proto == "https": result.ssl_cert = _get_ssl_cert(target, p) # Check vulnerability if result.version: result.vulnerable = is_version_vulnerable(result.version) result.details["patched_versions"] = { k: v["patched"] for k, v in AFFECTED_VERSIONS.items() } return result return result def test_auth_bypass(target: str, port: int = 443, site_url: str = None) -> dict: """ Test if the target is vulnerable to the authentication bypass. Sends a CSOM request without X-RequestDigest but with bypass routing headers. """ if site_url is None: site_url = f"https://{target}" proto = "https" if port in (443, 8443) else "http" base_url = f"{proto}://{target}:{port}" # Build detection payload payload = build_detection_payload(site_url) req_config = build_http_request(base_url, payload) status, headers, body, err, elapsed = _make_request( req_config["url"], headers=req_config["headers"], data=payload.encode("utf-8"), timeout=15, ) result = { "tested": True, "status_code": status, "response_time_ms": elapsed, "error": err, "auth_bypass": False, "evidence": {}, } if err: return result # Check for evidence of successful auth bypass: # 1. Server processes the request without 401/403 # 2. Response contains SharePoint data (not an auth error) # 3. Response doesn't ask for request digest if status == 200: # Check if response contains actual data (not auth error) if "Error" not in body or "Unauthorized" not in body: csom_markers = [ "", "", "", "ProcessQuery", "ClientQuery", # JSON CSOM response markers "SchemaVersion", "LibraryVersion", "ErrorInfo", "IsSiteAdmin", "ServerVersion", ] if any(marker in body for marker in csom_markers): result["auth_bypass"] = True result["evidence"]["response_contains_data"] = True result["evidence"]["no_auth_required"] = True if status == 401: result["evidence"]["auth_required"] = True result["evidence"]["bypass_failed"] = True if status == 403: result["evidence"]["forbidden"] = True result["evidence"]["bypass_failed"] = True # Check for request digest requirement if "X-RequestDigest" in body or "RequestDigest" in body: result["evidence"]["digest_required"] = True return result def scan_target(target: str, port: int = None, test_bypass: bool = True) -> ScanResult: """ Full scan of a single target. """ result = detect_sharepoint(target, port) if result.sharepoint_detected and result.vulnerable and test_bypass: bypass_result = test_auth_bypass( target, port=port or (443 if result.ssl else 80), site_url=f"{'https' if result.ssl else 'http'}://{target}" ) result.auth_bypass_confirmed = bypass_result.get("auth_bypass", False) result.details["bypass_test"] = bypass_result return result def scan_targets(targets: list, port: int = None, test_bypass: bool = True) -> list: """Scan multiple targets.""" results = [] for target in targets: target = target.strip() if not target or target.startswith("#"): continue print(f" Scanning {target}...") result = scan_target(target, port, test_bypass) results.append(result) return results def print_report(result: ScanResult): """Print a human-readable scan report.""" print("=" * 68) print(" CVE-2026-56164 VULNERABILITY SCANNER") print(" Microsoft SharePoint Server — Missing Authentication") print("=" * 68) print(f" Target: {result.target}") print(f" Time: {result.timestamp}") print(f" Reachable: {'Yes' if result.reachable else 'No'}") if not result.reachable: print(f" Status: UNREACHABLE") if result.error: print(f" Error: {result.error}") print("=" * 68) return print(f" SharePoint: {'DETECTED' if result.sharepoint_detected else 'NOT DETECTED'}") if result.sharepoint_detected: print(f" Version: {result.version or 'Unknown'}") print(f" Edition: {result.edition or 'Unknown'}") print(f" SSL: {'Yes' if result.ssl else 'No'}") print(f" Response: {result.response_time_ms:.0f}ms") if result.matched_headers: print(f"\n ─ SharePoint Headers ─") for h in result.matched_headers: print(f" {h}") if result.fingerprints: print(f"\n ─ Fingerprints ─") for fp in sorted(set(result.fingerprints)): print(f" {fp}") if result.endpoints: print(f"\n ─ Endpoints ─") for ep in result.endpoints: print(f" {ep}") if result.ssl_cert: print(f"\n ─ SSL Certificate ─") cn = result.ssl_cert.get("subject", {}).get("commonName", "N/A") org = result.ssl_cert.get("subject", {}).get("organizationName", "N/A") print(f" CN: {cn}") print(f" Org: {org}") print(f"\n ─ Vulnerability Status ─") if result.vulnerable: print(f" Status: VULNERABLE") if result.auth_bypass_confirmed: print(f" Bypass: CONFIRMED (auth bypass tested successfully)") elif "bypass_test" in result.details: print(f" Bypass: TESTED (see details)") else: print(f" Bypass: NOT TESTED") else: print(f" Status: NOT VULNERABLE (patched or unknown version)") print("=" * 68) def main(): parser = argparse.ArgumentParser( description="CVE-2026-56164 Scanner — Microsoft SharePoint Server Auth Bypass" ) parser.add_argument("--target", "-t", help="Single target host or IP") parser.add_argument("--targets", "-T", help="File containing targets (one per line)") parser.add_argument("--port", "-p", type=int, help="Specific port to scan") parser.add_argument("--json", "-j", help="Save results as JSON to file") parser.add_argument("--no-bypass-test", action="store_true", help="Skip authentication bypass testing") args = parser.parse_args() if not args.target and not args.targets: parser.error("Specify --target or --targets") print("\n" + "=" * 68) print(" CVE-2026-56164 VULNERABILITY SCANNER") print(" Microsoft SharePoint Server — Missing Authentication") print("=" * 68 + "\n") targets = [] if args.target: targets = [args.target] elif args.targets: with open(args.targets) as f: targets = [line.strip() for line in f if line.strip() and not line.startswith("#")] test_bypass = not args.no_bypass_test results = scan_targets(targets, args.port, test_bypass) print() for result in results: print_report(result) print() # Summary vulnerable = [r for r in results if r.vulnerable] detected = [r for r in results if r.sharepoint_detected] print(f" Summary: {len(detected)}/{len(results)} SharePoint detected, " f"{len(vulnerable)}/{len(results)} vulnerable\n") if args.json: output = [r.to_dict() for r in results] with open(args.json, "w") as f: json.dump(output, f, indent=2) print(f" Results saved to {args.json}\n") if __name__ == "__main__": main()