Skip to main content
Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.

Unifying Type Casting Exceptions For Cleaner Log Aggregation In Lucee CFML 5.3.7.47

By Ben Nadel on
Tags: ColdFusion

At InVision, we use Loggly as our centralized log aggregation service. And, when I go to consume said logs, I am generally breaking them down by "Error Message". Most of the time, this is great. However, when a script kiddie starts barfing malicious data into my pages, it creates a lot of noise in Loggly since Lucee CFML 5.3.7.47 creates unique error messages for each type casting exception. In order to cut down on this noise, and to help the security team fine-tune the WAF (Web Application Firewall) rules, I wanted to unify all type casting errors under a single error message.

To get a sense of what I mean by "noise", this is what my Loggly search was looking like after a malicious actor scanned the application:

Noisy Loggly errors broken down by error message.

This is a break down by "Error Message". And, as you can see, I have 115K errors all with unique type-casting error messages. This is the result of malicious data running into the concrete wall of security that is the CFParam tag.

To make this more manageable, I want to group all of these type casting errors under the same error message:

Lucee type casting error.

Under the hood, the majority (but not all) of the type casting errors are instances of the lucee.runtime.exp.CasterException Java class. I was hoping that I could just check the instance of the error object by using the isInstanceOf() function:

isInstanceOf( error, "lucee.runtime.exp.CasterException" )

... but, unfortunately, this did not work.

That said, I did discover that you can catch errors of the type, CasterException:

catch( "lucee.runtime.exp.CasterException" error )

And, that you can throw an existing error object using throw():

throw( object = error )

Putting this all together, I was able to start unifying type casting errors under a single error message. Of course, I didn't want to lose the original error message; so, I moved it into the "detail" field:

<cfscript>

	// Some test values.
	request.stringValue = "hello";
	request.structValue = {};
	request.arrayValue = [];
	request.numberValue = 4;

	try {

		// The PARAM tag will throw casting exceptions when it validates values.
		// --
		param name="request.stringValue" type="boolean";
		// param name="request.stringValue" type="numeric";
		// param name="request.structValue" type="string";
		// param name="request.arrayValue" type="string";

	} catch ( any error ) {

		logError( error );

	}

	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //

	/**
	* I log the given error to the standard error string as a JSON payload.
	* 
	* @error I am the object being logged.
	*/
	public void function logError( required any error ) {

		// We might want to modify the data being logged. As such, let's create a local
		// copy of it so we don't corrupt the passed-in value.
		var data = structCopy( error );

		// In Lucee CFML, the type casting error messages are instance-specific. Meaning,
		// they contain String-data that's unique to the values being cast. This makes
		// them VERY NOISY within log aggregation as they cannot be grouped together. As,
		// such, we want to take all type casting errors and try and coalesce them into a
		// single TYPE for aggregation.
		if ( isCasterException( error ) || isLooselyCasterException( error ) ) {

			// Move the "message" into the "detail".
			data.detail = ( data.message & ". " & data.detail );

			// Create a unifying "message" for all casting errors.
			data.message = "Lucee type casting error.";

		}

		systemOutput( serializeJson( data ), true, true );

	}


	/**
	* I determine if the given error object is a Type Casting exception.
	* 
	* @error I am the object being inspected.
	*/
	public boolean function isCasterException( required any error ) {

		var errorType = ( error.type ?: "" );

		// All casting errors are "Expression" errors. As such, if the type is not an
		// expression, we can short-circuit on digging any deeper into the error object
		// internals.
		if ( errorType != "expression" ) {

			return( false );

		}

		try {

			throw( object = error );

		} catch ( "lucee.runtime.exp.CasterException" localError ) {

			return( true );

		} catch ( any localError ) {

			return( false );

		}

	}


	/**
	* I LOOSELY determine if the given error object is a Type Casting exception by
	* looking at the contents of the error message.
	* 
	* @error I am the object being inspected.
	*/
	public boolean function isLooselyCasterException( required any error ) {

		var errorType = ( error.type ?: "" );
		var errorMessage = ( error.message ?: "" );

		// All casting errors are "Expression" errors. As such, if the type is not an
		// expression, we can short-circuit on digging any deeper into the error message.
		if ( errorType != "expression" ) {

			return( false );

		}

		// Looking for messages with a format like:
		// --
		// "Cannot cast type [X] to a value of type [Y]"
		return(
			errorMessage.reFindNoCase( "^(can't|cannot) cast" ) &&
			errorMessage.find( "[" )
		);

	}

</cfscript>

As you can see, when I go to log the CFParam error, I check to see if the error is a type casting error. And, if so, I move the "message" value into the "detail" field and override the "message" field with my unifying error message. And now, when those type casting errors get logged, they look like this:

Lucee CFML type casting errors unified under a single error message.

As you can see, the error data is now being logged with the message, Lucee type casting error.; and, the original error message Can't cast String [hello] to a value of type [boolean] has been copied / prefixed into the detail field.

FULL DISCLOSURE: In production, I'm actually just using the "loosely" version of the logic that inspects the contents of the error message (looking for "can't cast"). And, so far, this has been sufficient. But, I wanted to see if I could get more accurate as a fun exploration in this code.

Now, when I look in Loggly, all of these malicious requests are grouped under a single error message. This makes it much easier for me to consume the logs; and, it makes it easier for me to share "malicious logs" with our Security team.



Reader Comments

Hi Ben

This is a real eye opener.

I get hundreds of CAST error messages a day on my VPS. This has been happening for years :-(
I kept thinking it was to do with my dodgy code. But, it looks like it is a SQL injection thing.

Thanks for uncovering the source of these errors. I may even use your routine above to group them. It will help tidy things up.

Reply to this Comment

@Charles,

Heck yeah. There's a reason that "Injection" is always the number one item on the OWASP (Open Web Application Security Project): https://owasp.org/www-project-top-ten/

We are getting hammered with stuff like this all the time. It's so frustrating because it makes the logs harder to understand and to react-to. I keep having an ongoing conversation with my Security team as to weather or not I should be proactively blocking IP-addresses that throw a lot of these errors. They tend to fall in the "No, it's just chasing a moving target" camp. But, I keep thinking that I should do something.

Reply to this Comment

In a way, the fact you are getting 115K CAST errors means your web application has attracted a lot of attention. I kind of wish I had this problem, on your scale...:-)

Reply to this Comment

By the way Ben. I just got my first pure Angular front end role. I start tomorrow. It is a fully remote job. Feeling a little bit nervous! 😬
Unfortunately, Coldfusion has pretty much vanished from the UK. I really miss CF, but I have to earn a living somehow. 😉

Reply to this Comment

@Charles,

That's awesome, though, congratulations!! Angular is really fun stuff! I gotta get back into it more - I've been so side-tracked by all the Lucee CFML stuff. But, I still love me some Angular as well. Very exciting!!!

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Blog
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.