Before using the ColdFusion Admin API, certain security implications need consideration. Administrators must understand the potential for allowing unfettered access to the ColdFusion Administrator. ColdFusion secures the ColdFusion Administrator with a single password, which administrators should not provide to users. Administrators must also enable access to the Admin API code directory: /CFIDE/AdminAPI. This directory is installed by default, and the API modules are hard-coded to look for this path. ColdFusion ServiceFactorySoon after ColdFusion MX was released, developers learned how to access the ColdFusion ServiceFactory object, by using CreateObject() and <cfobject> calls to coldfusion.server.ServiceFactory. This Java object gives developers complete access to all ColdFusion server objects, including the Data Source, Licensing, Runtime, and Security Services. It also allowed developers to bypass the ColdFusion Administrator to configure data sources, debugging, and so on. Hackers could use it to disable the admin and RDS passwords and gain complete control over the server. In response, Macromedia expanded the sandbox security restrictions in CFMX7 for CreateObject(), allowing administrators to disable access to objects by type. Administrators can prevent CreateObject() calls to COM, Java, or Web Services objects while still allowing ColdFusion component instantiation. When coupled properly with sandbox security, the ColdFusion Admin API can be used to completely disable developer access to the ServiceFactory while still permitting custom ColdFusion administration. NOTE Under the hood, the Admin API components are invoking the ServiceFactory. So in effect, Macromedia recommends using the Admin API and disabling all direct calls to the ServiceFactory. TIP For more information on ColdFusion MX 7 sandbox restrictions, see Chapter 9, "Creating Server Sandboxes." Admin API Security RisksThe intent of the ColdFusion Admin API is to solve the challenge of extending ColdFusion Administrator functionality to developers/users without compromising security or exposing direct access to the ServiceFactory. There are valid reasons for providing this type of accessparticularly for ISPs wanting to allow customers to create as many DSNs (data source names) as they need without the administrative overhead. However, providing this programmatic access potentially exposes serious security risks, such as the following:
Securing the Admin APIFortunately, we can counter the aforementioned security risks with good security practices. Prevent unauthorized access and thwart rogue developers by securing the Administrator password, securing access to the Admin API directory, and limiting exposure to the Admin API methods. Developing custom modules with a good façade pattern that provides minimal exposure to Admin API methods will help prevent unintentional damage and inadvertent information disclosure. More on this in the "Building Custom Admin Consoles" section. CF Administrator PasswordThe ColdFusion Administrator and Admin API both have built-in security, but the key to that security is keeping the ColdFusion Administrator password secret. Access to the API methods requires authentication with the Administrator password. The password should be a strong string, say a minimum eight characters of mixed-case, alphanumeric, and special characters. Customers/users should not have access to this password. Keep the password written down in a secured location. CAUTION ColdFusion Administrator Security must be enabled in order to secure the Admin API with the Administrator password. If this is disabled, both the ColdFusion Administrator and Admin API are left wide open. The login() method of the administrator.cfc provides access control. You must authenticate with login() before using any methods of the other API components. The login() method allows the use of the RDS (remote development services) password for authentication by default. It is common for administrators to set the Administrator and RDS passwords to the same value. Best practice is to set the RDS password to a strong string different from the Administrator password, and to keep it in a secured location. TIP The ColdFusion MX 7 Dreamweaver Extensions use the RDS password in the Site definition to access the Admin API. NOTE RDS provides secure remote access to files and data sources, and enables ColdFusion debugging through ColdFusion Studio, HomeSite+, and Dreamweaver. Macromedia recommends disabling RDS on production servers to ensure security. See Macromedia TechNote 17276 at http://www.macromedia.com/go/tn_17276 for details on disabling RDS. Securing the Admin API DirectoryOstensibly, the Admin API code directory is accessible to all application code by default. To prevent unauthorized attempts to access the Admin API methods from other application code, secure the /CFIDE/AdminAPI directory. The "Securing the CFIDE Directory" section of Chapter 10, "Security in Shared and Hosted Environments," recommends completely removing the CFIDE directory structure from the main Web root. Placing this directory on a separate, more secure Web root helps ensure CFIDE's obscurity. Users will not easily be able to find the directory and, if they do, will be challenged for authentication in order to access it. You will want to restrict user access to Administrator functionality through authorized access to the Admin API code. CAUTION All application should run within sandboxes, especially in shared and hosted environments. Sandboxing creates directory structures to which the code has access. Any files or directories outside of a sandbox are protected from the code within. Therefore, ensure the sandboxes created for application code do not have access to the /CFIDE/AdminAPI directory. Only allow read and execute access to /CFIDE/AdminAPI for the custom admin console. Securing the Façade ComponentRecall that access to the Admin API is secured by the ColdFusion Administrator and/or RDS password. In addition, the API requires authentication via the administrator.login() method before calling all other API methods. This means either providing a challenge the password, hard coding it, or passing it dynamically. Since users should never have access to the Administrator password (and RDS should not be enabled on production systems), the only recourse is to hard-code the password somewhere in the custom admin module. The Application.cfc code in Listing 11.4 controls access to the example facade.cfc in Listing 11.1. CAUTION If you hard code the password or pass it as a variable, the value will show in the java class files compiled by ColdFusion in cf_root/WEB-INF/cfclasses when the Save Class Files option is enabled in the ColdFusion Administrator. If hackers or users gain access to the class file, they can decode it and extract the Administrator password. To prevent this scenario, disable the Save Class Files option, secure the cf_root/WEB-INF/cfclasses folder and contents with sandbox security and OS permissions. You can also place the password in a secure, external file and read it in at runtime. For example, create an admin.ini file and use the GetProfileString function to retrieve the administrator password: REQUEST.myAdminObj.login(GetProfileString("C:\secured\admin.ini", "Admin Pass", "password")) This will safely pass the Administrator password to the REQUEST.myAdminObj.login method at runtime, without exposing it as clear text in the java class file. Listing 11.4. Application.cfcSecuring Application Events[View full width] <cfcomponent> <!---#### File name: Application.cfc Description: Template that handles application events Assumptions: None Author name and e-mail: Sarge (ssargent@macromedia.com) Date Created: February 22, 2005 ####---> <!---#### Application initialization variables. ####---> <cfscript> THIS.name = "CustomAdmin"; THIS.loginStorage = "Cookie"; THIS.scriptProtect = "CGI,Form,URL"; THIS.sessionManagement = "yes"; THIS.sessionTimeOut = CreateTimeSpan(0,0,5,0); VARIABLES.adminPasswd = "admin"; // CF Admin Password </cfscript> <cffunction name="onRequestStart" returntype="void"> <cfargument name="thisRequest" required="true"> <cfif IsDefined("Form.Logout") OR IsDefined("URL.Logout")> <cflock scope="session" timeout="30" throwontimeout="yes"> <!---#### Log the user out of the Admin API. ####---> <cfif IsDefined('SESSION.myAdminObj')> <cfset SESSION.myAdminObj.logout> <!---#### Clear the facade object from the SESSION scope. ####---> <cfset StructDelete(SESSION, myAdminObj)> </cfif> </cflock> <!---#### Log the user out of ColdFusion. ####---> <cflogout> </cfif> <!---#### CFLOGIN structure for authentication code. ####---> <cflogin> <cfset REQUEST.loggedin = false> <cfif IsDefined("cflogin")> <!---#### Authenticate and login user to ColdFusion with appropriate roles. ####---> <cfif not CompareNoCase('admin', Trim(CFLOGIN.name)) and not CompareNoCase(Trim (CFLOGIN.password), "password")> <cfloginuser name="#CFLOGIN.name#" password="#CFLOGIN.password#" roles="admin ,publisher"> <cfset REQUEST.loggedin = "true"> <cfelse> <cfset REQUEST.badlogin = "true"> <cfset REQUEST.loggedin = "false"> <!---#### If the login fails, return to the login form. ####---> <cfinclude template="loginform.cfm"><cfabort> </cfif> <cfelse> <cfinclude template="loginform.cfm"><cfabort> </cfif> </cflogin> <cfif Len(Trim(GetAuthUser()))> <!---#### If user is authenticated then create a SESSION-level object call to the facade.cfc. ####---> <cflock scope="session" timeout="30" throwontimeout="yes"> <!---#### Create the facade object only once per session. ####---> <cfif NOT IsDefined("SESSION.myAdminObj")> <cfset VARIABLES.adminPasswdFile = ExpandPath(.) & "\admin.ini"> <!---#### Log the user into the Admin API. ####---> <cfset SESSION.myAdminObj = createObject("component", "ows.chapter11.facade") .login(GetProfileString(VARIABLES.adminPasswdFile, "Admin_Pass", "password"))> </cfif> <!---#### Create a REQUEST scope pointer for use in other templates on subsequent requests. ####---> <cfset REQUEST.myAdminObj = SESSION.myAdminObj> </cflock> <cfoutput> <!---#### Add Logout Button atop every page. ####---> <cfform name="Exit" action="index.cfm"> <cfinput type="submit" name="Logout" value="Logout"> </cfform> </cfoutput> </cfif> </cffunction> <cffunction name="onSessionEnd" returntype="void"> <cfargument name="SessionScope" required="true"> <cfargument name="ApplicationScope" required="false"> <cflock scope="session" timeout="30" throwontimeout="yes"> <cfif IsDefined("SESSION.myAdminObj")> <!---#### Ensure the user is logged out of the Admin API. ####---> <cfset SESSION.myAdminObj.logout> <!---#### Ensure the facade object is cleared from the SESSION scope. ####---> <cfset StructDelete(SESSION, myAdminObj)> </cfif> </cflock> <!---#### Ensure the user is logged out of ColdFusion. ####---> <cflogout> </cffunction> </cfcomponent> Listing 11.4 shows the CF Admin password hard-coded in the Application.cfc (see the VARIABLES.adminPasswd variable). This is necessary for the façade to work. The code uses VARIABLES.adminPasswd to authenticate to the facade.cfc. Revisit the facade.cfc code in Listing 11.1. We can secure the method calls using the <cffunction> roles attribute. Specifying roles="admin" will require users to authenticate with the role of admin in order to run the facade methods. (This sample code provided for this chapter is already secured with roles="admin".) TIP The facade.cfc methods in Listing 11.1 are also available as Web services by virtue of access="remote" in the <cffunction> calls. When roles="admin" is specified remote clients are also required to authenticate to ColdFusion as a user with the admin role in order to use the Web service. The Application.cfc ensures only authorized ColdFusion users can access the facade.cfc. It forces users to a login form where they must enter authentication criteria. Upon successful authentication, <cfloginuser> logs the user into the ColdFusion security paradigm with the role of admin. If GetAuthUser() returns a value, an instance of the facade.cfc is created in the SESSION scope and authenticated to the Admin API:
This SESSION scope object ensures authenticated access for every request during the user's session. Next, provide a handle to the SESSION object in the REQUEST scope to eliminate any locking concerns: <cfset REQUEST.myAdminObj = SESSION.myAdminObj> Finally, the Application.cfc includes logout code to exit the Admin API, logout of ColdFusion, and destroying of the SESSION scope façade object: <cflock scope="session" timeout="30" throwontimeout="yes"> <cfif IsDefined("SESSION.myAdminObj")> <!---#### Ensure the user is logged out of the Admin API. ####---> <cfset SESSION.myAdminObj.logout> <!---#### Ensure the facade object is cleared from the SESSION scope. ####---> <cfset StructDelete(SESSION, myAdminObj)> </cfif> </cflock> <!---#### Ensure the user is logged out of ColdFusion. ####---> <cflogout> NOTE Ensure that you log out from the Admin API (administrator.logout()) and from ColdFusion (<cflogout>). Place logout code in the OnSessionEnd method of Application.cfc to ensure logout is called when the user's session ends; that is, when the browser closes or session timeout is reached. See Chapter 8 for more information on <cflogout> and sessions. |