New Sitecore 8.2 & Sitecore 9 Security Patch

I guess I’m on a security hardening binge for 2019, since I’ll share a hot-off-the-presses security hardening measure from Sitecore today. I don’t want to say too much about the vulnerability, but this article explains in general terms and applying it to all Sitecore server roles for version 8.2 through any current 9 releases is emphasized as the best practice. I’d do it at the next opportunity.

I created a gist with the PowerShell necessary to apply this patch, just update line #8 with the path to your Sitecore website:


Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
Import-Module WebAdministration
$url = "https://kb.sitecore.net/~/media/7A638A36A71D4494981A8655E297AD23.ashx?la=en"
$tempLocation = "C:\tempLocation"
$zippedPatch = "$tempLocation\SitecoreSupportPackage.302938.zip"
$unzippedPatch = "$tempLocation\SitecoreSupportPackage"
$sitecoreRoot = "C:\InetPub\Your\Sitecore\Website"
if (!(Test-Path $tempLocation))
{
New-Item -ItemType Directory -Path $tempLocation
}
Invoke-WebRequest -Uri $url -OutFile $zippedPatch
Expand-Archive -Path $zippedPatch -DestinationPath $unzippedPatch -Force
Copy-Item "$unzippedPatch/website/*" -Destination $sitecoreRoot -Recurse -Force
Write-Host "Patch applied to $sitecoreRoot"
Remove-Item $unzippedPatch -Recurse
Remove-Item $zippedPatch -Recurse

Our team has internal automation taking the above a bit further and using another layer of abstraction, and that’s secret Rackspace sauce I won’t share publicly,  but the snippet above should have your environment patched in just a few seconds.

One of the key elements to the patch involves an update to the /sitecore/admin/logs.aspx page which, if you dig into it, reveals a grip-load of additional C# validation logic and other stuff going on . . .

secpatch

There’s a lot to unpack in there if you’re curious, but suffice it to say that Sitecore’s keeping all your bases covered and isn’t trusting user input (using a broad interpretation of that principle).

Sitecore Commerce security hardening note

Let’s start the New Year off with a fun Sitecore Commerce note. Using the latest Sitecore Commerce available today, that is running Sitecore 9.0 update-2 with Sitecore Commerce update-3 (you have to cross-reference https://dev.sitecore.net/Downloads/Sitecore_Commerce.aspx and https://kb.sitecore.net/articles/804595 to really sort this out), we’re applying routine security hardening.

Now that Sitecore is truly built on a hybrid of “plain” .Net and .Net Core, this security hardening effort is more nuanced.

Sitecore is still updating their documentation for the Sitecore 9 space and one can end up at dead-ends like https://doc.sitecore.com/developers/90/platform-administration-and-architecture/en/security-guide-251908.html that leads you over to the .Net Framework documentation when there are better notes with 100% relevancy to Sitecore elsewhere on the Sitecore site. I persevered and eventually found Sitecore’s updated information like this on the hash algorithm https://doc.sitecore.com/developers/90/platform-administration-and-architecture/en/change-the-hash-algorithm-for-password-encryption.html. Still, this documentation overlooks the .Net Core details and given that this Sitecore Commerce project we’re working on will use the latest and greatest, we had to do our own research.

Fortunately, we have some history with this having published https://developer.rackspace.com/blog/Updated-Security-Hardening-For-Sitecore-8.2 or earlier versions going back several years. The PowerShell we’ve used for ages to automate this work, however, wasn’t going to cut it with this new Commerce and .Net Core dimension:

snippet

Instead, we need to do something like this to update the JSON configuration for the Sitecore Identity Server. While you could get fancy and parse the JSON, I used a more direct replace approach to knock this out quickly:

$siteNamePrompt = Read-Host "enter Identity Server website name"
$site = get-website -name $siteNamePrompt
$appSettingsPath = "{0}\wwwroot\appsettings.json" -f $site.physicalPath
Get-Content $appSettingsPath).replace("""PasswordHashAlgorithm"":""SHA1""},", """PasswordHashAlgorithm"":""SHA512""},") | Set-Content $appSettingsPath

The end result is  that SitecoreIdentityServer\wwwroot\appsettings.json file needs an updated PasswordHashAlgorithm value:

        “IDServerCertificateStoreLocation”: “LocalMachine”,
“IDServerCertificateStoreName”: “My”,
        “PasswordHashAlgorithm”: “SHA512”
}

