t3finger

package
v0.0.0-...-b6f5494 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 7, 2026 License: MIT Imports: 24 Imported by: 0

Documentation

Overview

Package t3finger fingerprints a TYPO3 CMS website without authentication: it enumerates the installed extensions ("plugins") by abusing the deterministic Composer-mode asset path, and detects the core version by hashing the static assets TYPO3 ships and matching them against a database built from official releases.

Everything here is reachable pre-auth over plain HTTP and is intended for authorized security testing and asset inventory only.

Index

Constants

This section is empty.

Variables

View Source
var DefaultProbes = []string{

	"typo3/sysext/backend/Resources/Public/Css/backend.css",

	"typo3/sysext/core/Resources/Public/Icons/T3Icons/icons.json",

	"typo3/sysext/rte_ckeditor/Resources/Public/Contrib/@ckeditor/ckeditor5-core.js",
	"typo3/sysext/rte_ckeditor/Resources/Public/Contrib/ckeditor5-bundle.js",

	"typo3/sysext/backend/Resources/Public/JavaScript/date-time-picker.js",
	"typo3/sysext/backend/Resources/Public/JavaScript/modal.js",
	"typo3/sysext/backend/Resources/Public/JavaScript/module-menu.js",
	"typo3/sysext/backend/Resources/Public/JavaScript/Modal.js",
	"typo3/sysext/backend/Resources/Public/JavaScript/ModuleMenu.js",
	"typo3/sysext/backend/Resources/Public/JavaScript/FormEngine.js",
	"typo3/sysext/backend/Resources/Public/JavaScript/LoginRefresh.js",

	"typo3/sysext/core/Resources/Public/JavaScript/Contrib/es-module-shims.js",

	"typo3/sysext/rtehtmlarea/htmlarea/htmlarea.js",
	"typo3/sysext/install/Resources/Public/JavaScript/Install.js",
	"typo3/sysext/t3skin/stylesheets/visual/element_message.css",

	"typo3/cli_dispatch.phpsh",
}

DefaultProbes is the curated set of publicly-served static core files we hash for version detection in LEGACY (non-composer) installs. Paths are relative to the TYPO3 web root (i.e. what a browser requests). They were chosen for two properties: they exist across many releases, and their content changes between releases (often per-patch), so their md5 pins the version tightly.

Location naming changed across majors (CamelCase JS in ≤11, kebab-case ES modules in 12+), so both spellings are listed; a 404 on the wrong one is harmless. The engine also auto-probes any additional discriminating path the embedded DB knows about, so a fuller DB extends coverage without code changes.

View Source
var HashFull = false

HashFull, when set on a build, hashes the entire Resources/Public tree (deep). Off by default: only depth-1 public files are hashed, which keeps the embedded DB compact while retaining the high-signal, per-patch-changing files (backend.css, top-level backend JS, icons.json, the ckeditor bundle).

Functions

func AssetURL

func AssetURL(base, pkg string) string

AssetURL returns the deterministic /_assets/<md5>/ URL for a package. A bare Packagist name ("vendor/pkg") is wrapped as "/vendor/<name>/"; a value that already starts with "/" is hashed verbatim (with an enforced trailing slash).

func BranchOf

func BranchOf(version string) string

BranchOf returns the "major.minor" branch of a version (e.g. "13.4.33" -> "13.4").

func CompareVersions

func CompareVersions(a, b string) int

CompareVersions compares "a.b.c" strings numerically. Missing parts = 0.

func DefaultExtensionKeys

func DefaultExtensionKeys() []string

DefaultExtensionKeys returns the bundled TER extension keys (folder names), used for legacy-mode /typo3conf/ext/<key>/ enumeration. Refresh it with `t3scan buildwordlist -keys`.

func DefaultExtensionList

func DefaultExtensionList() []string

DefaultExtensionList returns the bundled Packagist TYPO3-extension names (vendor/package), used for Composer-mode /_assets/ enumeration. Refresh it with `t3scan buildwordlist`.

func FetchAdvisoriesFor

func FetchAdvisoriesFor(ctx context.Context, packages []string) (map[string]*AdvisoryDB, error)

FetchAdvisoriesFor queries the Packagist advisory feed for the given packages and returns advisoryDBs keyed by package name. Used to map enumerated extensions (with a composer name + version) to their known CVEs.

