Yesterday, I came across an interesting post by John Whish on getting an entity memento in ColdFusion. As suggested by Sam Farmer, John was using SerializeJSON() and DeserializeJSON() to serialize a ColdFusion component into a JSON string and then reserializing it back into a plain old ColdFusion struct. Typically, serialization wouldn't seem unusual; but, what struck me as very odd about John's findings was that the SerializeJSON() method was actually able to grab the private properties of the component instance.

I don't know what component serialization is typically used for; but, the fact that it can access private properties just seemed confusing. Now, I don't really care that much about in-house security, but being able to access private properties via serialization seems like a some sort of a security flaw? But again, I really don't care much about the security implications of this - what I do care about is simply understanding how it works. As such, I felt this required a little bit more exploration.

How are these private proprieties of the component being access? Is ColdFusion using the implicit getters defined by the CFProperty tags? Is it simply reaching into the variables scope? Or, is it using some combination of these two approaches?

To test this, I defined a Girl.cfc ColdFusion component with three properties: name, hair, and bust. Of these properties, only the "name" property has a CFProperty tag (which defines an implicit getter). The "hair" property has no CFProperty tag; but, it does have an explicitly defined getHair() accessor method which always returns "Blonde," regardless of what variables.hair actually contains. The "bust" property has neither a CFProperty tag nor an explicitly defined accessor method. With this combination of conditions across the properties, it should shed some light on how the serialization is accessing the component's private properties.

Girl.cfc

<!---

NOTE: We are defining the accessors attributes. As such,

implicit getters / setters *can* be defined for each of

the CFProperty tags.

--->

<cfcomponent

output="false"

accessors="true"

hint="I am a component with some private variables.">

<!---

We are only defining a single property; and that property

also has a GETTER defined for it. This will create an

actual getName() method in the component.

--->

<cfproperty name="name" type="string" getter="true" setter="false" />

<cffunction

name="init"

access="public"

returntype="any"

output="false"

hint="I return an initialized component.">

<!--- Define arguments. --->

<cfargument

name="name"

type="string"

required="false"

default=""

hint="I am the name property."

/>

<cfargument

name="hair"

type="string"

required="false"

default=""

hint="I am the hair property."

/>

<cfargument

name="bust"

type="string"

required="false"

default=""

hint="I am the bust property."

/>

<!--- Set the default propreties. --->

<cfset variables.name = arguments.name />

<cfset variables.hair = arguments.hair />

<cfset variables.bust = arguments.bust />

<!---

Return this component reference. Remember, when

using the new() method, we must explicitly return

an object reference.

--->

<cfreturn this />

</cffunction>

<cffunction

name="getHair"

access="public"

returntype="string"

output="false"

hint="I return a FALSE hair type.">

<!--- Return blonde no matter what. --->

<cfreturn "Blonde" />

</cffunction>

<cffunction

name="upgradeProperties"

access="public"

returntype="any"

output="true"

hint="I upgrade the component meta data that is involved in the serialization of the component properties.">

<!---

Create an array to hold our property attributes as

they are defined in the component meta data.

--->

<cfset local.attributes = [] />

<!--- Loop over the property names. --->

<cfloop

index="local.property"

array="#[ 'name', 'hair', 'bust' ]#">

<!---

Create the attribute collection for this property

