Posts tagged ‘ColdFusion’

So far in our attempts to get access to the templates and data of other hosted customers we have primarily focused on accessing the templates of other users in order to get access to their data through them. A more direct approach is to try to access the database directly.

Registered datasources

To access a registered datasource all you need is its name. More often then not, names are easy to guess. Depending on how the datasource is registered in the CF Administrator you may need a username and password as well. If the username and password to the datasource are registered in the ColdFusion Administrator ColdFusion will write them to neo-datasource.xml. They are encrypted there, but with a reversible algorithm (ColdFusion needs to be able to decrypt them in order to authenticate). The way to decrypt these passwords is well known. So if you have read access to neo-datasource.xml through an incorrect Sandbox configuration you have all usernames and passwords registered there.
If the username and password of the datasource are not registered in the ColdFusion Administrator chances are they are stored in the application scope somewhere and we have already seen that the application scope is insecure.

The first line of defense against this is to configure Sandbox Security to only allow access to specific datasources to each Sandbox. This will effectively lock down access from ColdFusion to the datasource, even if you know the full connection string.

Database to database connections

But as good as the protection for datasources is in ColdFusion, it only protects against connections from ColdFusion’s cfquery and cfstoredproc. Just as the trick to work around a disabled cfexecute is to create Java / COM / .NET objects, the trick to work around a disabled ColdFusion datasource connection is to start the connection from somewhere else. And the way to do that is to connect from one database to another. The protocol to do so, SQL/MED, is not widely implemented in databases, but most databases offer proprietary ways to do so. For instance, in MS SQL Server you would use:

SELECT *
FROM OpenDataSource(
  ’SQLOLEDB‘
  ,Data Source=server.asdf.com;User ID=yourusername;Password=yourpassword‘
  ).somedatabase.dbo.sometable;

In PostgreSQL that would be:

SELECT *
FROM dblink(
  'dbname=postgres'
  , 'SELECT ID, value FROM tables'
  ) AS t1(
  ID INT
  , value TEXT
  );

The one thing that makes this more difficult is that you usually need some sort of superuser privilege on the database server to be allowed to do this.

MS Access

MS Access is a file based database that is pretty popular in shared hosting. While people frequently question performance, scalability and security, it is still an attractive offering because it allows users to download the database, edit it offline and then upload an entirely new database with new data.

MS Access offers a very simple feature to access tables in other MS Access databases without the superuser privileges you would need on a more full-featured database server. You just specify the path to the data file you want to access directly in the query:

SELECT *
FROM table IN 'h:\sites\database.mdb'

I was reading a little discussion on whether the maxrows attribute of cfquery was pushed down all the way into the database like a TOP or LIMIT statement would, or whether the full query statement was executed and only a limited number of rows was retrieved. And while there was a lot of debate, there was no proof, not even a reference to some standard that said how it should work (which is something entirely different from how it does work).

Since I am personally convinced that the full query statement is executed and only a limited number of rows is retrieved I set out to prove so. In order to prove so, we need to have a query statement that returns rows to ColdFusion, but has a side-effect as well that is registered in the database. The the results of that side effect should be visible in ColdFusion. My first idea was to do a select with a RAISE NOTICE call per record, and then see if the number of print notices was equal to the maxrows in the cfquery. Only the output of a notice isn’t visible in ColdFusion.

The new idea was to write a query statement that not just inserts a record, but returns a record at the same time. That is easily done with the RETURNING clause in PostgreSQL.

INSERT INTO x (y)
VALUES (z)
RETURNING *

With some searching I figured out that MS SQL Server offers similar functionality

INSERT INTO x (y)
OUTPUT INSERTED.*
VALUES (z)

So here is the script to set up the database tables and values:

create table src (ID INTEGER);
create table tgt (ID INTEGER);
 