func FetchExtensionKeys

func FetchExtensionKeys(ctx context.Context) ([]string, error)

FetchExtensionKeys downloads the TER catalogue and returns every unique extension key. Used by `t3scan buildwordlist -keys` and for legacy-mode enumeration, where extensions live at /typo3conf/ext/<key>/ rather than the Composer-mode /_assets/<md5>/ path.

func FetchExtensionList

func FetchExtensionList(ctx context.Context) ([]string, error)

FetchExtensionList downloads the complete, current list of TYPO3 extension package names from Packagist. Used by `t3scan buildwordlist` to refresh the bundled default.

func LoadExtensionKeys

func LoadExtensionKeys(path string) ([]string, error)

LoadExtensionKeys reads extension keys from a file, or returns the embedded default when path is empty.

func LoadExtensionList

func LoadExtensionList(path string) ([]string, error)

LoadExtensionList reads candidate packages from a file (one per line, '#' comments allowed). If path is empty, the embedded default list is returned.

func SeedExtensionKeys

func SeedExtensionKeys() []string

SeedExtensionKeys returns the curated seed list of common extension keys used by `t3scan buildextdb` to build the default extension-probe DB.

func SortBySeverity

func SortBySeverity(advs []Advisory)

SortBySeverity orders advisories most-severe first, newest ID breaking ties.

Types

type Advisory

type Advisory struct {
	ID       string `json:"id"`       // TYPO3-CORE-SA-YYYY-NNN
	CVE      string `json:"cve"`      // CVE-YYYY-NNNNN (may be empty)
	Title    string `json:"title"`    //
	Affected string `json:"affected"` // composer constraint, e.g. ">=11.0.0,<11.5.51|>=12.0.0,<12.4.46"
	Link     string `json:"link"`     //
	Severity string `json:"severity"` // may be empty
}

Advisory is one published TYPO3 core security advisory.

func (Advisory) Affects

func (a Advisory) Affects(v string) bool

Affects reports whether version v falls within this advisory's affected range.

type AdvisoryDB

type AdvisoryDB struct {
	Package    string     `json:"package"`
	Advisories []Advisory `json:"advisories"`
}

AdvisoryDB is the embedded set of core advisories.

func FetchAdvisories

func FetchAdvisories(ctx context.Context) (*AdvisoryDB, error)

FetchAdvisories downloads the current TYPO3 core advisories from Packagist. Used by `t3scan buildadvisories` to refresh the embedded set.

func LoadAdvisories

func LoadAdvisories() *AdvisoryDB

LoadAdvisories returns the advisories compiled into the binary.

func (*AdvisoryDB) For

func (db *AdvisoryDB) For(v string) []Advisory

For returns every advisory that affects version v, most severe first.

type Baseline

type Baseline struct {
	Status int  `json:"status"`
	Size   int  `json:"size"`
	OK     bool `json:"ok"`
}

Baseline records what "not installed" looks like, calibrated per target instead of assuming 404 (some setups answer differently).

type Builder

type Builder struct {
	Concurrency int
	Progress    func(string)
	HTTP        *http.Client
}

Builder constructs a version-fingerprint DB by downloading official TYPO3 release tarballs and hashing the static files they serve.

func NewBuilder

func NewBuilder() *Builder

NewBuilder returns a Builder with sane defaults.

func (*Builder) Build

func (b *Builder) Build(ctx context.Context, versions []string, stamp string) (*DB, error)

Build downloads each version, hashes its public static files, and assembles a DB. Files whose content is identical across every version are pruned (they carry no signal); everything else is kept for path-based (legacy) and content-based (composer) matching.

func (*Builder) ListReleases

func (b *Builder) ListReleases(ctx context.Context) ([]string, error)

ListReleases returns every public (non-ELTS) release version, newest first is not guaranteed — callers should sort. ELTS releases are gated behind a paywall and cannot be downloaded, so they are skipped.

type CatMeta

type CatMeta struct {
	Author string // authorname (fall back to authorcompany)
	Owner  string // ownerusername — the TER account that owns the key
}

CatMeta is the catalogue metadata for one extension key (identity that lives in the TER index itself, so it is fetched without any per-version download).

type DB

