Thursday, January 22, 2026

Dead Ends, Red Herrings, and Failures In Our Time




On the good days in security research, you get to channel equal parts Archimedes and Ric Ocasek in your successes. The pieces all come together as expected, the hunches turn into reality, everything just works.  And you should, of course, celebrate those moments of eureka and magic that went into your discovery, since your newest exploit is often your favorite exploit.  Though there's the rub -- your favorite exploit may forever be the one that you haven't found yet.  The perennial next one.

But this post isn't about successes in security research; it's about the failures.  If you're lucky, failure comes quickly.  A guess or a "what about..." falls apart and is disproven in minutes and not hours (or longer).  Plus every failure is an opportunity to use what you've learned about an application or a protocol or whatever in the future.  The work that goes into ten dead ends from poring over source code or RFCs can lead to a much better understanding of the overall system.  And those failures might lead you to something that you otherwise wouldn't have found or wouldn't have thought about.  Something real.  But not today.

Today we'll be looking at some ColdFusion vulnerability research that was interesting and promising at first look, but ultimately wasn't exploitable in the way I had hoped.  
Service Layer CFC webservices were introduced in ColdFusion 9; they have been deprecated since ColdFusion 2018 and were removed in ColdFusion 2025.  There are CFC files for various services in the /CFIDE/services/ directory, such as image.cfc and pdf.cfc.  We'll be using the Upload service (upload.cfc) in our example, although the parts we care about all work the same way.  These services all take the following parameters:  method, serviceusername, and servicepassword, in addition to any method-specific parameters.  The serviceusername and servicepassword correspond with an authorized service user, defined in CFAdmin.


<><><><><><><><><><><><><><><><><><><><><>

Nick looked down into the pool from the bridge. It was a hot day. A kingfisher flew up the stream. It was a long time since Nick had looked into a stream and seen trout. They were very satisfactory. As the shadow of the kingfisher moved up the stream, a big trout shot upstream in a long angle, only his shadow marking the angle, then lost his shadow as he came through the surface of the water, caught the sun, and then, as he went back into the stream under the surface, his shadow seemed to float down the stream with the current, unresisting, to his post under the bridge where he tightened facing up into the current.

Nick's heart tightened as the trout moved.  He felt all the old feeling.¹


<><><><><><><><><><><><><><><><><><><><><>


After some code review and fuzzing, I stumbled on a way to generate what looked like a promising error -- Entity has incorrect type for being called as a function. -- that I could trigger by passing an arbitrary value in the isAllowed or isAllowedIP URL or POST data (FORM scope) parameter.  


So a request like this one:

http://my-cf-server/CFIDE/services/upload.cfc?method=uploadBase64Binary&serviceusername=foo&servicepassword=bar&isAllowed=EXCEPTION

would trigger this exception:



Error messages talking about calling a function?  Method names like invoke and _invokeUDF?  Sounds pretty promising, right?  


Let's look at the decompilation of /CFIDE/services/upload.cfc:



In the two lines marked above, we can see that we're calling parentPage._get() to get our isAllowed and isAllowedIP objects.


The _get() method in coldfusion.runtime.CfJspPage.class is called to search for the variables.  If we don't tamper with these values, these function objects are created in /CFIDE/services/base.cfc:


And the isAllowed and isAllowedIP functions are defined in coldfusion.servicelayer.ExposedServiceManager.class:



We can follow the code flow if we do pass in a URL parameter named isAllowed, tracing the _get() function in coldfusion.runtime.CfJspPage.class to understand how the   _get("ISALLOWED") in upload.cfc gets processed:





Besides just reading the decompiled code, we can also attach a remote JVM debugger through the searchScopes(ssr) flow.  Doing so shows us that we finally find our isAllowed variable in the Arguments scope:







And this makes sense based on how ColdFusion resolves variable scopes.  The Arguments scope contains variables passed in a call to a user-defined function or ColdFusion component methods.  When we pass isAllowed to our remote CFC method, it's added to the ArgumentCollection for that function call and stored in the Arguments scope. And when searching for an unscoped variable, the Arguments scope is the second scope searched.  


<><><><><><><><><><><><><><><><><><><><><>

Nick leaned back against the current and took a hopper from the bottle. He threaded the hopper on the hook and spat on him for good luck. Then he pulled several yards of line from the reel and tossed the hopper out ahead onto the fast, dark water. It floated down towards the logs, then the weight of the line pulled the bait under the surface Nick held the rod in his right hand, letting the line run out through his fingers.

There was a long tug. Nick struck and the rod came alive and dangerous, bent double, the line tightening, coming out of water, tightening, all in a heavy, dangerous, steady pull. Nick felt the moment when the leader would break if the strain increased and let the line go.

The reel ratcheted into a mechanical shriek as the line went out in a rush. Too fast. Nick could not check it, the line rushing out, the reel note rising as the line ran out. With the core of the reel showing, his heart feeling stopped with the excitement, leaning back against the current that mounted icily his thighs, Nick thumbed the reel hard with his left hand. It was awkward getting his thumb inside the fly reel frame.

As he put on pressure the line tightened into sudden hardness and beyond the logs a huge trout went high out of water. As he jumped, Nick lowered the tip of the rod. But he felt, as he dropped the tip to ease the strain, the moment when the strain was too great; the hardness too tight. Of course, the leader had broken. There was no mistaking the feeling when all spring left the line and it became dry and hard. Then it went slack.²


<><><><><><><><><><><><><><><><><><><><><>


When we do pass in an isAllowed value via the URL or POST data parameter, it's cast as a String.  I was also able to pass in values that were cast as arrays, booleans, and a few other object types.  But what we need is to pass in data that's cast as a UDFMethod object.  That would let us run code of our choice.  

I knew what I had to do, but couldn't find a way to do it.  And didn't know if it was even possible.  This led me down a path of lots of reading, code review, experimentation, and learning.  I shared what I knew with a few people in the hope that I'd find success with a collaborator.  Hours, days, weeks, months passed.  Idea, dig, test, fail, repeat.  After a lot of effort, I ultimately could not find any way to cast this data type from a URL parameter, FORM data, or any other user-controlled source as a UDFMethod.  All I could do was trigger an exception.  Sometimes you just have to put that idea or intriguing error message back on the shelf.

<><><><><><><><><><><><><><><><><><><><><>


Ahead the river narrowed and went into a swamp. The river became smooth and deep and the swamp looked solid with cedar trees, their trunks close together, their branches solid. It would not be possible to walk through a swamp like that. The branches grew so low. You would have to keep almost level with the ground to move at all. You could not crash through the branches. That must be why the animals that lived in swamps were built the way they were, Nick thought.

[...]

Nick stood up on the log, holding his rod, the landing net hanging heavy, then stepped into the water and splashed ashore. He climbed the bank and cut up into the woods, toward the high ground. He was going back to camp. He looked back. The river just showed through the trees. There were plenty of days coming when he could fish the swamp.³



_______________________________

  1. Ernest Hemingway, "Big Two-Hearted River"
  2. Ibid.
  3. Ibid.


No comments:

Post a Comment