class

Analyzer::Cfml::Pure

Inherits Analyzer::Cfml::CfmlEngine < Analyzer < FileHelper < Reference < Object

Plain CFML (ColdFusion / Lucee / BoxLang) attack surface.

CFML has two directly-reachable shapes and no route table:

  • .cfm pages are file-path routed — the path under the web root is the URL, exactly like plain PHP or JSP.
  • .cfc components are not reachable on their own, but any method declared access="remote" is callable over HTTP as /path/To/Component.cfc?method=<name>, with the declared arguments arriving as URL or FORM keys.

Framework route tables (Taffy taffy:uri, ColdBox Router.cfc, Wheels config/routes.cfm, FW/1 variables.framework.routes) are deliberately out of scope here — they warrant their own techs so the generic analyzer isn't the thing that decides framework semantics.

Constants

CFPARAM_ASSIGN_RE = /(?<![.\w])param\s+(url|form|cookie)\.(\w+)\s*=/i
CFPARAM_SCRIPT_RE = /(?<![.\w])param\b[^;\n]*?\bname\s*=\s*["'](url|form|cookie)\.(\w+)["']/i
CFPARAM_TAG_RE = /<cfparam\b[^>]*?\bname\s*=\s*["'](url|form|cookie)\.(\w+)["']/i
CGI_HEADER_RE = /(?<![.\w])cgi\.(http_[a-z_]+)/i

cgi.http_x_forwarded_for is the CFML spelling of an inbound header. The other cgi.* keys (request_method, script_name, ...) are server metadata, not request params.

COMPONENTS_ONLY_OPTION = "cfml_pure_components_only"

Set by the dispatcher when a CFML framework owns the route table.

INTERPOLATION_RE = /#[^#\n]+#/
IS_DEFINED_RE = /\bisDefined\s*\(\s*["'](url|form|cookie)\.(\w+)["']\s*\)/i
LIFECYCLE_PAGES = Set {"application.cfm", "onrequestend.cfm"}

Auto-run lifecycle includes, not requestable pages — the CFML analogue of global.asa.

REMOTE_HINT_RE = /remote/i

CFML is case-insensitive, so access="ReMote" is as valid as access="remote"; gate on one case-insensitive pass rather than a handful of literal spellings.

SCOPE_BRACKET_RE = /#{SCOPE_PREFIX}\s*\[\s*["']([^"'\]]+)["']\s*\]/i
SCOPE_DOT_RE = /#{SCOPE_PREFIX}\.([A-Za-z_]\w*)/i
SCOPE_PATTERNS = [CFPARAM_TAG_RE, CFPARAM_SCRIPT_RE, CFPARAM_ASSIGN_RE, SCOPE_DOT_RE, SCOPE_BRACKET_RE, STRUCT_KEY_EXISTS_RE, IS_DEFINED_RE]
SCOPE_PREFIX = "(?<![.\\w])(url|form|cookie)"

Request-scope reads on .cfm pages. The negative lookbehind keeps document.form.x / application.form.x from registering as a form scope read.

SCRIPT_ACCESS_REMOTE_RE = /\baccess\s*=\s*["']remote["']/i
SCRIPT_BLOCK_RE = /(<script\b[^>]*>)([\s\S]*?)(<\/script>)/i

Client-side JavaScript routinely names a local form (form.submit(), form.action), which the scope patterns above would otherwise read as CFML form-scope access — inventing params and flipping the page to POST. Script bodies are blanked, except #...# spans, because CFML genuinely interpolates server values into JS (var id = #url.id#;).

SCRIPT_REMOTE_PREFIX_RE = /(?<![\w.])remote\s+(?:\w+\s+)?function\s+(\w+)\s*\(/i

Script syntax has two spellings of the same thing. The prefix form is matched loosely and its argument list is delimited by real paren matching; the suffix form is driven off the access attribute so a component with hundreds of private methods costs one scan, not one paren walk per declaration.

SCRIPT_REMOTE_SUFFIX_RE = /(?<![\w.])function\s+(\w+)\s*\(([^)]*)\)([^{;]*?)access\s*=\s*["']remote["']/i
STRUCT_KEY_EXISTS_RE = /\bstructKeyExists\s*\(\s*(url|form|cookie)\s*,\s*["']([^"']+)["']\s*\)/i
WEBROOT_MARKERS = ["webroot/", "wwwroot/", "htdocs/"]

Only unambiguous web-root directory names. Bare public/ and www/ were tried and dropped: they collide with build output such as docs/public/, the same false positive FileHelper documents.

Locating the root from Application.cfc was also tried and is worse — every CFML sub-application, module and test harness ships one (7-25 per repo across the validation corpus), so anchoring collapsed Taffy's 20 example apps onto a single /index.cfm. Leaving the path repo-relative keeps colliding pages distinct and locatable.

Class methods

tech_name
Source

Instance methods

analyze
Source
tech

Instance-side view of the same declaration. The per-file rescues live on this base class, which has no way to name the analyzer that is running inside them, so a skipped file could not be attributed to a tech. Deriving it from analyzer_for keeps the name written exactly once.

Source