(NOTE: We have to use attribute collection - struct

won't work).

--->

<cfset local.attribute = createObject(

"java",

"coldfusion.runtime.AttributeCollection"

).init()

/>

<!--- Set attribute values. --->

<cfset local.attribute[ "name" ] = local.property />

<cfset local.attribute[ "getter" ] = "true" />

<!---

Add this attribute to the property attribute

collection array.

--->

<cfset arrayAppend(

local.attributes,

local.attribute

) />

</cfloop>

<!--- Overwrite the meta data properties. --->

<cfset getMetaData( this ).properties = javaCast(

"java.lang.Object[]",

local.attributes

) />

<!--- Return the updated meta data. --->

<cfreturn getMetaData( this ) />

</cffunction>

</cfcomponent>

In addition to the various properties, the Girl.cfc ColdFusion component also has a method called upgradeProperties(). This method alters the runtime meta data of the component definition to define all three properties as having a "getter". With this component in place, I then created a test script that tried to serialize the a Girl.cfc instance both before and after the upgradeProperties() method was invoked:

<!--- Create a girl object. --->

<cfset girl = new Girl(

name = "Joanna",

hair = "Brunette",

bust = "34A"

) />

<cfoutput>

<!--- Get the serialized data for this component. --->

JSON: #serializeJSON( girl )#<br />

<br />

<!---

Now, let's upgrade the runtime propreties of the

component class definition to include GETTERs.

NOTE: This does not actually define the getters,

it only defines the properties.

--->

<cfset girl.upgradeProperties() />

<!---

Now that we have upgraded the meta data definiton,

let's try to serialize the component again.

--->

JSON: #serializeJSON( girl )#<br />

<br />

<!---

Try to get one of the properties as if there were

implicit getters.

--->

<cftry>

Bust: #girl.getBust()#<br />

<br />

<cfcatch>

getBust() not defined.<br />

<br />

</cfcatch>

</cftry>

</cfoutput>

<!---

Now that we have updated our runtime properties, let's

output the component meta data.

--->

<cfdump

var="#getMetaData( girl )#"

label="Runtime Meta Data"

/>

As you can see, we're getting the JSON value of the Girl.cfc instance before and after the runtime class definition is altered. We also try to access any newly created implicit getter for the "bust" method. When we run the above code, we get the following page output:

What we have here is very interesting! When we first try to serialize the Girl.cfc instance, the only property ColdFusion accessed was "name" - the one defined explicitly by a CFProperty tag. Of course, this doesn't tell us if the property was access via an implicit getter or directly from the variables scope:

JSON: {"name":"Joanna"}

After we call the serialize the first time, we then alter the runtime properties of the component to set all three properties as being "getter"'able. Once we do that, the situation changes drastically. Now, all three properties are access during serialization:

JSON: {"bust":"34A","name":"Joanna","hair":"Blonde"}

But, let's not jump to conclusions about how these properties are being accessed. As demonstrated by the "Blonde" hair property, ColdFusion is clearly making use of our explicitly defined "getHair()" method which ignores any existing hair value. But what about the "bust" property? We have no explicitly defined getter, so ColdFusion has either created a just-in-time accessor method; or, it has circumvented the component's private scope security.

To test "bust" access, I tried to make use of any newly defined getBust() method. Unfortunately, this results in an exception, which I handle in a CFTry / CFCatch block:

Bust: getBust() not defined.

A quick CFDump of the newly upgraded component meta data confirms that while the properties have been updated, no new getter methods have been defined. This begs the question: are implicit getter methods actually created for CFProperty tags? Or, are implicit getter methods handled in a more "onMissingMethod" type fashion? Well, if you look at the one CFProperty tag that we did define in our component - the name property - you'll see that the getName() accessor is listed as one of the physically defined class methods. As such, I think it's fair to say that ColdFusion handles implicit accessors with physically defined methods.

From this experimentation, I think we can draw the conclusion that component serialization behavior is dictated primarily through the class properties. If a property is defined as having a "getter", that property will be returned in serialization one way or another. If either an implicit or explicit getter method is available, ColdFusion will use that method for data access. If, however, neither of those methods are available, ColdFusion will simply bypass scope security and access the given property directly in the Variables scope.

It should be noted that if you have Template Caching turned on, once you update the properties defined in the component meta data, it will be implicitly updated for any components subsequently instantiated. In fact, if I were to refresh the page used in the above demo, both calls to SerializeJSON() would be the same. For performance reason, the component meta data is only compiled when a particular component class is first instantiated; after that, all new instances of the given component use the pre-compiled meta data.

I guess I've never done Enterprise type architectures where I'm actually passing around full-on objects. I figured I would typically communicate through an API. I can't even think of a system where I would pass around objects.

@Elliott, according to Ben's experiment, serializeJSON() doesn't access the private data by default - it doesn't even access the getHair() method - so trying to clone a Girl.cfc via serializeJSON/deserializeJSON wouldn't actually work.

@Ben, very interesting approach to modify the component metadata to show that serializeJSON() works from that rather than anything else.

Note that you can easily inject a method into a CFC that returns the entire variables scope so security is a bit squishy in such a dynamic language (and that would then allow you to figure out what needed added to the metadata in order to modify what serializeJSON() did).

After doing this, I wanted to play with the functions listed in the meta data. I thought maybe you could use the meta data to create a sort of run-time prototypal inheritance or fly-weight pattern... but no. If you add a function to the meta data, it'll be sticky to the meta-data, but won't actually be available in the component.