type DB struct {
	// Files: servedPath -> md5 -> []version
	Files map[string]map[string][]string `json:"files"`
	// Versions is the full ordered list of versions covered by the DB.
	Versions []string `json:"versions"`
	// BuiltAt is a free-form build stamp (set by `t3scan builddb`).
	BuiltAt string `json:"builtAt,omitempty"`
	// contains filtered or unexported fields
}

DB is the version-fingerprint database. For each publicly-served asset path, it maps the file's md5 hex digest to the list of TYPO3 core versions that ship that exact byte content. Files whose content varies across releases are the discriminators; identical-everywhere files still confirm a stock install.

func LoadEmbedded

func LoadEmbedded() (*DB, error)

LoadEmbedded returns the database compiled into the binary.

func Parse

func Parse(b []byte) (*DB, error)

Parse loads a DB from JSON bytes.

func (*DB) AllPaths

func (d *DB) AllPaths() []string

AllPaths returns every served path known to the DB, sorted.

func (*DB) DiscriminatingPaths

func (d *DB) DiscriminatingPaths() []string

DiscriminatingPaths returns served paths whose content varies across versions (more than one hash bucket) — the files worth probing because they narrow the version. Sorted for determinism.

func (*DB) DiscriminatingPathsRanked

func (d *DB) DiscriminatingPathsRanked() []string

DiscriminatingPathsRanked returns discriminating paths ordered by how much they narrow the version (most hash-buckets first), so a bounded probe budget spends on the files that actually change most per patch — not an alphabetical slice. Ties broken by shortest path (stable top-level assets).

func (*DB) DiscriminatingUnderRanked

func (d *DB) DiscriminatingUnderRanked(prefix string) []string

DiscriminatingUnderRanked returns the discriminating paths that start with prefix (a sysext path like "typo3/sysext/backend/"), ranked by how much each narrows the version — for active probing of one package's files.

func (*DB) Empty

func (d *DB) Empty() bool

Empty reports whether the DB has no fingerprints.

func (*DB) Has

func (d *DB) Has(v string) bool

Has reports whether the DB already covers version v.

func (*DB) Merge

func (d *DB) Merge(src *DB)

Merge folds src into d: every (path, md5) → version mapping from src is added, version lists are de-duplicated and sorted, and the covered-version set is unioned. Used for incremental DB builds.

func (*DB) Newest

func (d *DB) Newest() string

Newest returns the highest version the DB covers, or "".

func (*DB) PresenceDiscriminatingPaths

func (d *DB) PresenceDiscriminatingPaths() []string

PresenceDiscriminatingPaths returns paths that exist in only SOME covered versions (added or removed across releases) — their mere presence/absence bounds the version even when their content never varies. These complement the content-discriminating paths for add/remove-boundary narrowing.

func (*DB) VersionsForAnyHash

func (d *DB) VersionsForAnyHash(md5hex string) []string

VersionsForAnyHash returns versions that ship a file — at ANY path — whose content md5 equals md5hex. Used when the request path can't be mapped to a DB path (composer mode). Unexported cache is built on first use.

func (*DB) VersionsForHash

func (d *DB) VersionsForHash(servedPath, md5hex string) []string

VersionsForHash returns the versions matching a (servedPath, md5) pair.

func (*DB) VersionsHavingFile

func (d *DB) VersionsHavingFile(servedPath string) []string

VersionsHavingFile returns the union of all versions in which servedPath exists (any content). Used for presence-based narrowing.

type ExtBuilder

type ExtBuilder struct {
	Concurrency int
	MaxVersions int // most-recent versions to hash per extension (0 = all)
	Progress    func(string)
	HTTP        *http.Client
}

ExtBuilder builds the extension DB by downloading real extension packages from the TYPO3 Extension Repository (TER) and recording, per extension: its dependencies, presence-probe files, and the content hash of every web-servable static file across versions (the per-version diff used to fingerprint the installed version of a plugin/theme).

func NewExtBuilder

func NewExtBuilder() *ExtBuilder

NewExtBuilder returns an ExtBuilder with sane defaults. The HTTP client uses a large idle-connection pool so high -c concurrency reuses keep-alive connections instead of churning a new TCP+TLS handshake per download.

func (*ExtBuilder) BuildExtProbeDB