Given the distributed nature of Sitecore 9 with Commerce, I think a discrete change like this just for the IdentityServer doesn’t warrant a lot of effort to integrate into the bigger security hardening Powershell script we use. It may be worthwhile to just update SIF at this point instead of applying security hardening after the Sitecore installation is complete. We’re also talking about SIF extension modules to run this type of logic after SIF is complete. For now, I’ll probably just keep this note handy for the foreseeable future and see whether Sitecore integrates the security hardening guidance directly into SIF in a future release (hint hint!) — or, over time we may collect a set of these best practice adjustments that deserves more effort to automate into a scripted deployment. For now, I think I’ve taken it as far as it deserves.

Encrypting Sitecore connection strings for Sitecore Commerce, Azure SQL, and beyond

There’s been a lot of Sitecore Commerce on my plate this Summer, and sadly one limitation of using that product for some customers is the requirement for SQL Server authentication instead of Active Directory and Windows Auth; I won’t get into why they need SQL auth at this point, but trust that in many use-cases this is a necessity.

In an effort to always deliver a secured platform for customers, at Rackspace we encrypt the App_Config/connectionStrings.config file to avoid having plaintext usernames and passwords on disk.    This is a link to our Rackspace GitHub “gist” performing such encryption with the ASP.Net tool aspnet_regiis.exe.  The logic is also there to un-encrypt, in case that’s necessary.

Encryption success
You can update the $configLocation variable at the top of the script to point to where your Sitecore installation is located; you then run this script using PowerShell, and you’ll get an output like this.

Once you’ve run the script, your connectionStrings.config file will resemble this:

Before you get too excited, for Sitecore Commerce in the current incarnation, there are several other plaintext passwords on disk in the \CommerceAuthoring\wwwroot\data\Environments and related .json files for both SQL and Sitecore.  The PowerShell I’ve shared doesn’t address those areas.  The Sitecore Commerce documentation does a good job of cataloging where to find these references, at least, but this still leaves a lot to be desired in terms of security.

I’m not going to go too far down this path, since I mostly wanted to post the PowerShell we use to automate SQL Server connection string encryption.  This technique can be useful for a variety of projects, not just for Sitecore Commerce — although this is the use case we’re repeatedly seeing right now.  If I have time, I’ll share some other Sitecore Commerce tips around Azure SQL friendly deployments (Sitecore’s documentation is a decent start, but lacking in some respects).

Here’s the script to encrypt/decrypt those Sitecore connectionStrings.config file:


<#
Note:
– The encyption is specific to each server, so this needs to be run separately on every IIS server
– ASPNet_RegIIS requires a web.config file to operate, so we have to massage our Sitecore .config into a web.config format it will understand
Steps:
1) Copy current Connectionstrings.config to a file named "web.config"
2) insert <configuration> node surrounding the <connectionStrings> XML
3) run this new web.config file through aspNet_RegIIS…
C:\Windows\Microsoft.NET\Framework\v4.0.30319\aspnet_regiis.exe -pef "connectionStrings" "S:\Sitecore\TEST-CMS\website\App_Config"
4) take the contents of the — now encrypted — web.config file and pull the information within the
<connectionStrings>…</connectionStrings> nodes and overwrite what's currently in connectionStrings.config
#>
$configLocation = "S:\Sitecore\website\App_Config"
#this is here only in case you want a back-up, but don't blindly leave a back-up around or it defeats the purpose of encrypting
#Copy-Item -Path ($configLocation + "\connectionStrings.config") -Destination ($configLocation + "\connectionStrings.PlainText.backup")
Copy-Item -Path ($configLocation + "\connectionStrings.config") -Destination ($configLocation + "\web.config")
$plainConnectionStrings = Get-Content ($configLocation + "\web.config")
$plainConnectionStrings.replace('</connectionStrings>', '</connectionStrings></configuration>') | Set-Content ($configLocation + "\web.config")
$plainConnectionStrings = Get-Content ($configLocation + "\web.config")
$plainConnectionStrings.replace('<connectionStrings>', '<configuration><connectionStrings>') | Set-Content ($configLocation + "\web.config")
#Encrypt
C:\Windows\Microsoft.NET\Framework\v4.0.30319\aspnet_regiis.exe -pef "connectionStrings" $configLocation
$encryptedString = Get-Content ($configLocation + "\web.config")
$encryptedString.replace('</connectionStrings></configuration>', '</connectionStrings>') | Set-Content ($configLocation + "\web.config")
$encryptedString = Get-Content ($configLocation + "\web.config")
$encryptedString.replace('<configuration><connectionStrings', '<connectionStrings') | Set-Content ($configLocation + "\web.config")
#this is now our XML to inject into the Sitecore connectionStrings.config
$encryptedString = Get-Content ($configLocation + "\web.config")
Clear-Content -Path ($configLocation + "\connectionStrings.config")
Set-Content -Path ($configLocation + "\connectionStrings.config") -Value $encryptedString
Remove-Item ($configLocation + "\web.config")
Write-Host "$configLocation\webconnectionStrings.config is now encrypted" -ForegroundColor Magenta
########################################################################
# to un-encrypt, run the following from the machine that performed the encryption. ConnectionStrings will be revealed in plain text in a new web.config file
<#
$configLocation = "S:\Sitecore\website\App_Config"
Copy-Item -Path ($configLocation + "\connectionStrings.config") -Destination ($configLocation + "\web.config")
$plainConnectionStrings = Get-Content ($configLocation + "\web.config")
$plainConnectionStrings.replace('</connectionStrings>', '</connectionStrings></configuration>') | Set-Content ($configLocation + "\web.config")
$plainConnectionStrings = Get-Content ($configLocation + "\web.config")
$plainConnectionStrings.replace('<connectionStrings', '<configuration><connectionStrings') | Set-Content ($configLocation + "\web.config")
C:\Windows\Microsoft.NET\Framework\v4.0.30319\aspnet_regiis.exe -pdf "connectionStrings" $configLocation
Write-Host "Check $configLocation\web.config for the plain text configuration" -ForegroundColor Magenta
#>

