class
Analyzer::Cfml::Pure
- Analyzer::Cfml::Pure
- Analyzer::Cfml::CfmlEngine
- Analyzer
- Reference
- Object
Overview
Plain CFML (ColdFusion / Lucee / BoxLang) attack surface.
CFML has two directly-reachable shapes and no route table:
.cfmpages are file-path routed — the path under the web root is the URL, exactly like plain PHP or JSP..cfccomponents are not reachable on their own, but any method declaredaccess="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.
Defined in:
analyzer/analyzers/cfml/pure.crConstant Summary
-
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_foris the CFML spelling of an inbound header. The othercgi.*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 asaccess="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
.cfmpages. The negative lookbehind keepsdocument.form.x/application.form.xfrom registering as aformscope 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
accessattribute 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/andwww/were tried and dropped: they collide with build output such asdocs/public/, the same false positiveFileHelperdocuments.Locating the root from
Application.cfcwas 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 Method Summary
Instance Method Summary
- #analyze
-
#tech : String
Instance-side view of the same declaration.
Instance methods inherited from class Analyzer
analyze
analyze,
base_path : String
base_path,
base_paths : Array(String)
base_paths,
base_relative_path(path : String) : String
base_relative_path,
callees_needed? : Bool
callees_needed?,
content_matches?(content : String, markers : Regex) : Bool
content_matches?,
http_header_name(name : String) : String | Nil
http_header_name,
line_number_for_index(content : String, char_index : Int32) : Int32
line_number_for_index,
logger : NoirLogger
logger,
parallel_analyze(files : Array(String), &block : String -> Nil)
parallel_analyze,
read_file_content(path : String) : String
read_file_content,
result : Array(Endpoint)
result,
tech : String
tech,
unique_params(params : Array(Param)) : Array(Param)
unique_params,
url : String
url,
web_root_path(path : String, markers : Array(String)) : String
web_root_path
Constructor methods inherited from class Analyzer
new(options : Hash(String, YAML::Any))
new
Macros inherited from class Analyzer
analyzer_for(tech)
analyzer_for
Instance methods inherited from module FileHelper
all_files : Array(String)
all_files,
get_files_by_basename(basename : String) : Array(String)
get_files_by_basename,
get_files_by_extension(extension : String) : Array(String)
get_files_by_extension,
get_files_by_extensions(extensions : Array(String)) : Array(String)
get_files_by_extensions,
get_files_by_prefix(prefix : String) : Array(String)
get_files_by_prefix,
get_files_by_prefix_and_extension(prefix : String, extension : String) : Array(String)
get_files_by_prefix_and_extension,
get_files_by_relative_path(relative_path : String, root : String = "") : Array(String)
get_files_by_relative_path,
get_public_dir_files(base_path : String, folder : String) : Array(String)
get_public_dir_files,
get_public_files(base_path : String, anchors : Array(String) = ["shard.yml", "Gemfile"]) : Array(String)
get_public_files,
walked_path(expanded : String) : String
walked_path
Class Method Detail
Instance Method Detail
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.