func (b *ExtBuilder) BuildExtProbeDB(ctx context.Context, keys []string, stamp string) (*ExtProbeDB, error)

BuildExtProbeDB builds full records for the given extension keys.

func (*ExtBuilder) CatalogueAuthors

func (b *ExtBuilder) CatalogueAuthors(ctx context.Context) (map[string]CatMeta, error)

CatalogueAuthors downloads the TER index and returns key -> author/owner meta. Used to enrich an existing DB without re-downloading any package.

func (*ExtBuilder) UpdateExtDB

func (b *ExtBuilder) UpdateExtDB(ctx context.Context, existing *ExtProbeDB, keys []string, stamp string) (*ExtProbeDB, UpdateStats, error)

UpdateExtDB brings an extension DB up to date with the live TER catalogue, downloading ONLY the (extension, version) pairs it does not already have:

  • a plugin with a new release -> just its new versions are fetched & merged
  • a brand-new plugin -> built from scratch
  • an unchanged plugin -> skipped entirely (zero downloads)

Pass existing == nil (or an empty DB) for a full build. The returned DB is the UNPRUNED working "raw" DB (every hashed public file, so the next update stays correct); call PruneForEmbed on it to get the compact DB embedded in the binary. New CVEs are orthogonal — they live in the advisory DB and are refreshed with buildadvisories, not here.

Downloads run through a SINGLE global worker pool over every (extension, version) pair, so Concurrency requests are always in flight regardless of how the versions are distributed across extensions. This matters because each TER zip is tiny but costs a full request round-trip: the build is latency-bound, so flat global concurrency — not per-extension goroutines — is what makes it fast and removes the long tail of version-heavy plugins.

type ExtEntry

type ExtEntry struct {
	Composer string                         `json:"composer,omitempty"`
	Author   string                         `json:"author,omitempty"` // authorname from the TER index
	Owner    string                         `json:"owner,omitempty"`  // ownerusername (TER account owning the key)
	Latest   string                         `json:"latest,omitempty"`
	Versions []string                       `json:"versions,omitempty"`
	Requires map[string]string              `json:"requires,omitempty"`
	Probes   []string                       `json:"probes"`
	Files    map[string]map[string][]string `json:"files,omitempty"`
	// contains filtered or unexported fields
}

ExtEntry is the full record for one extension (a plugin, a theme/sitepackage, or any TER package — themes are just extensions):

  • Composer/Latest/Versions: identity and coverage.
  • Requires: its declared dependencies (from composer.json).
  • Probes: web-servable files that prove it is installed (presence).
  • Files: for each web-servable path, md5 -> versions that ship that exact content — the per-version static-file diff used to pin the INSTALLED version of the extension by hashing what the target serves.

func (*ExtEntry) AnyHash

func (e *ExtEntry) AnyHash(md5hex string) []string

AnyHash returns versions of this extension shipping a file (any path) with the given content md5 — for when the served path can't be mapped exactly.

func (*ExtEntry) DiscriminatingFiles

func (e *ExtEntry) DiscriminatingFiles() []string

DiscriminatingFiles returns the paths whose content varies across versions — the files worth hashing on a live target to pin the extension's version.

func (*ExtEntry) DiscriminatingFilesRanked

func (e *ExtEntry) DiscriminatingFilesRanked() []string

DiscriminatingFilesRanked returns discriminating paths ordered by how much they narrow the version — most hash-buckets (changes most often) first, so probing a few pins the version tightly. Ties broken by shortest path (stable, usually top-level assets that are reliably served).

func (*ExtEntry) VersionForHash

func (e *ExtEntry) VersionForHash(path, md5hex string) []string

VersionForHash returns the versions of extension `key` whose file `path` has content md5 == md5hex (exact per-version static-file match).

type ExtProbe

type ExtProbe = ExtEntry

ExtProbe is the legacy alias kept for the builder's simpler output; the rich entry supersedes it. (Files/Requires may be empty for breadth-only builds.)

type ExtProbeDB

type ExtProbeDB struct {
	BuiltAt    string              `json:"builtAt,omitempty"`
	Extensions map[string]ExtEntry `json:"extensions"`
	// contains filtered or unexported fields
}

ExtProbeDB maps extension key -> its full record.

func LoadExtProbeDB