The Sitecore Pie: strategic slicing for better implementations

The Sitecore Pie

pie
At some point I want to catalog the full set of features a Sitecore Content Delivery and Content Management server can manage in a project.  My goal would be to identify all the elements that can be split apart into independent services.  This post is not a comprehensive list of those features, but serves to introduce the concept.

Think of Sitecore as a big blueberry pie that can be sliced into constituent parts.  Some Sitecore sites can really benefit from slicing the pie into small pieces and letting dedicated servers or services manage that specific aspect of the pie.  Too often, companies don’t strategize around how much different work their Sitecore solution is doing.

An example will help communicate my point: consider IIS and how it serves as the execution context for Sitecore.   Many implementations will run logic for the following through the same IIS server that is handling the Sitecore request for rendering a web page.  These are all slices of the Sitecore pie for a Sitecore Content Delivery server:

  1. URL redirection through Sitecore
  2. Securing HTTP traffic with SSL
  3. Image resizing for requests using low-bandwidth or alternative devices
  4. Serving static assets like CSS, JS, graphics, etc
  5. Search indexing and query processing (if one is using Lucene)

If you wanted to cast a broader net, you could include HTTP Session state for when InProc mode is chosen, Geo-IP look-ups for certain CD servers, and others to this list of pie slices.  Remember, I didn’t claim this was an exhaustive list.  The point is: IIS is enlisted in all this other work besides processing content into HTML output for Sitecore website visitors.

Given our specific pie slices above, one could employ the following alternatives to relieve IIS of the processing:

  1. URL Redirection at the load balancer level can be more performant than having Sitecore process redirects
  2. Apply SSL between the load balancer and the public internet, but not between the IIS nodes behind your load balancer — caled “SSL Offloading” or “SSL Termination”
  3. There are services like Akamai that fold in dynamic image processing as part of their suite of products
  4. Serving static assets from a CDN is common practice for Sitecore
  5. Coveo for Sitecore is an alternative search provider that can take a lot of customer-facing search aspects and shift it to dedicated search servers or even Coveo’s Cloud.  One can go even further with Solr for Sitecore or still other search tiers if you’re really adventurous

My point is, just like how we hear a lot this election season about “let Candidate X be Candidate X” — we can let Sitecore be Sitecore and allow it to focus on rendering content created and edited by content authors and presenting it as HTML responses.  That’s what Sitecore is extremely valuable for.

Enter the Cache

I’ve engaged with a lot of Sitecore implementations who were examining their Sitecore pie and determining what slices belong where . . . and frequently we’d make the observation that the caching layer of Sitecore was tightly coupled with the rest of the Sitecore system and caching wasn’t a good candidate for slicing off.  There wasn’t a slick provider model for Sitecore caches, and while certain caches could be partially moved to other servers, it wasn’t clean, complete, or convenient.

That all changed officially with the initial release of Sitecore 8.2 last month.  Now there is a Sitecore.Caching.DefaultCacheManager class, a Sitecore.Caching.ICache interface, and other key extension points as part of the standard Sitecore API.  One can genuinely add the Sitecore cache to the list of pie slices one can consider for off-loading.

In my next post, I will explore using this new API to use Redis as the cache provider for Sitecore instead of the standard in-memory IIS cache.

Unified Security Hardening Script for Sitecore

I wanted to share here some good work we shared on the Rackspace developer blog last week about Sitecore security hardening.

If one is pursuing security hardening for Sitecore, the whole piece is worth reading, but it’s particularly targeting those looking for repeatable, scripted processes to apply the website configs and IIS changes that secure Sitecore to a general standard.