insert into src (ID) values(1);
insert into src (ID) values(2);
insert into src (ID) values(3);
insert into src (ID) values(4);
insert into src (ID) values(5);
insert into src (ID) values(6);
insert into src (ID) values(7);
insert into src (ID) values(8);
insert into src (ID) values(9);
insert into src (ID) values(10);

And the test script to run from ColdFusion:

<!--- baseline --->
<cfquery datasource="pgtest">
DELETE FROM tgt;
</cfquery>
<cfquery datasource="pgtest" result="debug">
INSERT INTO tgt (ID) SELECT ID FROM src RETURNING *;
</cfquery>
<cfquery datasource="pgtest" name="result">
SELECT COUNT(*) AS result FROM tgt;
</cfquery>
<cfdump var="#debug#">
<cfdump var="#result.result#">
 
<!--- LIMIT/TOP --->
<cfquery datasource="pgtest">
DELETE FROM tgt;
</cfquery>
<cfquery datasource="pgtest" result="debug">
INSERT INTO tgt (ID) SELECT ID FROM src LIMIT 5 RETURNING *;
</cfquery>
<cfquery datasource="pgtest" name="result">
SELECT COUNT(*) AS result FROM tgt;
</cfquery>
<cfdump var="#debug#">
<cfdump var="#result.result#">
 
<!--- maxrows --->
<cfquery datasource="pgtest">
DELETE FROM tgt;
</cfquery>
<cfquery datasource="pgtest" result="debug" maxrows="5">
INSERT INTO tgt (ID) SELECT ID FROM src RETURNING *;
</cfquery>
<cfquery datasource="pgtest" name="result">
SELECT COUNT(*) AS result FROM tgt;
</cfquery>
<cfdump var="#debug#">
<cfdump var="#result.result#">

Results

The results are very clear:

Query PostgreSQL MS SQL Server
cfquery recordcount database count cfquery recordcount database count
Baseline 10 10 10 10
LIMIT / TOP 5 5 5 5
maxrows 5 10 5 10

So using a maxrows attribute in your query does not prevent you database from executing the query statement completely. The limit in the number of records is only enforced in the JDBC driver (or in ColdFusion) and the database runs the full query.

I would be interested in seeing results from other databases, although I don’t expect them to be different.

In the previous part I have shown that the datatype you use in MS SQL Server has major consequences for the database size. Using an NVARCHAR(35) instead of a UNIQUEIDENTIFIER can triple the size of your tables, and a VARCHAR(35) is somewhere in between. Conventional wisdom relates the performance inversely to the size of a database, with a factor between O(ln(n)) for indexed access and O(n) for table access. To test that, I created an artificial benchmark for my databases with several queries that mainly exercise range access to the tables that saw the highest size ratio from the datatype conversion and access more data then fits in RAM. (Due to the string format difference the values used in the queries differed slightly.) Then I ran those queries from the MS SQL Management Studio and checked the wall clock time.

Datatype Storage requirements (B) Database size (GB) Run time (s)
UNIQUEIDENTIFIER 16 29.6 22
VARCHAR(35) 35 32.5 35
NVARCHAR(35) 70 38.6 47

The benchmark proved conventional wisdom right (unsurprisingly I might add, because it was designed to prove conventional wisdom right).

Querying from ColdFusion

If we add ColdFusion to the mix, the equation changes. Crazy as it may sound, the typeless ColdFusion language has 2 incompatible 128-bit integer datatypes. First there is the UUID, a 128-bit integer with a 35-byte string representation. It has a terribly slow generator function CreateUUID(), but it is incompatible with the rest of the world. Then there is the GUID, a 128-bit integer with a 36-byte string representation. There is no function in ColdFusion to generate GUIDs, but you can either insert an extra - in a UUID or generate them from java.util.UUID. This is the string representation that the rest of the world, including MS SQL Server and Java, uses. Since neither the database nor ColdFusion can cast automatically, we need to make sure we use the right string representation on both sides.

The next question is whether or not to use cfqueryparam. In my opinion not using cfqueryparam should only be done in very specific circumstances where you have a reason not to use it (and comments in the code should explain that reason for the next programmer).