func LoadExtProbeDB() *ExtProbeDB

LoadExtProbeDB returns the embedded extension DB (decompressing it).

func LoadExtProbeDBFile

func LoadExtProbeDBFile(path string) (*ExtProbeDB, error)

LoadExtProbeDBFile loads a DB from a file path (json or json.gz).

func ParseExtProbeDB

func ParseExtProbeDB(b []byte) (*ExtProbeDB, error)

ParseExtProbeDB reads a DB from JSON or gzipped-JSON bytes.

func PruneForEmbed

func PruneForEmbed(raw *ExtProbeDB) *ExtProbeDB

PruneForEmbed returns a compact copy of a raw DB with version-invariant files dropped — the form embedded in the binary. The input is left untouched.

func (*ExtProbeDB) ByComposer

func (d *ExtProbeDB) ByComposer(name string) *ExtEntry

ByComposer returns the entry for a Packagist name (vendor/pkg), or nil. Used to version-fingerprint a composer-mode extension whose key we don't have.

func (*ExtProbeDB) CandidatesForComposer

func (d *ExtProbeDB) CandidatesForComposer(name string) []string

CandidatesForComposer returns every extension key that declares the given composer name — more than one when forks/dummies squat the same name. Ordered canonical-first (most versions). Empty if the name is unknown.

func (*ExtProbeDB) ComposerForAssetHash

func (d *ExtProbeDB) ComposerForAssetHash(md5hex string) string

ComposerForAssetHash returns the composer name behind a /_assets/<md5>/ hash.

func (*ExtProbeDB) Empty

func (d *ExtProbeDB) Empty() bool

Empty reports whether the DB has no entries.

func (*ExtProbeDB) IdentifyAssetHash

func (d *ExtProbeDB) IdentifyAssetHash(md5hex string) (key, composer string, ok bool)

IdentifyAssetHash reverses a composer-mode /_assets/<md5>/ directory hash back to the installed extension it belongs to, using a precomputed table of md5("/vendor/<composer>/") over the whole catalogue. This turns a hash already present in the target's HTML into a certain, zero-extra-request identification (the site is literally serving that package's asset). ok=false if unknown.

func (*ExtProbeDB) KeyForComposer

func (d *ExtProbeDB) KeyForComposer(name string) string

KeyForComposer returns the TER extension key for a Packagist name, or "".

func (*ExtProbeDB) Keys

func (d *ExtProbeDB) Keys() []string

Keys returns the extension keys in the DB, sorted.

type ExtResult

type ExtResult struct {
	Target        string      `json:"target"`
	Probed        int         `json:"probed"`
	Errors        int         `json:"errors"`
	Baseline      Baseline    `json:"baseline"`
	Extensions    []Extension `json:"extensions"`
	NotEnumerable bool        `json:"notEnumerable"`         // control probe itself looked like a hit
	Blocked       bool        `json:"blocked,omitempty"`     // IP ban / WAF / rate-limit detected
	BlockReason   string      `json:"blockReason,omitempty"` // what was observed
	Notes         []string    `json:"notes,omitempty"`
}

ExtResult is the full extension-enumeration report.

type Extension