We get a ton of use out of this unified PowerShell script that applyies the standards from the Sitecore Security Hardening Guide: unified PowerShell script for Sitecore security hardening.

We’ve also posted to GitHub the necessary .config patches for the security hardening:

The “unified” PowerShell script follows a pattern of downloading the .config and copies it to proper directory in the Sitecore installation:

# this .config is what we're applying: https://gist.github.com/grant-killian/b64aa6cabd18e9b0097257ee4a2dc614
$downLoadURI = "https://gist.githubusercontent.com/grant-killian/b64aa6cabd18e9b0097257ee4a2dc614/raw"
$downLoadPath = "C:\localStaging\Rackspace.SecurityHardening.Step5.IncreaseLoginSecurity.config"
Invoke-WebRequest -Uri $downLoadURI -OutFile $downLoadPath
Copy-Item -Path $downLoadPath -Destination $rackspaceInclude #we use a "Z.Rackspace" directory under /app_config/include

This PowerShell isn’t the end of the process of securing a Sitecore implementation, but more just a start.  Sitecore publishes these recommendations as part of their best practices, and what we’ve done at Rackspace it taken those recommendations and scripted them for ease of deployment across a spectrum of environments.  We’ve made slight adjustments here and there to suit our perspective and experience, as there is rarely a one size fits all solution to this kind of work.

Securing the Sitecore Login Surface Area

We’re standardizing the security hardening routines across several Sitecore customers and it’s curious that Sitecore’s documentation on improving login security doesn’t cover the Sitecore Login.RememberLastLoggedInUserName setting.

For reference, in the sitecore.config file this setting is provided as follows:

<!–  REMEMBER LAST LOGGED IN USER NAME
Specifies whether Sitecore will remember the last logged in user name on the login page (stored encrypted in a cookie).
If you set this to false, the user name field on the login page will always be blank.
Default: true
–>
<setting name=”Login.RememberLastLoggedInUserName” value=”true”/>

Kevin Obee has a succinct Sitecore config patch for this, we combine this with some other settings to arrive at the following unified SecurityHardening.ImproveLoginSecurity.config patch at Rackspace:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <settings>
      <setting name="Login.DisableAutoComplete">
        <patch:attribute name="value">true</patch:attribute>
      </setting>
       <setting name="Login.DisableRememberMe">
        <patch:attribute name="value">true</patch:attribute>
      </setting>   
      <!-- not officially part of Sitecore's security hardening measures,
 but still a good security practice -->
       <setting name="Login.RememberLastLoggedInUserName">
        <patch:attribute name="value">false</patch:attribute>
      </setting>   
    </settings>
  </sitecore>

I should also point out, the first part of Sitecore’s documentation on hardening the login surface for Sitecore is about enforcing SSL.  At Rackspace, we don’t do this at the Sitecore application layer (that’s actually the last step in the chain of request processing for a site visit, so that’s the slowest spot to do it and it’s fairly brittle).  Instead, we enforce this earlier in the chain at the F5 layer (or which ever load balancer a customer is running with).  There are other steps to take like in conjunction with this, such as IP whitelisting for access etc.  Again, the load balancer is the right place to do this sort of work.

More on this soon . . . our team is compiling a unified set of guidance around applying a the basic set of Sitecore’s security measures — and then some :).

How Sitecore Phones Home (maybe)

A question came up today around the office: does Sitecore monitor their installation base via a phone-home mechanism of some kind?  It’s relevant in a number of ways.   One way being as we work on cloud installations and elastic models for Sitecore, what data might Sitecore have that we could leverage for tracking licensing compliance, utilization, etc.

There may be other ways Sitecore tracks their server activities, but I used Reflector on the Sitecore.sitecore.login namespace to find one very likely place where Sitecore implements a call-back to the mothership.  Here goes . . .

Using a clean Sitecore 8.1 rev 151207 installation, in the source for the sitecore/login/default.aspx page is the following mark-up.  Note the “display:none;” on line 1 that hides this div from view:

   1:      <div id="licenseOptions" style="display: none;">
   2:  <%--            <h2 class="form-signin-heading">License and browser information</h2>--%>
   3:              <div class="license-info-wrap">
   4:                <ul>
   5:                  <li>System information</li>
   6:                  <li>License holder <%# License.Licensee %></li>
   7:                  <li>License ID <%# License.LicenseID %></li>
   8:                  <li>Sitecore version <%# About.VersionInformation() %></li>
   9:                </ul>
  10:  
  11:                <iframe id="StartPage" runat="server" allowtransparency="true" frameborder="0" scrolling="auto"
  12:                      marginheight="0" marginwidth="0" style="display: none; height: 105px;"></iframe>
  13:  
  14:              </div>
  15:              <div class="login-link-wrap">
  16:                <a href="javascript:;" id="licenseOptionsBack" class="login-link">&lt; Back</a>
  17:              </div>
  18:  
  19:            </div>

