Documentation
¶
Overview ¶
Package scanner implements the read-only HTTP scan engine: WordPress detection, plugin/theme enumeration via readme.txt/style.css/wp-json, and version comparison against an indexed local database.
Index ¶
- Variables
- func ExtractCoreVersionFromAssets(html string) (version string, found bool)
- func ExtractCoreVersionFromReadmeHTML(body string) (string, bool)
- func ExtractOPMLVersion(body string) (version string, found bool)
- func ExtractPassiveSlugs(html string) (plugins, themes []string)
- func ExtractPassiveSlugsIn(html, contentDir string) (plugins, themes []string)
- func ExtractPassiveVersions(html string) map[string]string
- func ExtractPassiveVersionsIn(html, contentDir string) map[string]string
- func ExtractRESTRoutePlugins(body []byte) []string
- func ExtractRSSVersion(body string) (version string, found bool)
- func ExtractRequiresAtLeast(body string) string
- func ExtractTestedUpTo(body string) string
- func ExtractTimthumbVersion(body string) (string, bool)
- func ExtractVersionFromChangelog(body string) (string, bool)
- func ExtractVersionFromReadme(body string) (version string, found bool)
- func ExtractVersionFromStyleCSS(body string) (version string, found bool)
- func ExtractVersionFromVersionPHP(body string) (string, bool)
- func ExtractWordPressVersion(html string) (version string, found bool)
- type CoreEvidence
- type Detected
- type Finding
- type LoginBrute
- type Options
- type Result
- type Scanner
- type Summary
- type User
- type Vulnerability
Constants ¶
This section is empty.
Variables ¶
var ErrBlocked = errors.New("blocked by WAF or error page")
ErrBlocked is returned when the homepage HTML matches --exclude-content-based (WAF or error page).
var ErrNotWordPress = errors.New("target does not appear to be a WordPress site")
ErrNotWordPress is returned when a scan target shows no WordPress signs.
var ErrOutOfScope = errors.New("target out of scope")
ErrOutOfScope is returned when --scope does not match the target URL.
var ErrRedirectBlocked = errors.New("redirect blocked")
ErrRedirectBlocked is wrapped by the redirect guard when a Location would cross the scanned target's host:port authority or exceed the hop limit. It is not a transport failure, so it is never retried by sendRequest.
Functions ¶
func ExtractCoreVersionFromAssets ¶ added in v0.6.0
ExtractCoreVersionFromAssets parses the WordPress core version from the ?ver= query string on core-released asset references (see coreAssetVerRe). found is false when no such asset reference carries a version.
func ExtractCoreVersionFromReadmeHTML ¶ added in v0.9.0
ExtractCoreVersionFromReadmeHTML parses the WordPress core version out of a served readme.html document: the canonical <h1 id="version"> heading first, then the looser ">Version X.Y.Z<" element form used by older releases. The candidate is sanitized via sanitizeVersion (control characters stripped, capped at maxVersionLen runes). found is false when neither marker matches or the sanitized result is empty.
func ExtractOPMLVersion ¶ added in v0.3.0
ExtractOPMLVersion parses the WordPress core version from the generator attribute of a wp-links-opml.php document (generator="WordPress/X.Y"). found is false when absent.
func ExtractPassiveSlugs ¶ added in v0.2.0
ExtractPassiveSlugs parses plugin and theme slugs from HTML the way WPScan does passive detection: any reference to wp-content/plugins/<slug>/ or wp-content/themes/<slug>/ counts as evidence the component is installed. Returns deduplicated, sorted plugin slugs and theme slugs.
func ExtractPassiveSlugsIn ¶ added in v0.2.0
ExtractPassiveSlugsIn is ExtractPassiveSlugs with a custom wp-content directory name: references to <contentDir>/plugins/<slug>/ or <contentDir>/themes/<slug>/ count as evidence the component is installed. Returns deduplicated, sorted plugin slugs and theme slugs.
func ExtractPassiveVersions ¶ added in v0.3.0
ExtractPassiveVersions parses plugin and theme versions from asset URLs in HTML: any wp-content/plugins/<slug>/...?ver=1.2.3 or wp-content/themes/<slug>/...?ver=1.2.3 reference counts as evidence of the installed version. The result maps slug to version; when a slug is referenced with several versions the first one in document order wins. The map is capped at maxPassiveVersions entries.
func ExtractPassiveVersionsIn ¶ added in v0.3.0
ExtractPassiveVersionsIn is ExtractPassiveVersions with a custom wp-content directory name, mirroring ExtractPassiveSlugsIn.
func ExtractRESTRoutePlugins ¶ added in v0.8.0
ExtractRESTRoutePlugins parses the WordPress REST API root index (/wp-json/) for plugin namespaces: when the body parses as {"routes": {...}} the route keys are the candidate list, and a plain array of route strings is accepted too. Each route key is reduced to its first namespace segment (see restRouteSlug); segments matching the exact known core prefixes (wp, oembed, wp-site-health, wp/block-directory) are dropped and anything not matching ^[a-z0-9_-]+$ or longer than maxRESTRoutePlugins runes is rejected. The surviving slugs are deduplicated, sorted and capped at maxRESTRoutePlugins entries. A body that parses as neither shape (or carries no routes at all) returns nil.
func ExtractRSSVersion ¶ added in v0.3.0
ExtractRSSVersion parses the WordPress core version from an RSS feed generator element in its URL form (<generator>https://wordpress.org/?v=X.Y.Z</generator>). found is false when absent.
func ExtractRequiresAtLeast ¶ added in v0.5.0
ExtractRequiresAtLeast parses the "Requires at least:" header line of a WordPress readme.txt or theme style.css and returns the minimum WordPress version required, sanitized. It returns "" when the header is absent. The match is case-insensitive, tolerates trailing spaces (and the "Requires at least = Y" separator spelling), and never spans lines.
func ExtractTestedUpTo ¶ added in v0.5.0
ExtractTestedUpTo parses the "Tested up to:" header line of a WordPress readme.txt (or theme style.css header) and returns the WordPress version the component was tested against, sanitized. It returns "" when the header is absent. The match is case-insensitive, tolerates trailing spaces (and the historical "Tested up to = X" separator spelling), and never spans lines.
func ExtractTimthumbVersion ¶ added in v0.8.0
ExtractTimthumbVersion parses the version out of a TimThumb source or info body using the common release markers (see timthumbVersionRe). The first marker present wins and the result is sanitized via sanitizeVersion (control characters stripped, capped at maxVersionLen runes). found is false when the body carries no recognizable marker.
func ExtractVersionFromChangelog ¶ added in v0.4.0
ExtractVersionFromChangelog parses the first version heading inside a readme.txt Changelog section ("== Changelog ==" or "## Changelog"): the classic "= X.Y.Z =" heading, the markdown "### X.Y.Z" heading, or a "X.Y.Z - ..." first line. The candidate is sanitized and must parse as a numeric version (internal/version.Parse). found is false when there is no Changelog section or no parseable heading — readmes whose only version is buried in the changelog (no "Stable tag:" line) still get detected.
func ExtractVersionFromReadme ¶
ExtractVersionFromReadme parses the "Stable tag:" line of a WordPress plugin readme.txt and returns the version string. found is false when no parseable stable tag exists.
func ExtractVersionFromStyleCSS ¶
ExtractVersionFromStyleCSS parses the "Version:" header of a WordPress theme style.css and returns the version string. found is false when no parseable version exists.
func ExtractVersionFromVersionPHP ¶ added in v1.1.0
ExtractVersionFromVersionPHP parses the WordPress core version from a wp-includes/version.php source body (the $wp_version assignment). found is false when the assignment is absent.
func ExtractWordPressVersion ¶
ExtractWordPressVersion parses the WordPress version from the generator meta tag in the homepage HTML. found is false when absent.
Types ¶
type CoreEvidence ¶ added in v0.3.0
type CoreEvidence struct {
Source string `json:"source"`
Version string `json:"version"`
Confidence int `json:"confidence,omitempty"`
}
CoreEvidence records one WordPress core version observation together with the source that produced it: "meta" (generator meta tag), "rss" (feed generator element), "opml" (wp-links-opml.php generator attribute), "asset-ver" (core-released asset ?ver= cache-buster), "readme-html" (readme.html "Version X.Y.Z" heading) or "fingerprint" (core asset md5 table). Confidence carries the same 0..100 reliability score used by Detected.
type Detected ¶
type Detected struct {
Slug string `json:"slug"`
Name string `json:"name"`
Type string `json:"type"`
Version string `json:"installed_version"`
Source string `json:"source,omitempty"`
Confidence int `json:"confidence,omitempty"`
// TestedUpTo and RequiresAtLeast are readme.txt / style.css header
// metadata ("Tested up to:" / "Requires at least:" lines) captured for
// reporting; both are omitted when the artifact carries none.
TestedUpTo string `json:"tested_up_to,omitempty"`
RequiresAtLeast string `json:"requires_at_least,omitempty"`
// ActiveInstalls is the active-install estimate for this slug from the
// --popular-file counts maps (counts_plugins / counts_themes, by Type),
// capped at maxActiveInstalls. Omitted (0) when the file carried no
// count for the slug; the built-in popular lists never carry counts.
ActiveInstalls int `json:"active_installs,omitempty"`
}
Detected is a plugin/theme/core component whose presence and version were identified on the target. Source records how it was found: "passive" (slug referenced in page HTML), "passive-ver" (asset ?ver= query string), "readme" (plugin readme.txt "Stable tag:" probe), "readme-changelog" (plugin readme.txt Changelog section heading), "composer" (plugin composer.json "version" field), "style.css" (theme stylesheet probe), "rest" (unauthenticated wp-json listing), "rest-routes" (plugin namespace from the wp-json route index), "auth-rest" (authenticated wp-json inventory) or "fingerprint" (core asset md5 table). Confidence is the 0..100 reliability estimate assigned to the detection source (see sourceConfidence); it is omitted from JSON output when zero.
type Finding ¶
type Finding struct {
Slug string `json:"slug"`
Name string `json:"name"`
Type string `json:"type"`
InstalledVersion string `json:"installed_version"`
Vulnerabilities []Vulnerability `json:"vulnerabilities"`
}
Finding links an installed component to its matching vulnerabilities.
type LoginBrute ¶ added in v0.3.0
type LoginBrute struct {
User string `json:"user"`
Password string `json:"password"`
URL string `json:"url"`
}
LoginBrute is a credential pair that successfully authenticated against the target, either through wp-login.php or the XML-RPC endpoint.
type Options ¶
type Options struct {
Threads int // concurrent HTTP requests (default 5)
Timeout time.Duration // per-request timeout (default 10s, alias for RequestTimeout)
Stealth bool // throttle to 1 request/second
RateLimit float64 // max requests per second (0 = unlimited)
APIOnly bool // skip brute-force enumeration, only wp-json/plugins
MaxRequests int // cap on brute-force enumeration requests (default 500)
Enumerate string // what to enumerate: u/p/t, combinable (default "pt")
UserAgent string // custom User-Agent for all requests
RandomUA bool // pick a random browser User-Agent per request
BasicAuthUser string // HTTP Basic auth username sent on every request (--basic-auth USER:PASS)
BasicAuthPass string // HTTP Basic auth password sent on every request
Cookie string // static Cookie header sent on every request (--cookie)
Headers map[string]string // extra request headers sent on every request (--headers k=v,..)
VHost string // Host header override for every request (--vhost)
Force bool // --force: scan on even without WordPress fingerprints
ExcludeVulns []string // --exclude-vulns: vulnerability IDs to skip entirely (case-sensitive)
DetectionMode string // passive (homepage only), aggressive (DB only), mixed (default)
Proxy string // http://, https://, socks5:// or socks5h:// proxy URL
ProxyAuth string // --proxy-auth USER:PASS for SOCKS5 proxies (RFC 1929)
ProxyTargetOnly bool // --proxy-target-only: use the proxy only for target-host traffic
TLSFingerprint string // --tls-fingerprint: chrome | firefox | random (TLSClientConfig variations)
PerHostRateLimit float64 // --per-host-rate-limit N: per-host requests per second (0 = off)
NoXMLRPC bool // skip the XML-RPC (xmlrpc.php) ping check
Checks string // extra checks: cb (config backups), dbe (db exports), comma-separated
ConnectTimeout time.Duration // TCP dial timeout (default 10s)
RequestTimeout time.Duration // per-request timeout (default 10s)
ContentDir string // wp-content directory (default "wp-content")
PluginsDir string // plugins directory (default "wp-content/plugins")
ExcludeContentBased string // regex; matching homepage HTML aborts the scan
Scope string // regex; a non-matching target URL is out of scope
PluginsList string // file with plugin slugs (one per line, # comments)
ThemesList string // file with theme slugs (one per line, # comments)
MaxScanDuration time.Duration // hard stop for the whole scan; 0 = unlimited
CacheTTL time.Duration // HTTP response cache TTL; 0 = off
CrawlPages int // --crawl-pages N: passively crawl up to N sitemap pages (0 = disabled)
Findings chan Finding // when set, every finding is emitted live
PasswordsFile string // --passwords FILE: wordlist for the wp-login brute force (one per line)
UsernamesFile string // --usernames FILE: wordlist for brute-force attacks (one per line)
User string // --user USER: single username for the XML-RPC multicall attack
XMLRPCBrute string // --xmlrpc-brute FILE: wordlist for the XML-RPC multicall attack
MCPerRequest int // --multicall-max-passwords N: passwords per multicall request (default 3)
WPAuth string // --wp-auth USER:PASS: Basic auth for the REST inventory
NoBrute bool // --no-brute: disable credential brute force (login + XML-RPC)
NoSummary bool // --no-summary: skip gathering scan summary statistics
// FingerprintDB is an optional path to a JSON core-fingerprint table
// ("{"files": {path: {md5hex: [versions]}}}") used as a final core
// version fallback when meta/RSS/OPML sources all fail. A missing or
// unparseable table is silently skipped. Disabled when empty.
// Context, when set, overrides the scan-wide context used by
// requestCtx() (signal-based cancellation etc.). MaxScanDuration
// takes precedence when both are set.
Context context.Context
FingerprintDB string
// CoreVersionOverride pins the reported WordPress core version to the
// --wp-version value, skipping the whole version-detection chain
// (generator meta, RSS/OPML generators, core asset ?ver= and the
// fingerprint table) while still fetching the homepage normally for
// passive evidence. Empty when unused.
CoreVersionOverride string
// InsecureTLS disables TLS certificate verification (--disable-tls-
// checks; scanners behind MITM proxies).
InsecureTLS bool
// MediaIDs caps attachment-ID probing for the "m" enumerate token;
// 0 keeps the legacy homepage-presence check only.
MediaIDs int
// PopularSlugs appends the static popular plugin/theme slug seed lists
// (popular.go) to aggressive enumeration after the DB top-slug list.
// The CLI flag defaults it to true; building Options directly leaves
// the zero value (false), which keeps the seed lists off.
PopularSlugs bool
// PopularThemes appends the static popular theme slug seed list
// (popular.go) to aggressive enumeration after the DB top-slug list,
// independently of PopularSlugs so the CLI can map the WPScan-style
// enumerate tokens onto them ("t" enables popular themes, "vt"/"at"
// keep only the vuln-heavy DB themes). The zero value (false) keeps
// the theme seeds off, mirroring PopularSlugs.
PopularThemes bool
// AllowForeignRedirect allows following HTTP redirects whose target
// host:port differs from the scanned target's authority. The default
// (false) blocks foreign redirects as SSRF hardening, surfacing them as
// fetch errors.
AllowForeignRedirect bool
// MaxRetries is how many times a transient transport error (a non-HTTP
// failure from the transport layer) is retried with exponential backoff
// plus jitter before giving up. The CLI flag defaults it to 2; 0
// disables retries entirely.
MaxRetries int
// Discover404 probes ONE deliberately nonexistent path
// (/<contentDir>-404-check-<random>/) after the homepage fetch and
// treats any wp-content references in its 200 body as passive
// plugin/theme evidence, WPScan's urls_in_404_page parity. The zero
// value (off) preserves the historical request budget; the CLI defaults
// it to true.
Discover404 bool
// PopularFile is an optional path to a JSON popular-list file
// ({"plugins":["slug",...],"themes":["slug",...], optionally
// "counts_plugins":{"slug":N} and "counts_themes":{"slug":N}}) that
// replaces the built-in popular.go seed lists for aggressive
// enumeration. The counts maps decorate detected components with
// active-install estimates (capped at maxActiveInstalls). A missing
// or unparseable file falls back to the built-ins with a one-time
// warning, never an error. Empty when unused.
PopularFile string
// PluginsThreshold aborts enumeration with a warning when at least
// this many plugins are found (0 = disabled, matching WPScan's
// --plugins-threshold default 100; WPScan errors, onyx warns so the
// scan still reports partial results). ThemesThreshold is the same
// for themes (WPScan default 20).
PluginsThreshold int
ThemesThreshold int
}
Options tunes the scan behaviour. Zero values fall back to defaults.
type Result ¶
type Result struct {
Target string `json:"target"`
IsWordPress bool `json:"is_wordpress"`
WordPressVersion string `json:"wordpress_version,omitempty"`
CoreEvidence []CoreEvidence `json:"core_evidence,omitempty"` // which source produced WordPressVersion
Evidence []string `json:"evidence,omitempty"`
Detected []Detected `json:"detected,omitempty"`
Findings []Finding `json:"findings,omitempty"`
Nuclei []nuclei.NucleiResult `json:"nuclei,omitempty"`
PoCs []pocs.PoCLink `json:"pocs,omitempty"`
Users []User `json:"users,omitempty"`
XMLRPC bool `json:"xmlrpc,omitempty"` // xmlrpc.php ping answered
XMLRPCPingback bool `json:"xmlrpc_pingback,omitempty"` // pingback.ping exposed (SSRF amplification)
// XMLRPCMethods lists the methods advertised by system.listMethods,
// capped at 20 entries; nil when XML-RPC is disabled or the list
// could not be parsed.
XMLRPCMethods []string `json:"xmlrpc_methods,omitempty"`
Interesting []string `json:"interesting,omitempty"`
ConfigBackups []string `json:"config_backups,omitempty"`
DBExports []string `json:"db_exports,omitempty"`
RateLimitHits int `json:"rate_limit_hits,omitempty"` // 429s seen
TimedOut bool `json:"timed_out,omitempty"` // --max-scan-duration expired
RateLimitedAbort bool `json:"rate_limited_abort,omitempty"` // enumeration stopped: target kept answering 429
Errors []string `json:"errors,omitempty"`
LoginBrutes []LoginBrute `json:"login_brutes,omitempty"` // valid credentials found by brute force
AuthStatus string `json:"auth_status,omitempty"` // --wp-auth: authenticated | failed | ""
Summary *Summary `json:"summary,omitempty"` // scan statistics; nil with --no-summary
// ScannedAt records when the scan finished (UTC); added in schema 1.0
// so saved results carry their own timestamp.
ScannedAt time.Time `json:"scanned_at"`
// SchemaVersion is the version of the Result JSON shape, so CI
// consumers can detect breaking output changes. "1.0" is the first
// versioned shape (onyx 0.5.0+); always present.
SchemaVersion string `json:"schema_version"`
}
Result is the output of a scan.
type Scanner ¶
type Scanner struct {
// contains filtered or unexported fields
}
Scanner drives one scan against a single target.
func NewScanner ¶
NewScanner builds a Scanner for base, using the given database and options.
func (*Scanner) SetProgress ¶ added in v0.2.0
SetProgress attaches a progress bar to the scanner. A nil bar disables progress reporting entirely.
type Summary ¶ added in v0.3.0
type Summary struct {
DurationMS int64 `json:"duration_ms"`
Requests int `json:"requests"`
RateLimited int `json:"rate_limited"`
Detected int `json:"detected"`
Findings int `json:"findings"`
Critical int `json:"critical"`
High int `json:"high"`
Medium int `json:"medium"`
Low int `json:"low"`
Users int `json:"users"`
}
Summary holds scan-wide statistics, computed at the end of Scan() (skipped with --no-summary). Severity counts come from the findings.
type User ¶ added in v0.2.0
User is a WordPress user account discovered during enumeration. ID is the numeric user id (when known), Slug is the author-archive slug, Name is the display name (only the REST API provides it).
type Vulnerability ¶
type Vulnerability struct {
ID string `json:"id"`
Title string `json:"title"`
CVE string `json:"cve"`
CVSSScore float64 `json:"cvss_score"`
Rating string `json:"cvss_rating"`
Description string `json:"description"`
AffectedLabels []string `json:"affected_versions"`
PublishedAt string `json:"published_at"`
// Epss is the EXPloit Prediction Scoring System probability (0..1)
// attached by the intel enrichment step; omitted when unknown.
Epss float64 `json:"epss,omitempty"`
// Kev reports whether the CVE is listed in the CISA Known Exploited
// Vulnerabilities catalog; omitted when false.
Kev bool `json:"kev,omitempty"`
// CVSSVector is the raw CVSS vector string from the feed record
// (e.g. "CVSS:3.1/AV:N/AC:L/..."); omitted when unknown.
CVSSVector string `json:"cvss_vector,omitempty"`
// Remediation is the human-readable fix guidance from the feed
// (e.g. "Update the plugin to version X or newer"); omitted when
// the record carries none.
Remediation string `json:"remediation,omitempty"`
// PatchedVersions lists the feed's known-fixed versions for this
// software entry; omitted when unknown.
PatchedVersions []string `json:"patched_versions,omitempty"`
}
Vulnerability is one matched database record.