type Extension struct {
	Package   string `json:"package"`            // vendor/name (composer) or ext key (legacy)
	Confirmed bool   `json:"confirmed"`          // a subpath under it also responded
	Evidence  string `json:"evidence,omitempty"` // the subpath/URL that confirmed it
	AssetURL  string `json:"assetUrl,omitempty"` // the /_assets/<md5>/ URL that hit (composer)
	Location  string `json:"location,omitempty"` // where it was found (legacy path)
	Status    int    `json:"status"`             // HTTP status of the hit
	// Version and VersionSource are populated by legacy enumeration when a
	// version-bearing file (composer.json, ext_emconf.php, …) is web-readable.
	Version       string `json:"version,omitempty"`
	VersionSource string `json:"versionSource,omitempty"`
	// ComposerName is the Packagist name (vendor/pkg) read from composer.json,
	// used to look the extension up in the advisory feed.
	ComposerName string `json:"composerName,omitempty"`
	// VersionSources lists how the version was determined ("ext_emconf.php",
	// "static-file hash", …) when more than one method agreed.
	VersionExact bool `json:"versionExact,omitempty"` // pinned by static-file hash
	// VersionCandidates is the full candidate set when Version is a range (from
	// static-file hashing) — used for accurate per-candidate CVE assessment.
	VersionCandidates []string `json:"versionCandidates,omitempty"`
	// Key is the TER extension key (differs from the composer name; unique even
	// when several keys share a composer name). Drives the canonical Link.
	Key string `json:"key,omitempty"`
	// Author / Owner identify who ships this extension — the disambiguator when
	// several extensions declare the same composer name.
	Author string `json:"author,omitempty"`
	Owner  string `json:"owner,omitempty"`
	// Link is the canonical TER extension page (by key).
	Link string `json:"link,omitempty"`
	// Ambiguous is set when this is one of several extensions that share the
	// asset path (same composer name) and the served files couldn't single one
	// out — only one of the Ambiguous siblings is actually installed.
	Ambiguous bool `json:"ambiguous,omitempty"`
	// Latest is the newest published version of this extension: from the DB
	// snapshot (built at DB-build time) or refreshed live with --live-versions.
	Latest string `json:"latest,omitempty"`
	// LatestSource is "db" (embedded snapshot) or "live" (queried at scan time).
	LatestSource string `json:"latestSource,omitempty"`
	// Outdated is true when the detected version is behind Latest.
	Outdated bool `json:"outdated,omitempty"`
	// Requires is the extension's declared dependencies (from the probe DB).
	Requires map[string]string `json:"requires,omitempty"`
	// Vulns lists known advisories affecting this extension version (with -cve).
	Vulns []Advisory `json:"vulnerabilities,omitempty"`
	// VulnsPossible lists advisories for the package when the version is unknown
	// (composer-mode enumeration) — the extension has known CVEs, but whether
	// this install is affected depends on its (unreadable) version.
	VulnsPossible []Advisory `json:"vulnerabilitiesPossible,omitempty"`
}

Extension is one enumerated package result.

type FileProbe

type FileProbe struct {
	Path     string   `json:"path"`
	Status   int      `json:"status"`
	MD5      string   `json:"md5,omitempty"`
	Size     int      `json:"size,omitempty"`
	Matched  bool     `json:"matched"`
	Versions []string `json:"versions,omitempty"`
	ByPath   bool     `json:"byPath,omitempty"` // matched on exact path (legacy) vs content-only
}

FileProbe is the outcome of hashing one served file.

type Finding

type Finding struct {
	Kind     string `json:"kind"`     // install-tool | debug-mode | sitemap | host-header | error-page
	Severity string `json:"severity"` // info | low | medium | high
	Title    string `json:"title"`
	Detail   string `json:"detail,omitempty"`
	URL      string `json:"url,omitempty"`
}

Finding is a security-relevant pre-auth observation.

type Fingerprinter

type Fingerprinter struct {
	HTTP       *http.Client
	DB         *DB
	Advisories *AdvisoryDB
	ExtProbes  *ExtProbeDB
	Releases   *Releases
	UserAgent  string
	// Insecure skips TLS certificate verification when true.
	Insecure bool
	// Proxy, when set, routes all HTTP(S) through this proxy URL. Supports
	// http://, https:// and socks5:// schemes (e.g. Burp on
	// http://127.0.0.1:8080, or socks5://127.0.0.1:9050 for Tor).
	Proxy string
	// ProbeConcurrency bounds concurrent HTTP requests (default 16).
	ProbeConcurrency int
	// Rate caps requests per second across all workers (0 = unlimited).
	Rate float64
	// contains filtered or unexported fields
}

Fingerprinter is the shared client for both extension enumeration and version detection against a TYPO3 target.

func New

func New(opts ...Option) (*Fingerprinter, error)

New builds a Fingerprinter. By default it loads the embedded version DB; a missing/empty DB is tolerated (extension enumeration needs no DB).

func (*Fingerprinter) AnnotateExtensionCVEs

func (f *Fingerprinter) AnnotateExtensionCVEs(ctx context.Context, res *ExtResult) error

AnnotateExtensionCVEs looks up known advisories for every enumerated extension that has both a Packagist name and a version, and fills in Extension.Vulns. It performs a single batched request to the advisory feed.

func (*Fingerprinter) Detect