If we remove the “display:none” and load the page, some interesting details about the environment are revealed.  On my local machine it appears like this:

displayed

That’s not particularly interesting, but if you return to the mark-up and examine the IFrame defined on line #11 named StartPage with the runat=server attribute, you might turn your attention to what’s going on server-side when this default.aspx page loads.

Reflector, or any decompiler, will show that Sitecore.sitecore.login.Default contains an OnInit method with various checks for authentication, databinding, and so on.  I’ll omit most of that method except for the one call that’s of interest here, as we look for a way our local Sitecore system could communicate back to Sitecore the company.

protected override void OnInit(EventArgs e)
{

this.RenderSdnInfoPage();

}

Diving into the RenderSdnInfoPage method, we have this:

   1:  private void RenderSdnInfoPage()
   2:  {
   3:      this.StartPage.Attributes["src"] = new UrlString(Settings.Login.SitecoreUrl) {
   4:          ["id"] = Sitecore.SecurityModel.License.License.LicenseID,
   5:          ["host"] = WebUtil.GetHostName(),
   6:          ["licensee"] = Sitecore.SecurityModel.License.License.Licensee,
   7:          ["iisname"] = WebUtil.GetIISName(),
   8:          ["st"] = WebUtil.GetCookieValue("sitecore_starttab", string.Empty),
   9:          ["sc_lang"] = Context.Language.Name,
  10:          ["v"] = About.GetVersionNumber(true)
  11:      }.ToString();
  12:      this.StartPage.Attributes["onload"] = "javascript:this.style.display='block'";
  13:  }

And now we’re in business!  Sitecore loads up various pieces of data from the local running Sitecore instance and appends it to the end of our URL that we inject into the src attribute of the IFrame.  That src attribute in a default Sitecore installation starts with http://sdn.sitecore.net/startpage.aspx (defined as the Login.SitecoreUrl configuration setting) and the rest of the URL is information communicated to Sitecore about this local instance.

For example, using my Firefox HTML Inspector, my generated IFrame source was as follows (know that I have altered identifying information about my license):

<i frame src=”http://sdn.sitecore.net/startpage.aspx?id=20150821010030&host=sc81rev151207&licensee=Not%20Really%20Sharing&iisname=b86449a111164a2cd9c37d771c094dce&st&sc_lang=en&v=8.1.151207” id=”StartPage” allowtransparency=”true” scrolling=”auto” marginheight=”0″ marginwidth=”0″ style=”display: block; height: 105px;” onload=”javascript:this.style.display=’block'” frameborder=”0″></i frame>

I think it’s a pretty safe bet that startpage.aspx on the sdn.Sitecore.net site has some logic running to catalog the querystring parameters and passively monitor what’s going on in the wild.

Programmatically Setting the Sitecore Admin Password (and how to secure it)

For an enterprise building out a lot of Sitecore environments, where they’re setting up elastic-scaling or dynamic provisioning of Sitecore CM or CD servers, the default settings of the Sitecore install can leave something to be desired.

One such “something to be desired” is the handling of the password for the sitecore\admin account.  It’s puzzling why a parameter isn’t included in the Sitecore installer to set the sitecore\admin account password.  It feels trivial, but the process of logging in and manually changing the password through the Sitecore Client after a new installation can be a big pain in an automated setting.  Consider doing that for 10 CM environments . . . or beyond . . .

While there are ways to do it with a library like Sitecore Powershell Extensions (Set-UserPassword), we were looking for a method that didn’t require that Sitecore Powershell dependency.  I’m a big fan of minimalism when it comes to including modules and 3rd party dependencies, maybe to a fault, but having a method of setting a secure and unique Sitecore admin password that used the plain out-of-the-box Sitecore API was most desirable.

So we crafted this utility .ASPX page to drop into our sitecore/admin directory on the web server — and we’ll make a call to that page near the end of our environment provisioning setup.  If we want to be really fancy, we could delete this page after we’ve run it once, to tidy up after ourselves.  It’s designed, however, to only alter the original “b” password and won’t perform changes beyond making that initial change.

Without further ado, here’s the full .aspx code.  I’ll dive into a bit more detail afterwards.

