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.