Sitecore security hardening

Sitecore provides easy to follow Security Hardening Guides which contain instructions on how to setup basic security for production sites. Unfortunately it seems that even certified Sitecore partners don’t always follow these guidelines, which provides hackers with attack vectors unique to Sitecore installations. The following article is mainly aimed at Sitecore 6.4 and earlier. Code samples are based on C# 4.0.

Reason

Disregarding the recommended security settings can have unpleasant consequences, mostly due to the fact that Sitecore contains web accessible administrative tools. Unless you’re running Sitecore 6.5 these tools default to being accessible by everyone.

For example, “/sitecore/admin/showconfig.aspx” is a tool for checking the contents of the Web.config file. It displays the “finalized” Web.config, containing all changes merged from configuration files in “/App_Config/Include”. It’s great for debugging, but shouldn’t be left accessible for unauthorized visitors. Sites advertising the use of outdated Sitecore versions, possibly containing well documented security flaws, are open invitations for attackers.

Even seemingly protected interfaces like the Visual Sitecore Service (which requires a username/password combination) provide ample opportunity for misuse. Many companies use email addresses or email address prefixes as usernames, and it’s fair to assume that at least some authors use “simplistic passwords” for their Sitecore logins, thus making “VerifyCredentials” an excellent option for brute force attacks.

“Please don’t touch”:

Preventing access to the URLs outlined in Sitecores Security Hardening Guides can be done using file system security or changing the Web.config file. I find the latter preferable for the following reasons:

  • GUI based file access is not always provided (e.g. when deploying via FTP)
  • security settings don’t have to be set repetitively for each server in a staged environment
  • possible to copy basic security settings across several solutions and modify as needed

Code

The configuration shown below prevents unauthenticated users from accessing administrative forms, debug tools and the Sitecore web service. It’s very simplistic and grants access to all authenticated users, even if they’re just simple customers, so a fully fledged production setup must be modified accordingly.

<configuration>
  [...]
  <location path="sitecore/admin">
    <system.web>
      <authorization>
        <deny users="?" />
      </authorization>
    </system.web>
  </location>
  <location path="sitecore/debug">
    <system.web>
      <authorization>
        <deny users="?" />
      </authorization>
    </system.web>
  </location>
  <location path="sitecore/shell/WebService">
    <system.web>
      <authorization>
        <deny users="?" />
      </authorization>
    </system.web>
  </location>
</configuration>

Implementing a tool which checks whether or not a site follows the basic rules regarding URL  accessibility requires very little coding effort:

public static class SimpleSecurityAnalyzer
{
  private static Uri _site;

  public static void Main()
  {
    Console.Title = string.Format("Simple Security Analyzer [{0:yyyy-MM-dd HH:mm}]", DateTime.UtcNow);
    Console.Write("Enter URL of the site to check: ");
    _site = null;
    while (_site == null)
      _site = GetSiteUrl();

    Console.WriteLine("Working...");
    Console.WriteLine();

    foreach (UrlGroup urlGroup in UrlGroupRepository.GetGroups())
      Console.WriteLine(CreateReport(urlGroup));

    Console.WriteLine("Done.");
    Console.ReadLine();
  }

  private static Uri GetSiteUrl()
  {
    string url = Console.ReadLine();
    if (string.IsNullOrEmpty(url))
      return null;

    if (!HasProtocol(url))
      url = "http://" + url;

    return Uri.IsWellFormedUriString(url, UriKind.Absolute) ? new Uri(url) : null;
  }

  private static bool HasProtocol(string url)
  {
    return url.StartsWith("http://", StringComparison.OrdinalIgnoreCase) || url.StartsWith("https://", StringComparison.OrdinalIgnoreCase);
  }

  private static string CreateReport(UrlGroup urlGroup)
  {
    StringBuilder report = new StringBuilder();
    IEnumerable<string> accessibleUrls = GetAccessibleUrls(urlGroup.Urls);
    report.AppendFormat("[ {0} ]", urlGroup.Warning);
    report.AppendLine();
    foreach (string accessibleUrl in accessibleUrls)
      report.AppendLine(accessibleUrl);
    if (accessibleUrls.Any())
      report.AppendLine();
    else
      report.AppendLine("None.");
    return report.ToString();
  }

  private static IEnumerable<string> GetAccessibleUrls(IEnumerable relativeUrls)
  {
    return (from relativeUrl in relativeUrls
              let absoluteUrl = _site + relativeUrl.TrimStart('/')
              let result = CheckAccess(absoluteUrl)
            where result == HttpStatusCode.OK
            select absoluteUrl).ToArray();
  }

  private static HttpStatusCode CheckAccess(string url)
  {
    HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
    // Don't follow redirects to e.g. login pages. We're only interested in "directly accessible" URLs.
    request.AllowAutoRedirect = false;
    try
    {
      using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
        return response.StatusCode;
    }
    catch (WebException ex)
    {
      return ((HttpWebResponse)ex.Response).StatusCode;
    }
  }
}

The URLs which Sitecore recommends protecting have been hard coded into a simple repository. In the example below they’ve been divided into groups based on their “type” and associated with a short explanatory warning.

public class UrlGroup
{
  public string Warning { get; set; }
  public IEnumerable Urls { get; set; }
}

public static class UrlGroupRepository
{
  public static IEnumerable<UrlGroup> GetGroups()
  {
    return new[]
    {
    new UrlGroup { Warning = "Accessible debug URLs", Urls = new[] { "/sitecore/debug/default.aspx" } },
    new UrlGroup { Warning = "Accessible web service URLs", Urls = new[] { "/sitecore/shell/webservice/service.asmx" } },
    new UrlGroup { Warning = "XML files can be downloaded (sample)", Urls = new[] { "/sitecore/shell/applications/shell.xml" } },
    new UrlGroup { Warning = "XSLT files can be downloaded (sample)", Urls = new[] { "/xsl/system/webedit/hidden rendering.xslt" } },
    new UrlGroup { Warning = "Accessible administrative URLs", Urls = new[] { "/sitecore/admin/cache.aspx",
      "/sitecore/admin/dbbrowser.aspx",
      "/sitecore/admin/login.aspx",
      "/sitecore/admin/restore.aspx",
      "/sitecore/admin/serialization.aspx",
      "/sitecore/admin/showconfig.aspx",
      "/sitecore/admin/stats.aspx",
      "/sitecore/admin/unlock_admin.aspx",
      "/sitecore/admin/updateinstallationwizard.aspx",
      "/sitecore/admin/wizard/installupdatepackage.aspx" } }
    };
  }
}

Example

Below are some examples of sites powered by Sitecore, as advertised on http://www.sitecore.net/Customers.aspx.

Saralee.com:

Sitecore.net:

Heineken.com (UK Sitecore site of the year 2011):

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s