1:  <%@ Language="C#" %>  
2:  <HTML>  
3:         
4:        public void Page_Load(object sender, EventArgs e)  
5:        {  
6:          object newPwd = Request.QueryString.Get("p");  
7:          if( object.ReferenceEquals( newPwd, null ) )  
8:          {  
9:            //no parameter provided  
10:          }  
11:          else  
12:          {  
13:            System.Web.Security.MembershipUser user = GetUser();      
14:            Sitecore.Diagnostics.Assert.IsNotNull((object) user, typeof (Sitecore.Security.Accounts.User));  
15:            user.ChangePassword("b", newPwd.ToString());  
16:            lblSummary.Text="New password set to " + newPwd.ToString() + " for UserName " + user.UserName;  
17:          }  
18:        }  
19:        System.Web.Security.MembershipUser GetUser()  
20:        {  
21:           return System.Web.Security.Membership.GetUser(@"sitecore\admin");  
22:        }  
23:      
24:    <body>  
25:     <form id="MyForm" runat="server">  
26:       <asp:Label runat="server" ID="lblSummary">  
27:         Pass the password you'd like to define for the sitecore\admin user to this page in the querystring as the "p" parameter.  
28:         <br /><br />  
29:         For example http://[domain-name]/sitecore/admin/SetPassword.aspx?p=NewPassword1  
30:         <br /><br />  
31:       </asp:Label>  
32:     </form>  
33:    </body>  
34:  </HTML>  