func (f *Fingerprinter) Detect(ctx context.Context, rawURL string) (*VersionResult, error)

Detect fingerprints the TYPO3 version of rawURL.

func (*Fingerprinter) DetectMode

func (f *Fingerprinter) DetectMode(ctx context.Context, target string) (Mode, error)

DetectMode does a light identify pass and reports the install layout, so the extension enumerator can pick the right technique (Composer /_assets/ vs legacy /typo3conf/ext/). Returns ModeUnknown if it can't tell.

func (*Fingerprinter) EnumerateExtensions

func (f *Fingerprinter) EnumerateExtensions(ctx context.Context, target string, packages []string, progress func(done, total int)) (*ExtResult, error)

EnumerateExtensions probes each candidate package against the target and returns the ones whose asset directory responds differently from a known-absent control. Progress, if non-nil, is called with (done, total).

func (*Fingerprinter) EnumerateExtensionsLegacy

func (f *Fingerprinter) EnumerateExtensionsLegacy(ctx context.Context, target string, keys []string, progress func(done, total int)) (*ExtResult, error)

EnumerateExtensionsLegacy detects each extension by requesting a file it actually ships (from the probe DB, or generic markers) under the legacy roots. A 200 ⇒ installed. Versions come from the probe DB and/or ext_emconf.php.

func (*Fingerprinter) PassiveExtensions

func (f *Fingerprinter) PassiveExtensions(ctx context.Context, target string) (*ExtResult, error)

PassiveExtensions finds installed extensions WITHOUT brute force, from signals the target already exposes:

  • composer-mode /_assets/<md5>/ URLs in the HTML, reversed against the catalogue — certain, since the site is serving that package's own asset;
  • legacy /typo3conf/ext/<key>/ paths — the key is written in the URL;
  • an exposed /composer.lock — the full package list with EXACT versions;
  • legacy typo3conf/PackageStates.php — the active-extension list.

Each hit is confirmed and records how it was found. Versions are filled in from composer.lock when available, and otherwise pinned by hashing the package's public files (composer hits, reusing the /_assets/<md5>/ prefix). This is the cheap first pass; brute force is only needed afterwards for backend-only extensions that expose no public asset.

func (*Fingerprinter) Reachability

func (f *Fingerprinter) Reachability(ctx context.Context, target string) *Reachability

Reachability probes the target root and classifies whether we are being blocked. A network error is NOT a block (host down / DNS) — only a live HTTP response that denies us counts.

func (*Fingerprinter) RefreshExtensionLatestLive

func (f *Fingerprinter) RefreshExtensionLatestLive(ctx context.Context, res *ExtResult)

RefreshExtensionLatestLive queries Packagist for the current newest stable version of every enumerated extension that has a Packagist name, overriding the DB snapshot's Latest and recomputing Outdated. Used by --live-versions so a stale embedded DB never hides that a plugin has a newer release.

type Mode

type Mode string

Mode is the install layout of a TYPO3 target.

const (
	ModeComposer Mode = "composer" // TYPO3 >= 11 default: core under vendor/, assets under /_assets/<md5>/
	ModeLegacy   Mode = "legacy"   // classic layout: typo3/sysext/** web-served
	ModeUnknown  Mode = "unknown"
)

type Option

type Option func(*Fingerprinter)

Option configures a Fingerprinter.

func WithConcurrency

func WithConcurrency(n int) Option

WithConcurrency sets the maximum number of concurrent requests.

func WithDB

func WithDB(db *DB) Option

WithDB sets a custom version database (defaults to the embedded one).

func WithHTTPClient

func WithHTTPClient(c *http.Client) Option

WithHTTPClient sets a custom HTTP client.

func WithInsecure

func WithInsecure(b bool) Option

WithInsecure disables TLS certificate verification.

func WithProxy

func WithProxy(p string) Option

WithProxy routes traffic through an http://, https:// or socks5:// proxy.

func WithRate

func WithRate(r float64) Option

WithRate caps requests per second (0 = unlimited).

type Reachability

type Reachability struct {
	Status    int    `json:"status"`           // HTTP status at the root
	Reachable bool   `json:"reachable"`        // got any HTTP response at all
	Blocked   bool   `json:"blocked"`          // root looks banned / WAF-blocked
	Reason    string `json:"reason,omitempty"` // human explanation
	WAF       string `json:"waf,omitempty"`    // detected WAF vendor, if any
}

