ColdFusion Security - Detecting Modified Pages
No matter how secure your server is there exists the potential for someone to upload their own pages or modify existing pages on the server via an exploit of some sort. How can you detect this or stop this from happening?
A little ColdFusion code can help detect modified or unknown pages and stop these pages from running.
As well as hashing passwords (see previous article on securing passwords) you can also take hashes of pages to check if they have been modified.
First we need to generate the hash of the pages in our site we want to make secure. This can be done by using cffile to read the ColdFusion files and generate a hash for each page via the hash function. The page path and the page's hash are then stored in a database table. (If the site has a small number of pages they instead could be stored in a structure in the application scope).
We then add an onRequest method of Application.cfc or code to the Application.cfm page to check the hash of the current page with the hash of the page stored in the database. If the hashes are the same then the page runs as normal. Otherwise the page has been modified or a new page and the page isn't run but an error page is displayed instead. The error page is a normal ColdFusion page so could log or email the error.
Here's what the code in Application.cfm would look like:
2<cfset pagecontents = "">
3<cftry>
4 <cffile action="read" variable="pagecontents" file="#GetBaseTemplatePath()#">
5
6 <cfcatch>
7 <!--- if error reading file continue --->
8 </cfcatch>
9</cftry>
10
11<!--- get page from database --->
12<cfquery name="dbpage" datasource="#dsn#" >
13 select page, hash from pages
14 where page = <cfqueryparam value="#CGI.SCRIPT_NAME#" cfsqltype="cf_sql_varchar">
15</cfquery>
16
17<!--- check if page exists and page hash is correct --->
18<cfif dbpage.recordcount is 0 or hash(pagecontents) neq dbpage.hash>
19 <cfinclude template="error.cfm">
20 <cfabort>
21</cfif>
Or the onRequest method of Application.cfc:
2 <cfargument name="page" type="String" required="true">
3
4 <!--- read the cfm file --->
5 <cfset var pagecontents = "">
6 <cftry>
7 <cffile action="read" variable="pagecontents" file="#page#">
8
9 <cfcatch>
10 <!--- if error reading file continue --->
11 </cfcatch>
12 </cftry>
13
14 <cfset var dbpage = "">
15
16 <!--- get page from database --->
17 <cfquery name="dbpage" datasource="#variables.dsn#" >
18 select page, hash from pages
19 where page = <cfqueryparam value="#page#" cfsqltype="cf_sql_varchar">
20 </cfquery>
21
22 <!--- check if page exists and page hash is correct --->
23 <cfif dbpage.recordcount is 1 and hash(pagecontents) is dbpage.hash>
24 <cfinclude template="#arguments.page#">
25 <cfelse>
26 <cfinclude template="error.cfm">
27 </cfif>
28 </cffunction>
Either way I'd recommended (if possible) that the Application.cfm or Application.cfc is kept outside of the web root.
Of course the down side of doing this is that ever time you make a change to a file you need to change the page's hash in the database so it can run. There is also a small performance hit of reading the file and generating the hash on every request.
The above code only checks requested pages but it wouldn't take too much work to have instances of CFCs or custom tags checking themselves as well.
You can have a SELECT only datasource on the site, then nobody will be able to update your files anyway.
If they're capable of putting content on your site then surely they can replace the application.cfm as well, which makes this method null and void.
Yes worse case Application.cfc or Application.cfm could be replaced that's why I suggested it should be placed outside the web root to reduce the chance of this happening.
Could someone still alter files on your server with this system in place? Yes they could but a) some protection is better than none and b) it would be more effort for them to do so.
Another reason I find this method obsolete is that this method will add mode overhead to your database. You probably won't see it on the a site that gets 5K hits a day, but on a site like mine that get 4.5 million hits a day, I think this method will melt my database server.
In the end, I think the best method to go to protect your website from hackers is the old fashion ways. Make sure you have all ports disabled accept port 80. Use a firewall and NAT the incoming requests to the webserver so you don't have to the server on a DMZ. Have another internal firewall from the webserver to the DB server and only open the ports necessary to access the DB server. Limit the CF services by using a user account and granting permission where needed. Disable FTP access completely or if you need it, use SFTP instead and limit by IP address and port.
The database load is tiny and likely to be less than other database calls on the page for your typical dynamic ColdFusion page. The page query could be cached if performance was an issue. With a site with large amount of traffic you would want to load test this before implementing it in production.