Some explanation:

  1. First off, why a lame old .ASPX page when there’s cool MVC approaches or at least code-behind techniques proven effective for over a decade of .Net development?  This page is a self-contained unit that doesn’t need compiled .dlls or anything else, so it’s an easily deployed option. It’s not fancy, but it doesn’t cause a recompilation if one adds it to an existing site and is the most simple tool for the job.
  2. I full qualify all the references instead of including assemblies at the top (maybe with the exception of the Request reference on line #6 — I guess I could have used HttpContext.Current.Request instead, to be fully consistent 🙂 ).  This is a habit I developed a long time ago, where other developers are frequently using the code as a reference or building on it as a proof-of-concept.  By using System.Web.Security.MembershipUser on line #13 I make it totally explicit where the MembershipUser lives in the API (same with Sitecore.Security.Accounts.User etc).  This page will be easier for other developers to work with as a result.
  3. On line #15, the call to user.ChangePassword, is something I pulled straight from the SitecoreMembershipProvider class defined in the Sitecore.Security namespace.  To find this method, I used a .Net reflection tool to inspect the Sitecore assemblies and poked around the MembershipProvider code in the Sitecore.Kernel.  I can’t say enough good things about using a decompiler to work with Sitecore — it’s an absolute must for serious Sitecore work.  Here’s a screen-shot of the decompiled output, for example:

Capture.JPG

This .aspx code intentionally works only on the sitecore\admin account, and it’s hard-coded to only change the default “b” password.  There are methods to extend this and make it truly dynamic, and make it more secure, but for our limited purpose of facilitating a one-time change to the Sitecore admin password for a freshly minted Sitecore CM server, this approach is clean and minimal.  This page could be called as a post install step, loading http://%5Bdomain-name%5D/sitecore/admin/SetPassword.aspx?p=NewPassword1 for example, and display the new password in plain text this one time for the new administrator.  I think, if one were to use this page in “the wild” it makes sense to escape that querystring value and not write it blindly to the page (avoiding potential cross-site scripting vulnerabilities), but there’s a lot more to say about security with this entire .ASPX page . . .

Securing this sort of thing

As Kam Figy pointed out, sending the password in plain text as a querystring value will — by default — have the querystring value added to the server IIS logs in plain text.  This is certainly not ideal, but the idea here is to establish a random strong password for a fresh Sitecore install.  Our assumption is that the site administrators would change the password from our randomly generated one, and maybe we can help them by forcing them to change it on first login (adding a <loggingin> processor to force a change, maybe?).   We could also look at passing the password some other way, in a custom host-header or via database.  Maybe we generate the new password in our ASPX page and communicate it back to our provisioning system some other way?  There are many avenues here, but since our real goal is to prevent the standard “b” password from allowing anyone to access a site that is freshly provisioned . . . I’m not too worried about it.  If it’s days or weeks until somebody actually logs in to the CM server, leaving the standard “b” password alone is a much more significant security liability.

Just one final note about securing utility pages like this, since I think it follows from the above.  We intentionally didn’t use the approach below because we want to allow an anonymous HTTP request to change the admin password.  That’s most likely a terrible idea for most any other use-case, so here’s what you could do to lock access to this page down:

Add the following to the top of the page:

1:  <%@ Language="C#" Inherits="Sitecore.sitecore.admin.AdminPage" %>  
2:  <HTML>  
3:    <script runat="server" language="C#">  
4:        protected override void OnInit(EventArgs args)  
5:        {  
6:          CheckSecurity(true);  
7:          base.OnInit(args);  
8:        }  

Line #1 includes the instruction to inherit the page from Sitecore.sitecore.admin.AdminPage class (that’s another good one to check out in a decompilation tool — it’s in the Sitecore.Client assembly!).  Here’s what the CheckSecurity method shows in that decompiled AdminPage:

Capture

On line #6 of my code for “Securing this sort of thing” I call to CheckSecurity(true) and we can see that the boolean true controls if we allow those in the Sitecore Developer role to also have access to this resource, or not.  The logic of this check is to redirect the visitor to the admin login screen if they’re not authenticated, effectively locking this page down from unauthorized access.  This is something I would include in nearly every administrative script I introduce for Sitecore; whether one allows developers or not, via the isDeveloperAllowed boolean parameter, is something to determine based on the purpose of the page.

Understanding Sitecore Session Timeouts

This post describes the configuration of general session timeouts in Sitecore.  Implementation teams are often interested in how to approach session management, cautious of situations where authors are logged-off after working with a piece of content for an extended amount of time (such as authoring in a RichTextField for 30+ minutes ). As I explain below, focusing on just http session timeouts will only partly address the problem. Internally, Sitecore has both regular http session timeouts and authentication timeouts that factor into this.  Some teams are tempted to trigger ajax calls to the server to extend the session lifetime . . . but as you’ll see, this is already handled by Sitecore and further customization of this process could weaken the security of the system.

For additional background: Sitecore made significant improvements to the session management logic with Sitecore 6.6 Update-7; proper support for sliding expiration was introduced, and full respect for standard ASP.Net authentication configuration was added to the platform. This may add to the confusion on this subject, as work-arounds and various custom patches may have been necessary for an older Sitecore implementation to achieve what is now out-of-the-box session support.

The Universal Editing Concern: Lost Work Scenario

Consider the following steps taken by a Sitecore author in the Content Management environment:

  1. Content Author creates new item or opens an existing one
  2. Author starts editing text in a RichText field
  3. The editing takes a long time, so the author remains working on that field for longer than the session timeout value (or they might take a 20 minute coffee brake in the middle, etc)
  4. Eventually the Content Author clicks Save to move on with their work

In a typical non-Sitecore ASP.Net application, after session expiration, all the work will be lost as the user will need to log back in to the system.  This is exactly the lost work scenario we’ll aim to avoid.

On Content Delivery Servers

For the Sitecore CD servers, one controls session timeout in the regular ASP.Net way, via the sessionState configuration node in web.config:

<sessionState timeout="180" ...

The timeout value is in minutes, so 180 equals 3 hours. Sitecore recommends a short session timeout for CD servers to conserve server resources. A default of 20 minutes is usually adequate, but each implementation is different. This has no relevance to the lost work scenario as CD servers aren’t where authors work with editing content, but I include this note in the interest of showing the complete configuration for Sitecore and session timeout management.

Content Authoring Servers

HTTP Session Timeouts
The sessionState configuration in web.config governs session duration in Sitecore CM servers just as it does for CD servers. HTTP session timeouts are defined as follows:

<sessionState timeout="180" ...

Authentication Timeouts
Along with HTTP Session timeouts, when dealing with the Sitecore Client on CM servers, it’s important to know how Authentication Timeouts come into play as these are less obvious to most implementations. To understand Authentication Timeouts, know that there are two types of Sitecore authentication sessions:
1) Not-persistent auth session
2) Persistent auth session
A “Persistent” auth session is created when the user logs in and has the “Remember Me” box checked on the login form; the “Not-Persistent” auth session is when this is not checked. See the login screen below for reference.
loginremember
The two types of auth sessions use different locations to configure timeouts. Both locations are, by default, in the web.config file of the Sitecore webroot.

Not-Persistent Authentication Session

The lifetime of the “not-persistent” auth session is determined by settings in the authentication node as follows:

<authentication mode="None">
  <forms name=".ASPXAUTH" cookieless="UseCookies" timeout="180" slidingExpiration="true" />
</authentication>

The value of the “timeout” attribute controls the lifetime of the auth session in minutes. If it’s not specified in the configuration, the default timeout value is 30 minutes. For clarity, it’s a good practice to explicitly set a value for this property.
The value of the “slidingExpiration” attribute controls whether sliding expiration is enabled.

From the MSDN documentation on slidingExpiration:
“Sliding expiration resets the expiration time for a valid authentication cookie if a request is made and more than half of the timeout interval has elapsed. If the cookie expires, the user must re-authenticate. Setting the SlidingExpiration property to false can improve the security of an application by limiting the time for which an authentication cookie is valid, based on the configured timeout value.”