Since we use cfqueryparam we need to decide is whether to use Unicode for our string parameters or not. This is an option in the ColdFusion administrator that enables or disables this for the entire datasource. Most applications I work with need some form of i18n support so we need unicode and have to enable this.

Lastly we need to pick a database driver. ColdFusion ships with a MS SQL Server driver from DataDirect, but there are other alternatives. I have tested the drivers from Microsoft as well during this round.

Test 1

The benchmark I am using for this first round is extremely simple, just a primary key lookup of a row of the table that had the largest relative size reduction between NVARCHAR and UNIQUEIDENTIFIER. This is run for each database datatype, for each driver, once with cfqueryparam and once without cfqueryparam.

Query 1
	SELECT
		*
	FROM
		table
	WHERE
		primary_key= '#searchValue#'
Query 2
	SELECT
		*
	FROM
		table
	WHERE
		primary_key = <cfqueryparam cfsqltype="cf_sql_varchar" value="#searchValue#">

The query without cfqueryparam is the query that is most similar to a query as we would run it from the MS SQL Management Studio. It is only included to provide a reference since I couldn’t believe the results at first, I would not want this in any production code. The table below shows the results, averaged over all runs and all drivers.

Datatype Query 1 (ms) Query 2 (ms)
UNIQUEIDENTIFIER 5 6
VARCHAR(35) 5 39700
NVARCHAR(35) 6 6

As you can see the differences are minimal, except for the case where we have a query sending a cfqueryparam with Unicode hint. This is the same behavior I have shown before: a character set mismatch forces MS SQL Server to change the execution plan to do a table scan instead of an index lookup. The effect is just much bigger here because this is a real sized dataset and not a small testcase.

Test 2

After seeing this I dropped the VARCHAR database from the tests. That allowed me to increase the number of iterations for each test and add a few more queries and still finish this year. So for the following test I had the following queries:

Query 1
	SELECT
		*
	FROM
		questionScore
	WHERE
		questionScoreID = '#searchValue#'
Query 2
	SELECT
		*
	FROM
		questionScore
	WHERE
		questionScoreID = N'#searchValue#'
Query 3
	SELECT
		*
	FROM
		questionScore
	WHERE
		questionScoreID = <cfqueryparam cfsqltype="cf_sql_varchar" value="#searchValue#">
Query 4
	SELECT
		*
	FROM
		questionScore
	WHERE
		questionScoreID = <cfqueryparam cfsqltype="cf_sql_uniqueidentifier" value="#searchValue#">

The way I ended up with Query 4 is that I had no idea what the proper cfsqltype for a uniqueidentifier was, so I just tried something and it didn’t generate an error. (It doesn’t really work either because it doesn’t do any validation on the client side either.) The results over 100 queries provided an interesting view.

With unicode hint for datasource Without unicode hint for datsource
Datatype Driver Query 1 (ms) Query 2 (ms) Query 3 (ms) Query 4 (ms) Query 1 (ms) Query 2 (ms) Query 3 (ms) Query 4 (ms)
UNIQUEIDENTIFIER A 266 485 969 1360 265 500 637 1406
B 250 500 953 1391 250 547 984 1437
NVARCHAR(35) A 297 562 1141 1578 313 579 1110 1594
B 297 579 1079 1625 296 579 1093 1640

As you can see there is a consistent pattern where queries without cfqueryparam are faster then queries with cfqueryparam, and also that cf_sql_varchar is faster then cf_sql_uniqueidentifier. There is a small difference between the speed of a UNIQUEIDENTIFIER and a NVARCHAR and queries with a Unicode hint appear to be marginally faster. The differences between the drivers are well within the margin of error for this test.

But the most important thing is that no combination shows the awful behavior the combination of a VARCHAR field and Unicode hints show.

Preliminary conclusions

