DEV Community

Cover image for Safely Fetching Scoped Variables (while avoiding Scope Injection)
James Moberg
James Moberg

Posted on

Safely Fetching Scoped Variables (while avoiding Scope Injection)

I'm testing some ideas. I'm not sure if I'm on the right path or not, but thought I'd share.

I have some UDF & CFC libraries that we've built over the year and I have some checks to determine whether default application variables exist and use them to override default values. In order to avoid possible "Scope Injection" & errors (when scopes don't exist), I thought I'd attempt to write a function that uses "safe navigation" to verify scope classname, verify key (in the struct keylist) and return the value (w/optional fallback).

I've included a simple example where application.randomVarName_xxx is injected into the URL & FORM scopes on a script that doesn't use an application CFC or CFM file. As a result, dumping the application value actually returns url.application.randomVarName_xxx instead of a non-existent application-scoped value.

Another example is the retrieval of a server variable with a default value (if it doesn't exist).

Here's a little bit of my post-CFSummit2022 discussion of this on Twitter. https://twitter.com/gamesover/status/1578154242031767553

  • Yeah... I tend to use structKeyExists (or struct.keyExists("keyname")) when the key names are dynamic/random... but yeah, isdefined does reach out to all other scopes if it can't find it. Too bad there's no option to limit searching to within a specific scope. - Me
  • Short term that would be an easy UDF to write and share on GitHub. Longer term that would be a nice upgrade to IsDefined() they could implement ala updates to StructNew() - Nolan

Here's the source code proof-of-concept. Let me know your thoughts.

NOTE: I'd provide a direct link to TryCF.com, but I noticed that gists are permanently cached and never refresh. If you want to test it there, please copy-and-paste the code.

https://gist.github.com/JamoCA/7e544f1488a703d868cecc2b7ae5c2ed

<!--- 2022-10-07 getScopedVariable UDF by James Moberg / SunStar Media
GIST: https://gist.github.com/JamoCA/7e544f1488a703d868cecc2b7ae5c2ed
BLOG: https://dev.to/gamesover/safely-fetching-scoped-variables-while-avoiding-scope-injection-2ee3
TWEET: https://twitter.com/gamesover/status/1578522152600424449
This getScopedVariable UDF tests to determine if a valid scope + variable exists w/o throwing an error.
If non-existent, a default value is returned. --->
<cfscript>
// This is only test code.
// The reason for this is to 1) attempt to validate scoped variables when session, client or application variables aren't enabled
// and 2) return a fallback variable if non-existent (instead of throwing an error)
// ACF 2016+ has application this.searchImplicitScopes setting
// Railo/Lucee has applications this.scopeCascading="strict" setting
// More info on Scope Injection and scopes that are affected
// https://www.petefreitag.com/item/834.cfm
// https://helpx.adobe.com/coldfusion/developing-applications/the-cfml-programming-language/using-coldfusion-variables/about-scopes.html
function getScopedVariable(required string name, required string scope="application", any default="") {
local.scopeClassMap = [
"application": ["ApplicationScope", "ApplicationImpl"]
,"form": ["FormScope", "FormImpl"]
,"url": ["UrlScope", "URLImpl"]
,"session": ["SessionScope", "sessionMemory"]
,"client": ["NoOperClientScope", ""]
,"server": ["ProtectedScope", "ServerImpl"]
,"cookie": ["CookieScope", "CookieImpl"]
,"cgi": ["CgiScope", "CGIImpl", "CGIImplReadOnly"]
];
local.variable = {};
switch(lcase(arguments.scope)) {
case "application":
local.subType = application?.getClass()?.getName();
local.variable = application;
break;
case "form":
local.subType = form?.getClass()?.getName();
local.variable = form;
break;
case "url":
local.subType = url?.getClass()?.getName();
local.variable = url;
break;
case "session":
local.subType = session?.getClass()?.getName();
local.variable = session;
break;
case "client":
try {
local.subType = client?.getClass()?.getName(); // Lucee bug
if (local.keyexists("subType")) local.variable = client;
} catch (any e){};
break;
case "server":
local.subType = server?.getClass()?.getName();
local.variable = server;
break;
case "cookie":
local.subType = cookie?.getClass()?.getName();
local.variable = cookie;
break;
case "cgi":
local.subType = cgi?.getClass()?.getName();
local.variable = cgi;
break;
default:
return arguments.default;
break;
}
// return fallback if subtype doesn't exist or doesn't match classname
if (!local.keyexists("subType") || !arrayfind(local.scopeClassMap[arguments.scope], listlast(local.subType,"."))) {
return arguments.default;
}
// return fallback if key does not exist in the scoped object
if (!listfindnocase(structkeylist(local.variable), arguments.name)){
return arguments.default;
}
return local.variable[arguments.name];
}
// establish a variable name
testVarName = "randomVarName_#gettickcount()#";
// inject variables into common scopes that are searched
url.application = [
"#testVarName#": ["url scope"]
];
form.application = [
"#testVarName#": ["form scope"]
];
writedump(var=url, label="url (w/injected scope-named key)");
writedump(var=form, label="form (w/injected scope-named key)");
// output "application" variable. (This application variable doesn't exist and shouldn't be dumped.)
try {
writedump( var=application[testVarName], label="application.#testVarName# (unsafe)");
} catch (any e) {
writeoutput("<div>Error: Application scope or '#testVarName#' doesn't exist.</div>");
}
// use function to validate scoped variable and return fallback if it doesn't exist.
result = getScopedVariable(name=testVarName, scope="application", default=["default fallback"]);
writedump( var=result, label="getScopedVariable");
// Server scope fallback test
// server[testVarName] = [now()];
// if scoped variable (that isn't searched) doesn't exist, an error is thrown.
try {
writedump( var=server[testVarName] , label="server.#testVarName# (unsafe)");
} catch (any e) {
writeoutput("<div>Error: server scope or '#testVarName#' doesn't exist.</div>");
}
// use function to validate scoped variable and return fallback if it doesn't exist.
result = getScopedVariable(name=testVarName, scope="server", default=[now()]);
writedump( var=result, label="getScopedVariable");
// structdelete(server, testVarName);
</cfscript>

Speedy emails, satisfied customers

Postmark Image

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up

Top comments (2)

Collapse
 
bennadel profile image
Ben Nadel • Edited

So, just looking for clarity, does this only matter if the variable in question does not exist? Meaning, is the url scoped checked for application.foo only if application.foo doesn't exist? Or does it have to do with scope precedence?

Oh, actually I see in the article from Pete that you mentioned, that it only does this if the variable doesn't exist.

Collapse
 
gamesover profile image
James Moberg

Even if the variable does exist, what if it's not from the exact scope that you are expecting it to be from? I've seen some lazy CFML and have helped some developers that weren't aware of what was happening since they tended to reuse the same variable names within multiple scopes. I've now found that it's best to "be explicit" and "trust, but verify". (I credit my "dotNet blood brother" for the "trust, but verify" mantra as he works in a var-typed environment and is constantly encountering issues like this from other developers that he manages.)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Discover a treasure trove of wisdom within this insightful piece, highly respected in the nurturing DEV Community enviroment. Developers, whether novice or expert, are encouraged to participate and add to our shared knowledge basin.

A simple "thank you" can illuminate someone's day. Express your appreciation in the comments section!

On DEV, sharing ideas smoothens our journey and strengthens our community ties. Learn something useful? Offering a quick thanks to the author is deeply appreciated.

Okay