class Analyzer::Aspnet::WebForms

Overview

ASP.NET WebForms attack surface.

WebForms is file-path routed: an .aspx page's location under the web root is its URL. The parameters, however, mostly do not live in the page — they live in its code-behind and, more often, in the .ascx user controls the page registers. Across the validation corpus only ~19% of literal request reads sit in page code-behind and 54% sit in user controls, so a page-only scan would miss most of the surface. Control reads are therefore attributed to the pages that register them.

Defined in:

analyzer/analyzers/aspnet/webforms.cr

Constant Summary

ALIASED_COLLECTION_RE = /\b\w+\s*\.\s*(QueryString|Form|ServerVariables)\s*[\(\[]\s*"([^"]*)"\s*[\)\]]/i

Collection reads, in both indexer syntaxes. In every pattern below the closing delimiter must follow the literal immediately: Request("laauth-" & TabModuleId) builds its key at runtime, and capturing laauth- from it would invent 216 bogus params.

QueryString / Form / ServerVariables name nothing but an HTTP request, so any receiver is accepted. That is what catches aliased locals: kartris's Image.ashx assigns Dim req As HttpRequest = context.Request and then does ten reads through req, which a Request-anchored pattern would all miss.

BARE_REQUEST_RE = /\bRequest\s*[\(\[]\s*"([^"]*)"\s*[\)\]]/i
CLASS_DEFINITION_RE = /(?:^|\s)(?:Partial\s+|partial\s+)?(?:Public\s+|Friend\s+|public\s+|internal\s+)?(?:Class|class)\s+(\w+)/

Class declarations, used only to resolve services whose code lives away from the handler file.

CONTENT_PAGE_RE = /\bMasterPageFile\s*=|<asp:Content\b/i

A content page's form belongs to its master page. The master is normally resolved and scanned, so SERVER_FORM_RE finds it there — but not always: CarrotCake's plugin pages name ~/Site1.Master from a sibling project the scan base does not contain. Declaring a master is itself the evidence, since a master page without an HtmlForm is not a usable master.

CS_SIGNATURE_RE = /\b(?:public|internal)\s+(?:(?:static|virtual|override|async)\s+)*[\w<>\[\],.\s]+?\s+(\w+)\s*\(([^)]*)\)/
DESIGNER_FILE_RE = /\.designer\.(?:cs|vb)\z/i
DIRECTIVE_ATTR_RE = /([\w:.-]+)\s*=\s*"([^"]*)"/
DIRECTORY_INDEX = "default.aspx"
FRAMEWORK_FIELDS = Set {"__viewstate", "__viewstategenerator", "__viewstateencrypted", "__eventtarget", "__eventargument", "__eventvalidation", "__lastfocus", "__previouspage", "__asyncpost", "__scrollpositionx", "__scrollpositiony"}

WebForms postback plumbing, not user-facing parameters.

GLOBAL_ASAX = "global.asax"

Global.asax marks the application root, and unlike CFML's Application.cfc there is at most one per web app (verified across the corpus: 1 for kartris, 1 for DNN at DNN Platform/Website/, 0 for the two module-style repos), which makes it a trustworthy anchor rather than a guess.

PAGE_DIRECTIVE_RE = /<%@\s*(?:Page|Control|Master|WebHandler|WebService)\b([\s\S]*?)%>/i

Directives span multiple physical lines in real projects, so the whole <%@ ... %> block is captured rather than the first line.

PAGE_EXTENSIONS = [".aspx", ".ashx", ".asmx"]

Requestable handlers. .ascx (user control) and .master (master page) are composed into pages and are blocked by the default IIS handler mapping, so they are sources of params but never routes.

POST_HANDLING_RE = /\bIsPostBack\b|__doPostBack|\bRequest\s*\.\s*(?:Form|Files|InputStream)\b/i

Code-behind evidence, for handlers and for pages whose markup carries no form at all. Reading — or merely enumerating — the form collection is conclusive: DNN's PayPal IPN receiver has no markup beyond its directive and does foreach (string strName in this.Request.Form), which yields no literal parameter name but proves the page is a POST target. Files and InputStream are only ever populated by one.

REGISTER_DIRECTIVE_RE = /<%@\s*Register\b([\s\S]*?)%>/i
REQUEST_COLLECTION_RE = /\bRequest\s*\.\s*(QueryString|Form|Params|Cookies|Headers|ServerVariables)\s*[\(\[]\s*"([^"]*)"\s*[\)\]]/i

Params / Cookies / Headers are ordinary member names — mail messages, HTTP clients and parsers all expose them — so these must be anchored to Request. \bRequest\. still covers every receiver chain (this., context., HttpContext.Current.).

SERVER_FORM_RE = /<[\w:.]*form\b[^>]*\brunat\s*=\s*["']?server/i

Evidence that a page can act on a POST.

<form runat="server"> is the decisive one — the framework posts back to the page's own URL — but the tag name has to be matched loosely, because control libraries subclass HtmlForm: DNN's own Default.aspx carries <dnn:Form ID="Form" runat="server">, and an anchored <form missed it.

SERVICE_EXTENSIONS = [".asmx", ".ashx"]
VB_SIGNATURE_RE = /\b(?:Public|Friend)\s+(?:(?:Overrides|Shared|Overloads|NotOverridable)\s+)*(?:Function|Sub)\s+(\w+)\s*\(([^)]*)\)/
WEBMETHOD_ATTR_RE = /(?:\[WebMethod[^\]]*\]|<WebMethod[^>]*>)/i

[WebMethod] / <WebMethod()> on an .asmx service maps to POST /Service.asmx/MethodName.

WEBMETHOD_WINDOW = 400
WEBROOT_MARKERS = ["wwwroot/"]

Class Method Summary

Instance Method Summary

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, 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

def self.tech_name : String #

[View source]

Instance Method Detail

def analyze #

[View source]