Note that the slidingExpiration value defaults to true, but for clarity it’s a good practice to explicitly include it in the forms definition xml node.

Persistent Authentication Session

Lifetime of “persistent” Sitecore auth sessions is controlled by the “Authentication.ClientPersistentLoginDuration” setting; the value is in days.

<setting name="Authentication.ClientPersistentLoginDuration" value="180"/>

Note: prior to Sitecore 6.2, this setting was known as Authentication.TicketTimeout

KeepAlive.aspx

Sitecore prevents the lost work scenario, caused by regular http session expiration, via a page called “KeepAlive.aspx.” When the Content Editor is open, a short while before a session is going to expire there is Sitecore javascript code that makes a request to [webroot]/sitecore/service/keepalive.aspx to extend the session. The browser connects to the server with a simple http request to prevent session expiration.

The KeepAlive.aspx page doesn’t, however, extend the authentication expiration; it only extends the http session. This is because it can be a security vulnerability to perpetuate the lifetime of an authentication ticket automatically for an idle browsesr. The KeepAlive.aspx page can be customized to extend the authentication expiration, but it’s important to understand the repercussions. It’s possible for an editor to never have their authentication ticket expire if modifications like the following are made:

Out-of-the-box, KeepAlive.aspx uses this logic in the Page_Load event to extend the lifetime of the http session:

protected override void OnLoad(EventArgs e)
{
  if (Tracker.IsActive)
  {
    Tracker.CurrentPage.Cancel();
  }
}

To extend the authentication expiration, one could use this logic instead in KeepAlive.aspx:

protected override void OnLoad(EventArgs e)
{
  if (Tracker.IsActive)
  {
    Tracker.CurrentPage.Cancel();
  }

  //Start of customization. Not sure you really wan't to do this . . .
  HttpCookie cookie = HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];
  if (cookie != null)
  { 
    Sitecore.Web.Authentication.TicketManager.Relogin    (Sitecore.Web.Authentication.TicketManager.GetCurrentTicketId());
  }
  //end customization
}

Again, this is not generally recommended as authors could potentially never have their authentication tickets expire when their computer is idle, but happens to still have a browser open to the Sitecore Client.

Active Directory and Authentication Session

If an implementation is using Active Directory integration (the standard Ldaplogin.aspx with the Sitecore AD Module), it’s programmatically the same as not checking the “remember me” checkbox and so it uses not-persistent auth sessions. If you’re using standard Sitecore AD integration, the authentication and forms xml nodes are most relevant to configuring authentication timeouts.

Conclusion

Session timeout and authentication session timeout are two different concepts, but often get combined together when talking about ASP.Net web application “sessions” with Sitecore.

Sitecore recommends using a session timeout (defined in the sessionState configuration) that’s as short as can be while still meeting the needs of the implementation. The default of 20 minutes is a reasonable starting point and Sitecore’s javascript calls to KeepAlive.aspx will keep that session from expiring if an author is working in the Sitecore Client but not making http requests to the web server directly due to user actions.

For the Sitecore Content Management servers, authentication session expiration is more likely to cause an author to be redirected to the login page and cause the lost work scenario. Sitecore introduces no KeepAlive functionality for authentication sessions as it could be a security vulnerability.

      There are two locations to set an authentication timeout value:

    1. The authentication node in web.config has a Forms timeout attribute (for not-persistent auth sessions, also used with standard Sitecore Active Directory module integration)
    2. The Authentication.ClientSessionTimeout setting in web.config (for persistent auth sessions)

Confusion can be caused by testing with a regular Sitecore account (not Active Directory) and one checks the “remember me” checkbox on the login page . . . then the user is testing with a persistent auth session and not exercising the configuration relevant to Sitecore’s AD module.

I should also add that there’s a session timeout bug in recent Sitecore versions; authors can be mistaken for robot traffic and so their session is terminated prematurely. It’s discussed on the Sitecore KB item https://kb.sitecore.net/articles/135940<?a> but, I’ve encountered two projects where the KB resolution wasn’t sufficient in solving the problem. In those situations, one needs to remove the suggested KB article configuration patch and modify the system.webServer section of the Sitecore web.config (if you’re using IIS Classic mode you’d use the httpModules section instead). The blue colored configuration change resolves the issue:

...<add type="Sitecore.Web.HttpModule,Sitecore.Kernel" name="SitecoreHttpModuleExtensions" />
      <add type="Sitecore.Support.FixHttpSessionTimeout.CausedByRobotDetection,Sitecore.Support.414299" name="RevertingAnalyticsBotSessionDuration" />

Armed with this knowledge, Sitecore implementations can make the best use of the session timeout settings.