class

Analyzer::Php::Wordpress

Inherits Analyzer::Php::PhpEngine < FileScanEngine < Analyzer < FileHelper < Reference < Object

WordPress attack-surface extractor.

WordPress does not expose a conventional route table; its HTTP surface is registered imperatively through a handful of well-known APIs:

  • REST API — register_rest_route( $namespace, $route, $args ) served under /wp-json/{namespace}/{route}.
  • Admin AJAX — add_action( 'wp_ajax_{action}', ... ) and the public wp_ajax_nopriv_{action} variant, dispatched by /wp-admin/admin-ajax.php?action={action}.
  • Admin POST — add_action( 'admin_post_{action}', ... ) / admin_post_nopriv_{action}, dispatched by /wp-admin/admin-post.php?action={action}.

Constants

ADMIN_AJAX_PATH = "/wp-admin/admin-ajax.php"
ADMIN_POST_PATH = "/wp-admin/admin-post.php"
ALL_HTTP_VERBS = ["GET", "POST", "PUT", "PATCH", "DELETE"]
ARRAY_KEY_RE = /\A['"]([^'"]+)['"]\s*=>/
CALLABLE_ARRAY_RE = /\A(?:array\s*\(|\[)\s*[^,]+,\s*['"](\w+)['"]\s*(?:\)|\])\z/i

array( $this, 'check' ) / [ __CLASS__, 'check' ] / 'check' / 'Class::check': the name of the method or function called.

CALLABLE_STRING_RE = /\A['"](?:[\w\\]+::)?(\w+)['"]\z/
CAPABILITY_CHECK_RE = /(?:\breturn\s+(?:\(\s*bool\s*\)\s*)?|\bif\s*\(\s*!\s*)(?:#{CAPABILITY_FNS})\s*\((?:[^,()]*,)?\s*(?:['"]([\w-]+)['"])?(?:[^()]|\([^()]*\))*\)\s*(?:;|\)\s*(?:\{\s*)?(?:return|throw)\b)/

A check that decides the callback: its return value (return current_user_can('x');), or the whole condition of an if that refuses (if ( ! current_user_can('x') ) { return new WP_Error(...); }). A check under a further condition ('edit' === $context && ! ...) leaves the default path open, so it is not one.

CAPABILITY_FNS = "current_user_can|user_can|current_user_can_for_site|current_user_can_for_blog"
CLASS_DECL_RE = /(?<![:\w$>])(?<!new\s)\bclass\s+(\w+)(?:\s+extends\s+\\?(?:[\w\\]+\\)?(\w+))?[^{;]*\{/
DISPATCH_METHODS = ["GET", "POST"]

WordPress admin-ajax / admin-post dispatch on $_REQUEST['action'], so both verbs are valid entry points.

HOOK_RE = /add_action\s*\(\s*['"](wp_ajax_(?:nopriv_)?|admin_post_(?:nopriv_)?)([A-Za-z0-9_\-]+)['"]\s*,/i

add_action('wp_ajax_foo', ...) / wp_ajax_nopriv_foo add_action('admin_post_foo', ...) / admin_post_nopriv_foo

The trailing \s*, requires the hook name to be a complete string literal (followed by the callback argument). This rejects a prefix+variable concatenation like 'wp_ajax_myplugin_' . $tab, whose closing quote is followed by . — otherwise we would emit the truncated phantom action myplugin_.

LOGIN_CHECK_RE = /(?:\breturn\s+(?:\(\s*bool\s*\)\s*)?|\bif\s*\(\s*!\s*)(?:#{LOGIN_FNS})\s*\(\s*\)\s*(?:;|\)\s*(?:\{\s*)?(?:return|throw)\b)/
LOGIN_FNS = "is_user_logged_in"
NO_PROPERTIES = {} of String => String
PERMISSION_KEY_RE = /\A['"]permission_callback['"]\s*=>\s*/
REST_ARGS_KEY_RE = /\A['"]args['"]\s*=>\s*/
REST_METHOD_CONSTANTS = {"READABLE" => ["GET"], "CREATABLE" => ["POST"], "EDITABLE" => ["POST", "PUT", "PATCH"], "DELETABLE" => ["DELETE"], "ALLMETHODS" => ["GET", "POST", "PUT", "PATCH", "DELETE"]}

WP_REST_Server:: method-group constants → concrete HTTP verbs.

SPLIT_ARGS_RULES = Noir::TopLevelSplit::Rules.new(nest: (Noir::TopLevelSplit::Nest::Paren | Noir::TopLevelSplit::Nest::Bracket) | Noir::TopLevelSplit::Nest::Brace, quotes: "\"'", escape: Noir::TopLevelSplit::Escape::InQuotes, strip: false, empties: Noir::TopLevelSplit::Empties::DropTrailing, per_kind: false, clamp: false)

Rules::SHARED_DEPTH_RAW with a trailing empty dropped and, like erlang/cowboy.cr, WITHOUT clamping: the body this replaces closed brackets with a bare depth -= 1, so a fragment beginning with a closer (")x, y") drives depth negative and stops splitting. That is observable on the regex-sliced input this actually receives, so it is preserved rather than normalised.

File-local because wordpress is the only splitter pairing clamp: false with unstripped parts.

STRING_PROPERTY_RE = /\b(?:public|protected|private|var)\s+(?:static\s+)?(?:\??[\w\\]+\s+)?\$(\w+)\s*=\s*(?:'([^'\\]*)'|"([^"\\$]*)")/
TAGGER = "wordpress_analyzer"
THIS_ASSIGN_RE = /\$this\s*->\s*(\w+)\s*=\s*(?:'([^'\\]*)'|"([^"\\$]*)")\s*;/

$this->namespace = 'wp/v2';, as a constructor sets it.

Class methods

tech_name
Source

Instance methods

analyze
Source
analyze_file(path : String) : Array(Endpoint)
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

Nested types