So with this batch of tests I can now indicate an order of preference for the datatype to store 128-bit integers in:

  1. If you are building a new application, use the GUID datatype on the ColdFusion side and the UNIQUEIDENTIFIER datatype on the MS SQL Server  side. This minimizes the size of your database, maximizes the speed and gives you a string representation that is compatible with the rest of the world.
  2. If you have an existing application where you use UUIDs and store them in a VARCHAR field, it may be worthwhile to convert that VARCHAR field. Conversion to an NVARCHAR should be quick and easy, conversion to a UNIQUEIDENTIFIER gives the best performance but will require a lot more work.

A still need to figure out where the speed differences in the last table originate: the MS SQL Server or ColdFusion. I have run some initial tests that suggest that the performance difference is actually in the driver and not the database, but since the differences are a lot smaller I need more tests and SQL profiles to figure that out. That may take a while, but when I do I’ll post back.

So far we have seen that to secure our shared hosting customers from each other we need to place them in a Sandbox limiting file system access, datasource access and disabling access to cfexecute, cfobject and CreateObject(JAVA/COM/.NET) . Typically I add cfregistry to the list of disallowed tags as well. While not strictly necessary for security (you are running ColdFusion with a non-privileged account, aren’t you?), the registry is a shared resource that is easily fragmented and has a limited size, so I just want to keep customers out.

cfdump

The problem we have now is that apart from the functionality we intended to disable we have inadvertently disabled some more functionality, including the cfdump tag. In the directory /WEB-INF/cftags/ are a number of .cfm templates that implement tags and one of them is dump.cfm. But since this tag is included much like a customtag would be included, it runs with the same permissions that a normal customtag would be running. And those permissions disable the use of CreateObject(JAVA), causing cfdump to throw an error when some datatypes are dumped.

The workaround (go ask Adobe for a solution) is to replace /WEB-INF/cftags/dump.cfm with a custom dump template. I have made the one I use available as a download. It isn’t pretty, it doesn’t dump Java objects (but shared hosting customers can’t create them anyway), but it does the job.

cfinterface

cfinterface is another one of the tags implemented in CFML in /WEB-INF/cftags/. If you try to use a component implementing an interface you will notice that this throws an error. Apparently we need to grant read and execute permissions on that directory to every Sandbox (you can adapt the script I posted before for that). I just ran into this this weekend when I updated my dump template to show interface information as well, so I guess that shows how much I use shared hosting myself.

The ColdFusion Administrator and Admin API

In ColdFusion 8 Adobe introduced multiple user accounts and permission delegation for the ColdFusion Administrator and the Admin API.  I have my doubts about delegating access to the ColdFusion Administrator in a shared hosting environment since the delegation is based on functionality and not on Sandboxes. So that means you can delegate access to see certain features, such as the logfiles, but not certain features for a specific Sandbox, i.e. the logfiles of a specific customer. Since pretty much every feature in the Administrator I can delegate has security implications, I can not delegate anything.

With the  Admin API the situation is even worse. Internally the Admin API has been implemented in CFML and it suffers from the same problem as cfdump: it uses cfobject and createobject(JAVA) functionality that we can not enable without giving users the ability to upgrade themselves to root administrator. The only way we can use them is in our own management scripts and as a backend invoked through some facade we need to write first.

Application scope variables

ColdFusion piggybacks its application scope variables on the Java application scope. This allows for an integration in the application scope between for instance ColdFusion and JSP code: variables you write in one are available in the other. The downside is that on the Java side the application scope is considered an instance wide scope and all data is accessible. That means that whatever customer A puts in the application scope is accessible to customer B. Put the following in your Application.cfc to see how that works:

<cfcomponent>
 
	<cfset this.name = "" />
 
	<cffunction name="onRequestStart">
		<cfdump var="#application#" />
		<cfabort />
	</cffunction>
 
</cfcomponent>

This sharing of the application scope is by design and as a result it is unsafe to store data in the application scope on a shared host.

I have been hit by a mysterious msvcr71.dll not found error when upgrading the JDK on a CF 8 install on an on and off basis. Today I finally got annoyed enough with it to start digging into the cause. Apparently this is a known issue with Java 6:

Windows Java SE 6 applications using custom launchers must be installed with msvcr71.dll in the same directory as the launcher executable. According to http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vclib/html/_CRT_C_Run.2d.Time_Libraries.asp, this is the new Microsoft C runtime distribution model:

“An application should use and redistribute msvcr71.dll, and it should avoid placing a copy or using an existing copy of msvcr71.dll in the system directory. Instead, the application should keep a copy of msvcr71.dll in its application directory with the program executable. Any application built with Visual C++ .NET using the /MD switch will necessarily use msvcr71.dll.”

This also explains the on and off nature of the error. The way I understand it, you need to go through the following steps before you experience the issue:

  1. you have deployed a portable WAR/EAR file(the portable WAR/EAR files do not contain all the .dll files the Windows specific ones do);
  2. or you have created extra instances and adapted your java.library path in jvm.config;
  3. you perform a JDK upgrade;
  4. you have not installed any application that placed msvcr71.dll somewhere on the path.

I think that explains why I have been experiencing this issue in some cases and not in others. Anyway, Sun’s recommendation to drop msvcr71.dll in the executable folder with jrun.exe solves the issue. (Tempting as it may be to drop it in the JDK folder, it is better to put it in the JRun folder: that way it still works when you upgrade your JDK next time.) I have reported the issue with Adobe so it should be solved in Centaur.

Last February I posted on how storing UUIDs using the native UUID datatype in PostgreSQL was about 15% faster then using a VARCHAR(35) datatype. In March I posted about the performance effects of the “String Format” setting for MS SQL Server databases when using cfqueryparam. Recently I have been working on checking the performance effects when combining different datatypes for UUIDs with the string formats settings for a MS SQL Server 2005 database. (Or to be more exact, I have been checking the performance effects of different datatypes for storing 128-bit integers in the database.)

The starting point is an existing database for an application. That database stores UUIDs using a VARCHAR(35) datatype. From that database I extracted the CREATE statements for the schema using SQL Server Management Studio and build a script from there that follows the following pattern:

  1. create database
  2. create tables including:
    • primary key constraints
    • indexes
    • check constraints
  3. load data (and transform where needed)
  4. create foreign key constraints

Due to the ordering of the load script where all data is loaded before the foreign keys are created the order in which the tables are created and loaded doesn’t really matter: there will never be foreign key constraint errors, not even with circular foreign keys.

For the different datatypes I then manipulated the script a little bit. The first change was a replace to change all occurrences of VARCHAR(35) to NVARCHAR(35). The second was to change all occurrences of VARCHAR(35) with UNIQUEIDENTIFIER. For the last one I then wrote a function to convert the ColdFusion UUID format to the standard format the the UNIQUEIDENTIFIER datatype expects. This function looks like:

CREATE  function var_to_UUID(@cfUUID VARCHAR(35)) RETURNS uniqueidentifier AS
BEGIN
  Return
    CASE
      WHEN Len(@cfUUID) = 35 THEN Substring(@cfUUID,1,23) + '-' + Substring(@cfUUID,24,12)
      ELSE NULL
    END;
END

Next I had to manipulate the data loading script to use this function to convert all the UUIDs. That required some manual editing to make all the insert statements look like:

INSERT INTO [testUUID].[dbo].[language](
  [languageID]
  ,[language]
  )
SELECT
  testUUID.dbo.var_to_uuid([languageID])
  ,[language]
FROM
  [source_DB].[dbo].[language]

The database I used for these tests is on the one hand far from trivial: 80+ tables with normal and composite primary keys, 120+ foreign keys and many check constraints. On the other hand it is very trivial: just one stored procedure and one UDF. To make sure that I am comparing apples to apples I ran the existing database through the create and load sequence to establish my base for comparison and I ran all databases through the example index optimization script from MSDN. The numbers I got after just that sequence are:

Datatype Storage requirements (bytes) Database size
UNIQUEIDENTIFIER 16 29.6 GB
VARCHAR(35) 35 32.5 GB
NVARCHAR(35) 70 38.6 GB

As you can see the database size correlates very well to the storage size of the different datatypes. Apparently we have about 26.5 GB of non-UUID data in the database (which for this database is mostly in NTEXT fields in 2 tables of 18 GB and 6 GB data size respectively). For the tables most and least affected by the change the statistics are:

Table Datatype Storage requirements (bytes) Table size Index size
Most affected UNIQUEIDENTIFIER 16 2.0 GB 1.5 GB
VARCHAR(35) 35 3.4 GB 2.6 GB
NVARCHAR(35) 70 6.4 GB 5.1 GB
Least affected UNIQUEIDENTIFIER 16 17.9 GB 0.3 MB
VARCHAR(35) 35 17.9 GB 0.7 MB
NVARCHAR(35) 70 17.9 GB 2.0 MB

So as you can see there may be good reason to pick the right datatype: if your schema has many UUID columns you can save upto a factor three in storage space (or hardly any space at al if you have few UUIDs). Looking at it from the MS SQL Sever perspective the preferred storage format is UNIQUEIDENTIFIER, followed by VARCHAR(35), and lastly NVARCHAR(35). But MS SQL Server is only half of the equation, ColdFusion is the other half and in the next installment I will show you why the order from the ColdFusion side is radically different.

In the first part we have set the stage for this series: the goal is to protect one shared hosting customer from an ‘inside attack’ by another shared hosting customer on the same ColdFusion instance. In the second part we have gone into the reason why we need ColdFusion Enterprise Edition to secure the filesystem from direct access through cffile and cfdirectory. In this part we will see why we need to take additional steps to secure the filesystem against the other languages we can use in / call from ColdFusion.

Java

Since ColdFusion is written in Java the integration with Java is pretty tight and we can easily use Java code from ColdFusion. So what happens if we try not to use a CFML tag to access a file from some other customer, but use Java directly. You don’t need any Java knowledge to do so, if you Google for some code you will find plenty of examples. For this I downloaded the code from Ben Nadel’s Java Exploration in ColdFusion: java.io.LineNumberReader blogpost, changed the ExpandPath(”./data.txt”) to my template name and ran the code. It displayed the sourcecode of the template, so the code was good. Next I changed the code so the file it pointed to was “c:\\test\\secret.txt”, which is outside my Sandbox. And the code promptly returned an error.

So far so good: our Sandbox protects against reading files directly through Java.

CFEXECUTE

So if we can’t read files from elsewhere on the filesystem directly, perhaps we can copy them to our Sandbox first  using the native functionality of the operating system, and then read them once they are in our Sandbox. (And if you don’t know which files you need, just use xcopy and copy them all recursively.) For that we turn to the cfexecute tag. In the default form that tag would be used like this:

<cfexecute name="c:\windows\system32\cmd.exe" arguments="/c copy #source# #target#" />

But because we are in a Sandbox that will most likely not work  because your Sandbox will not allow you to access the c:\windows\system32\cmd.exe file. To circumvent that, simply upload a copy of cmd.exe to your Sandbox and use that one. With that the code to copy files and then read them through cffile becomes:

So by now it should be clear that CFEXECUTE needs to be disabled to maintain security in a shared hosting environment. The bottom line is that with CFEXECUTE you start a process, that process runs as the same user as ColdFusion, the ColdFusion user by definition has access to the templates of other customers, so the process you started has access to them as well. And this means that useful functionality (albeit used by a limited number of users) needs to bes disabled.

COM, .NET and Java again

The problem is that CFEXECUTE is not the only way to start a new process. COM, .NET and Java can do so as well. Below is a simple code example that shows how to dynamically generate a batchfile, write that to the filesystem inside the Sandbox, execute it through Java, and read the result.

<!--- target file outside our Sandbox --->
<cfset secretFile = server.coldfusion.rootdir & "\lib\neo-datasource.xml" />
<!--- batch file inside our Sandbox --->
<cfset batchFile = ExpandPath("./copy.bat") />
<!--- location where we copy the file inside our Sandbox --->
<cfset readableCopyOfSecretFile = ExpandPath("./readableCopyOfSecretFile.xml") />
<!--- copy command for in our batchfile --->
<cfset batchFileContent = 'cmd /c "copy #secretfile# #readableCopyOfSecretFile#"' />
 
<!--- write the batchfile inside our Sandbox --->
<cffile action="write" file="#batchFile#" output="#batchFileContent#" />
 
<!--- execute the batchfile --->
<cfset CreateObject("java", "java.lang.Runtime").getRuntime().exec(batchFile) />
 
<!--- read the copy of file inside the sandbox --->
<cffile action="read" file="#readableCopyOfSecretFile#" variable="fileContent" />
<!--- display the contects of the file --->
<cfoutput>#HTMLCodeFormat(fileContent)#</cfoutput>

We can prevent this by modifying the Sandbox configuration and disabling the execute permission from the Sandbox, but since ColdFusion itself needs execute permissions to run CFML that will kill ColdFusion as well.  Executing processes from COM works the same as from Java. From .NET it works slightly different because the actual .NET service runs in another process and could potentially have different permissions from the ColdFusion account, but the inability to differentiate the permissions between different users remains.

So the hoster is left with a hard choice: disable CFEXECUTE, CFOBJECT, CreateObject(.NET), CreateObject(COM) and CreateObject(JAVA) or accept that there is no security whatsoever in the shared hosting configuration. If you disable these tags a lot of applications and frameworks won’t work anymore. For instance Transfer ORM needs Java access, so any application build on top of it will not work in a secured shared hosting environment.

Warning

A long time ago I had some other code examples online that could be used to extend the functionality of a shared hosting environment. I have taken these examples down not just because with the arrival of the Admin API they are obsolete, but also because I was informed that somebody had wrecked a shared hosting server with them. So please before you start uploading and running these examples on your shared host, please take a moment to consider the following points:

  • If you want to look further into this code, why not do that on your own development environment?
  • In most jurisdictions accessing other peoples data / code is illegal.
  • Some of the code examples (especially if you replace copy with xcopy) can consume lots of resources.
  • You will never be able to test whether your hosting account is protected from others. You will only be able to test if others are protected from you. So really, why not on your own development environment?

ColdFusion 8 includes the very nice server monitor that allows you to see what is happening in real time. The problem with the server monitor is that it runs in the same process as ColdFusion so when ColdFusion becomes unresponsive there is a chance that the server monitor is inaccessible as well. And of course the server monitor is Enterprise Edition only.

But some of the functionality in the server monitor is built on native functionality of the JVM and there are some tools in the JDK that can extract some of the same information from a running ColdFusion server. The most important one is jstack. Jstack is a program that can connect to any running Java program and create a stack dump of all running threads and some memory statistics. Jstack is not included with the JVM that Adobe uses for ColdFusion 8 so you have to get a full JDK from Sun to get a copy. (While you are at it, why not install JDK 6 update 10 or later that solves the class loading bug in earlier JDK 6 versions?) Once you have that you can simply getting a stack trace by running the command “jstack <pid>” where pid is the process identifier of the ColdFusion (JRun) process you want to investigate.

The complication is that you need to do this from the same useraccount as the account the process you want to analyze is running. On Linux that is simply a matter of su, but on Windows this is more complicated. If you are running ColdFusion in the default configuration where  it runs under the SYSTEM account you need to start another program as SYSTEM, and that is not as easy as it may seem because you can not use the runas command to switch to that account. Some time ago a solution for that was posted at the MSDN blogs that involved creating a temporary service and starting that. I have packaged that solution as the following batchfile:

?View Code WINBATCH
sc create testsvc binpath= "cmd /K start" type= own type= interact
sc start testsvc
sc delete testsvc

When I run this on Windows I get a nice command prompt under the system account. If you want to run this over a remote desktop session, make sure you are connected to the console session (use “mstsc /admin /v:servername” to start your remote desktop session). From that command line I can now run jstack, jmap and all the other JDK tools to analyze a running Java program.

In the first part we have set the stage for this series: the goal is to protect one shared hosting customer from an ‘inside attack’ by another shared hosting customer on the same ColdFusion instance.

If no precautions have been taken, attacking is a simple job. The starting point is a template that does a directory listing of every drive from “a:\” to “z:\” using cfdirectory. When you find something interesting, like the “coldfusion8″ directory, the “jrun4″ directory or a “customers” directory, just drill down from there. Not elegant, but very effective. The only way to hide from it is to use netwerk shares with hard to guess names, but even then it is just a matter of drilling down from the #server.coldfusion.rootdir# to the logfiles and see what the directory paths in some of the mappings and logged errors are.
Hiding (i.e. obscurity) provides no effective hurdle against an inside job and hence no real security.

To get real security against this reading of directories we need to enable Sandbox Security. Sandbox Security allows us to define a directory on the filesystem as a Sandbox and subject every request that starts from that Sandbox to a set of constraints. These constraints can include which tags are allowed, i.e. forbid cfregistry outright, or which resources can be accessed. Typically each Sandbox is defined at the root of a customers FTP and / or WWW directory and then allows for access of only some directories and datasources. Setting up the allowed resources and tags in a Sandbox can occasionally be a bit counterintuitive, for instance to allow a file to be used in a cfinclude it needs execute permissions and several extra directories need to be accessible for some tags.

The thing with Sandbox Security is that it is a feature that is only available in Enterprse Edition. ColdFusion 8 Standard Edition has the ability to restrict the usage of tags, functions and resources as well, but everybody operates in the same Sandbox. So while we can disallow customer A from reading the files of customer B through cfdirectory, that would also disallow customer B from reading his own files. And disabling tags and functions won’t be foolproof either because there is always a way around that. For instance, even if you can’t read a certain file with cffile that doesn’t stop you from mailing it to yourself using cfmailparam.

So here we see the first issue with shared hosting and security: in order to combine them and get a system that is even remotely securable, the hoster needs to invest significantly in a ColdFusion 8 Enterprise Edition license and needs to figure out how to configure Sandbox Security properly. Obviously (if the hoster even decides to bother with all that in the first place) that expense gets charged to the hosted customers, making ColdFusion hosting more expensive then for instance PHP hosting.

A frequent cause for complaints among ColdFusion developers is that shared hosting for ColdFusion is hard to find, expensive and/or limited in functionality. I am not really affected by that since I don’t develop applications that are intended to be deployed in a shared hosting environment. But I do get to see the other side of the medal: at Prisma IT we offer some shared hosting (it is not an area we focus on) and I am responsible for the architecture of the platform. So in this and some followup articles I will dive into some of the complications of securing a shared hosting platform running Adobe ColdFusion 8 on Windows.

Setting the stage

These articles will specifically be about ‘inside jobs’: securing ColdFusion shared hosting customers from each other, while they run on the same ColdFusion instance. This is not about securing servers from outside threats, the starting point is some customer who can FTP his CFML templates to the server and wants to look at the templates and the database records of another customer on the same system.
For this discussion I will largely ignore the complications of combining ColdFusion with other scripting languages. While it is most certainly an interesting subject, the issues involved are too diverse for some general blogs.

Topics

The topics aren’t set in stone yet, but for now I am writing on the following blogs:

  • Filesystem access and Sandbox Security
  • Memory access
  • ColdFusion internals
  • Java, COM and .NET
  • Microsoft Access

Feel free to suggest more / other content in your comments.