Reachability is the outcome of a lightweight "are we actually allowed to talk to this host?" check — used to tell a real "not TYPO3 / no plugins" result apart from an IP ban / WAF block, where every path answers a blanket 4xx/5xx and any scan result would be meaningless.

type Release

type Release struct {
	Version string `json:"version"`
	Type    string `json:"type"`
	ELTS    bool   `json:"elts"`
}

Release is one entry from the get.typo3.org release API.

type Releases

type Releases struct {
	Latest   string            `json:"latest"`   // newest stable version overall
	Branches map[string]string `json:"branches"` // "13.4" -> "13.4.33"
}

Releases tracks the latest stable release per TYPO3 branch (major.minor), so a detected version can be flagged as up-to-date or behind. Sourced from the official get.typo3.org release feed.

func FetchReleases

func FetchReleases(ctx context.Context) (*Releases, error)

FetchReleases downloads the current release feed and computes the latest stable (non-development) release per branch. Used by `t3scan buildreleases`.

func LoadReleases

func LoadReleases() *Releases

LoadReleases returns the embedded release snapshot.

func (*Releases) LatestForBranch

func (r *Releases) LatestForBranch(version string) string

LatestForBranch returns the latest known patch of a version's branch, or "".

type UpdateStats

type UpdateStats struct {
	Extensions    int `json:"extensions"`    // total in the resulting DB
	NewExtensions int `json:"newExtensions"` // keys built for the first time
	Updated       int `json:"updated"`       // existing keys that gained versions
	Unchanged     int `json:"unchanged"`     // existing keys already up to date
	Downloads     int `json:"downloads"`     // version zips fetched this run
}

UpdateStats summarizes an incremental extension-DB update.

type VersionResult

type VersionResult struct {
	Target     string      `json:"target"`
	IsTypo3    bool        `json:"isTypo3"`
	Mode       Mode        `json:"mode"`
	BasePath   string      `json:"basePath,omitempty"`
	Version    string      `json:"version,omitempty"`      // best single answer, when confident
	Range      string      `json:"versionRange,omitempty"` // human summary
	Candidates []string    `json:"candidates,omitempty"`
	Confidence string      `json:"confidence"` // high | medium | low
	Method     string      `json:"method,omitempty"`
	Markers    []string    `json:"markers,omitempty"` // is-TYPO3 evidence
	Files      []FileProbe `json:"files,omitempty"`
	Notes      []string    `json:"notes,omitempty"`

	// Blocked is set when the target looks like it is denying us (IP ban / WAF /
	// rate-limit) rather than genuinely not being TYPO3 — any result is then
	// unreliable. BlockReason/WAF explain what was seen.
	Blocked     bool   `json:"blocked,omitempty"`
	BlockReason string `json:"blockReason,omitempty"`
	WAF         string `json:"waf,omitempty"`
	// LatestInBranch is the newest stable release of the detected version's
	// branch (e.g. 13.4.35 for a 13.4.x target); NewestOverall is the newest
	// stable TYPO3 release overall. Outdated is set when the target is behind
	// its branch's latest patch. Sourced from the get.typo3.org release feed.
	LatestInBranch string `json:"latestInBranch,omitempty"`
	NewestOverall  string `json:"newestOverall,omitempty"`
	Outdated       bool   `json:"outdated,omitempty"`
	// ExtensionsHint lists extension keys passively discovered in the served
	// HTML (from typo3conf/ext/<key>/ asset URLs) — free, no enumeration needed.
	ExtensionsHint []string `json:"extensionsHint,omitempty"`
	// Findings holds security-relevant pre-auth observations (exposed install
	// tool, debug mode, XML sitemap, host-header disclosure, …).
	Findings []Finding `json:"findings,omitempty"`

	// Vulnerabilities lists published core advisories affecting the detected
	// version. Maybe holds advisories affecting only some candidates when the
	// version is a range (uncertain until the version is pinned).
	Vulnerabilities []Advisory `json:"vulnerabilities,omitempty"`
	Maybe           []Advisory `json:"maybeVulnerable,omitempty"`
	// contains filtered or unexported fields
}

VersionResult is the full version-detection report for one target.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL