App-WebPerimeterScan

 view release on metacpan or  search on metacpan

lib/App/WebPerimeterScan/Messages.pm  view on Meta::CPAN


	'x-powered-by.title'    => 'X-Powered-By discloses the platform: {value}',
	'x-powered-by.detail'   => 'The header advertises the application stack and usually its version.',
	'x-powered-by.evidence' => 'X-Powered-By: {value}',
	'x-powered-by.fix'      => 'Remove X-Powered-By (expose_php = Off, or Header unset X-Powered-By).',

	'generator-disclosed.title'    => 'Generator meta tag discloses: {generator}',
	'generator-disclosed.detail'   => 'The CMS and its version are advertised in the markup.',
	'generator-disclosed.evidence' => '<meta name="generator" content="{generator}">',
	'generator-disclosed.fix'      => 'Remove the generator meta tag.',

	'php2shell.title'    => 'PHP version vulnerable to PHP2Shell ({version})',
	'php2shell.detail'   => {
		select => 'reason',
		forms  => {
			unsupported => 'PHP {version} has no upstream fix for PHP2Shell. CVE-2026-109618 and CVE-2026-109619 are remote code execution flaws that moved from disclosure to active exploitation within hours.',
			other       => 'PHP {version} is below the PHP2Shell fix level {fixed}. CVE-2026-109618 and CVE-2026-109619 are remote code execution flaws that moved from disclosure to active exploitation within hours.',
		},
	},
	'php2shell.evidence' => 'X-Powered-By reports PHP/{version}',
	'php2shell.fix'      => 'Upgrade to the fixed release for this branch ({fixed_list}) and restart the SAPI.',

	'cookie-flags.title'    => "Cookie '{name}' is missing {missing}",
	'cookie-flags.detail'   => 'A cookie without HttpOnly can be read by injected script; without Secure it is sent over plaintext; without SameSite it is attached to cross-site requests.',
	'cookie-flags.evidence' => 'Set-Cookie: {cookie} (seen on {url})',
	'cookie-flags.fix'      => 'Set Secure, HttpOnly and SameSite=Lax (or Strict) on session cookies.',

	# ==================================================================
	# Response body checks
	# ==================================================================
	'directory-listing.title'    => 'Directory listing is enabled',
	'directory-listing.detail'   => 'The server returns an index of the directory, which reveals files that are not linked from anywhere.',
	'directory-listing.evidence' => 'Response title is "{title}"',
	'directory-listing.fix'      => 'Disable auto-indexing (Options -Indexes / autoindex off).',

	'error-disclosure.title'  => 'Verbose error output discloses internals ({markers})',
	'error-disclosure.detail' => "Error pages leak file paths, framework versions and sometimes query fragments, which shortens an attacker's reconnaissance.",
	'error-disclosure.fix'    => 'Log details server-side and return a generic error page with the correct status code.',
	'error-marker.php'        => 'PHP error',
	'error-marker.perl'       => 'Perl error',
	'error-marker.python'     => 'Python traceback',
	'error-marker.java'       => 'Java stack trace',
	'error-marker.sql'        => 'SQL error',
	'error-marker.stack'      => 'stack trace',

	'mixed-content-active.title'  => 'Active mixed content on an HTTPS page',
	'mixed-content-active.detail' => 'Scripts, stylesheets, frames or form targets are loaded over plaintext, so a network attacker can replace them wholesale. Form actions over HTTP also send their contents in the clear.',
	'mixed-content-active.fix'    => 'Move every script, stylesheet, frame and form target to https:// and add upgrade-insecure-requests to the CSP.',

	'mixed-content-passive.title'  => 'Passive mixed content on an HTTPS page',
	'mixed-content-passive.detail' => 'Images, media or icons are fetched over plaintext, so they can be modified in transit and can leak the page URL through the Referer header.',
	'mixed-content-passive.fix'    => 'Reference these resources over https://.',

	# ==================================================================
	# CORS and methods
	# ==================================================================
	'cors-misconfiguration.title'    => 'CORS policy allows untrusted origins',
	'cors-misconfiguration.detail'   => {
		select => 'case',
		forms  => {
			credentials => 'Any origin may read authenticated responses, so a malicious site can act as the logged-in user.',
			reflected   => 'The server echoes back whatever Origin it is given, so any site can read these responses.',
			other       => 'Any origin may read these responses. Harmless for genuinely public data, dangerous for anything user-specific.',
		},
	},
	'cors-misconfiguration.evidence' => {
		select => 'credentials',
		forms  => {
			yes   => 'Access-Control-Allow-Origin: {allowed}; Access-Control-Allow-Credentials: true (request sent Origin: {origin})',
			other => 'Access-Control-Allow-Origin: {allowed} (request sent Origin: {origin})',
		},
	},
	'cors-misconfiguration.fix'      => 'Echo back only origins on an explicit allowlist, never a wildcard, and only with credentials when necessary.',

	'trace-enabled.title'    => 'HTTP TRACE is enabled',
	'trace-enabled.detail'   => 'TRACE echoes the request back, which can be combined with other flaws to read cookies and headers.',
	'trace-enabled.evidence' => 'Allow: {allow}',
	'trace-enabled.fix'      => 'Disable TRACE (TraceEnable off in Apache).',

	'write-methods-enabled.title'    => 'Write methods advertised on a public URL',
	'write-methods-enabled.detail'   => 'PUT, DELETE or PATCH are allowed here. That may be intended for an API, but it is worth confirming the endpoint authenticates and authorises.',
	'write-methods-enabled.evidence' => 'Allow: {allow}',
	'write-methods-enabled.fix'      => 'Restrict methods per route at the web server or application layer.',

	# ==================================================================
	# TLS
	# ==================================================================
	'tls-unavailable.title'  => 'TLS handshake failed',
	'tls-unavailable.detail' => 'The host did not complete a TLS handshake on the scanned port, so either HTTPS is not served there or the TLS configuration is broken.',
	'tls-unavailable.fix'    => 'Confirm the port serves HTTPS and that the certificate chain is complete.',

	'tls-invalid-certificate.title'  => 'TLS certificate does not verify',
	'tls-invalid-certificate.detail' => 'Browsers will show a full-page warning, and users who click through are training themselves to ignore the warning that protects them.',
	'tls-invalid-certificate.fix'    => 'Serve the full chain including intermediates, and issue the certificate for the names actually in use.',

	'tls-self-signed.title'    => 'TLS certificate is self-signed',
	'tls-self-signed.detail'   => 'A self-signed certificate provides no identity guarantee, so it offers no protection against interception.',
	'tls-self-signed.evidence' => 'Subject and issuer are identical: {subject}',
	'tls-self-signed.fix'      => 'Use a certificate from a publicly trusted CA.',

	'tls-expired.title' => {
		plural => 'days',
		forms  => {
			one   => 'TLS certificate expired {days} day ago',
			other => 'TLS certificate expired {days} days ago',
		},
	},
	'tls-expired.detail'   => 'Every visitor sees an interstitial warning, and API clients will reject the connection.',
	'tls-expired.evidence' => 'notAfter: {not_after}',
	'tls-expired.fix'      => 'Renew the certificate and automate renewal.',

	'tls-expiring.title' => {
		plural => 'days',
		forms  => {
			one   => 'TLS certificate expires in {days} day',
			other => 'TLS certificate expires in {days} days',
		},
	},
	'tls-expiring.detail'   => 'Certificate expiry is a self-inflicted outage, and automated clients fail closed.',
	'tls-expiring.evidence' => 'notAfter: {not_after}',
	'tls-expiring.fix'      => 'Renew now and automate renewal with ACME.',

	'tls-legacy-protocol.title'    => 'Deprecated TLS protocol version accepted ({protocols})',
	'tls-legacy-protocol.detail'   => 'TLS 1.0 and 1.1 are deprecated by RFC 8996 and are barred by PCI DSS. They only survive to support clients that should be retired.',
	'tls-legacy-protocol.evidence' => 'Server completed a handshake with {protocols}',
	'tls-legacy-protocol.fix'      => 'Require TLS 1.2 or newer (SSLProtocol -all +TLSv1.2 +TLSv1.3).',

	'tls-weak-cipher.title'    => 'Weak cipher negotiated: {cipher}',
	'tls-weak-cipher.detail'   => 'The negotiated suite is broken or has no authentication, so the connection can be decrypted or impersonated.',
	'tls-weak-cipher.evidence' => 'Cipher: {cipher}; protocol: {protocol}',
	'tls-weak-cipher.fix'      => 'Restrict the cipher list to AEAD suites and disable anonymous and export ciphers.',

	# ==================================================================
	# Ports
	# ==================================================================
	'portscan-failed.title'  => 'Port scan could not run',
	'portscan-failed.detail' => '{error}',

	'port-open.title'    => 'Port {port} accepts connections from outside the host',
	'port-open.detail'   => {
		select => 'port',
		forms  => {
			21    => 'FTP is unencrypted and frequently allows anonymous access',
			22    => 'SSH is reachable; confirm it only allows keys and is patched',
			23    => 'Telnet is unencrypted',
			25    => 'SMTP is reachable from the internet',
			445   => 'SMB should never be exposed to the internet',
			587   => 'Message submission is reachable',
			993   => 'IMAPS is reachable',
			995   => 'POP3S is reachable',
			1433  => 'Microsoft SQL Server should not be internet-facing',
			3000  => 'A development application server appears to be exposed',
			3306  => 'MySQL should not be internet-facing',
			3389  => 'RDP is a common ransomware entry point',
			5432  => 'PostgreSQL should not be internet-facing',
			5900  => 'VNC is unencrypted and often unauthenticated',
			6379  => 'Redis is unauthenticated by default',
			8000  => 'An alternate HTTP port is exposed',
			8080  => 'An alternate HTTP port is exposed',
			8443  => 'An alternate HTTPS port is exposed',
			8888  => 'An alternate HTTP port is exposed',
			9200  => 'Elasticsearch has no authentication by default',
			11211 => 'memcached has no authentication at all',
			27017 => 'MongoDB has historically shipped without authentication',
			other => 'A service is reachable on this port',
		},
	},
	'port-open.evidence' => {
		select => 'has_banner',
		forms  => {
			yes   => 'Banner: {banner}',
			other => 'TCP connect succeeded, no banner',
		},
	},
	'port-open.fix'      => 'Close the port at the firewall unless it must be public; if it must, restrict it by source address and require authentication.',

	'ports-other-open.title'    => 'Other reachable ports: {ports}',
	'ports-other-open.detail'   => 'These are not inherently unsafe, but every open port is attack surface that has to be patched and monitored.',
	'ports-other-open.evidence' => 'Open: {ports}',
	'ports-other-open.fix'      => 'Close anything not actively needed.',

	'outdated-ssh-banner.title'  => 'SSH announces an old version',
	'outdated-ssh-banner.detail' => 'OpenSSH releases before {minimum} include versions with published remote flaws; the banner alone is not proof of a working exploit, but it is a strong hint.',
	'outdated-ssh-banner.fix'    => 'Upgrade OpenSSH and restart sshd.',
	'outdated-ssh-banner.item'   => 'port {port}: {banner}',

	# ==================================================================
	# Well-known paths
	# ==================================================================
	'git-exposed.title'  => 'Exposed Git repository metadata',
	'git-exposed.detail' => 'The .git directory is being served. Source code, commit history and any credentials ever committed can be reconstructed from it.',
	'git-exposed.fix'    => 'Deny requests to /.git/ at the web server, and deploy with the repository outside the document root.',

	'git-head.title'  => 'Exposed Git HEAD',
	'git-head.detail' => 'A Git ref file is readable, which confirms the repository is exposed.',
	'git-head.fix'    => 'Deny requests to /.git/ at the web server.',

	'svn-exposed.title'  => 'Exposed Subversion metadata',
	'svn-exposed.detail' => 'Subversion administrative files are readable.',
	'svn-exposed.fix'    => 'Deny requests to /.svn/ at the web server.',

	'env-exposed.title'  => 'Exposed environment file',
	'env-exposed.detail' => 'A dotenv file is downloadable. These usually hold database passwords, API keys and application secrets.',
	'env-exposed.fix'    => 'Serve only the application entry point: deny dotfiles at the web server and keep configuration above the document root.',

	'htpasswd-exposed.title'  => 'Exposed password file',
	'htpasswd-exposed.detail' => 'An htpasswd file is downloadable, so password hashes can be taken offline and cracked.',
	'htpasswd-exposed.fix'    => 'Store password files outside the document root and deny access to dotfiles.',

	'htaccess-exposed.title'  => 'Exposed .htaccess',
	'htaccess-exposed.detail' => 'Server configuration is readable and reveals internal rewrite targets and module versions.',
	'htaccess-exposed.fix'    => 'Deny access to dotfiles at the web server.',

	'web-config-exposed.title'  => 'Exposed web.config',
	'web-config-exposed.detail' => 'An IIS configuration file is readable and may contain connection strings.',
	'web-config-exposed.fix'    => 'Deny access to web.config.',

	'php-config-backup.title'  => 'Exposed WordPress configuration backup',
	'php-config-backup.detail' => 'A backup copy of wp-config.php is downloadable, exposing database credentials and salts.',
	'php-config-backup.fix'    => 'Remove backup copies from the document root and deny requests for .bak/.old/.orig files.',

	'sql-dump-exposed.title'  => 'Exposed database dump',
	'sql-dump-exposed.detail' => 'A database dump is downloadable, which is usually a complete copy of the application data.',
	'sql-dump-exposed.fix'    => 'Remove dumps from the web root and store backups off the web server.',

	'ds-store-exposed.title'  => 'Exposed .DS_Store',
	'ds-store-exposed.detail' => 'A macOS folder index is readable, which leaks the names of files (sometimes files that are otherwise unreachable) in that directory.',
	'ds-store-exposed.fix'    => 'Deny dotfiles at the web server and stop deploying macOS metadata.',

	'server-status-exposed.title'  => 'Apache server-status is public',
	'server-status-exposed.detail' => 'mod_status is reachable, exposing internal URLs, client addresses and server load.',
	'server-status-exposed.fix'    => 'Restrict /server-status with Require ip on localhost, or disable mod_status.',

	'server-info-exposed.title'  => 'Apache server-info is public',
	'server-info-exposed.detail' => 'mod_info is reachable and publishes the full module configuration.',
	'server-info-exposed.fix'    => 'Restrict /server-info with Require ip on localhost, or disable mod_info.',

	'phpinfo-exposed.title'  => 'phpinfo() is public',
	'phpinfo-exposed.detail' => 'A phpinfo page leaks absolute paths, loaded extensions, environment variables and sometimes credentials.',
	'phpinfo-exposed.fix'    => 'Delete phpinfo scripts from production.',

	'spring-actuator-exposed.title'  => 'Spring Boot actuator environment is public',
	'spring-actuator-exposed.detail' => 'The actuator env endpoint is reachable and commonly exposes datasource credentials and API keys.',
	'spring-actuator-exposed.fix'    => 'Require authentication on /actuator/**, and only expose health and info.',

	'spring-health-exposed.title'  => 'Spring Boot actuator health is public',
	'spring-health-exposed.detail' => 'Health detail is public. This is minor on its own but confirms a Spring Boot management surface.',
	'spring-health-exposed.fix'    => 'Expose only the health endpoint, and set management.endpoint.health.show-details=never.',

	'composer-json-exposed.title'  => 'composer.json is public',
	'composer-json-exposed.detail' => 'Dependency pins are readable, which tells an attacker exactly which versions to try.',
	'composer-json-exposed.fix'    => 'Deny access to composer.json and composer.lock.',

	'package-json-exposed.title'  => 'package.json is public',
	'package-json-exposed.detail' => 'Dependency pins are readable, which tells an attacker exactly which versions to try.',
	'package-json-exposed.fix'    => 'Deny access to package.json and package-lock.json.',

	'crossdomain-xml.title'  => 'Permissive crossdomain.xml',
	'crossdomain-xml.detail' => 'A wildcard Flash cross-domain policy lets any site read responses from this origin (still honoured by some legacy clients).',
	'crossdomain-xml.fix'    => 'Remove crossdomain.xml, or restrict allow-access-from to specific trusted domains.',

	'security-txt-present.title'    => 'security.txt is published',
	'security-txt-present.detail'   => 'Good: this gives a researcher a route to report a finding to you privately.',
	'security-txt-present.evidence' => 'Contact directive found',

	'robots-disclosure.title' => {
		plural => 'count',
		forms  => {
			one   => 'robots.txt names {count} path',
			other => 'robots.txt names {count} paths',
		},
	},
	'robots-disclosure.detail'   => 'robots.txt is public. Anything listed there is a map of what the operator considers sensitive; it must not be the only protection.',
	'robots-disclosure.evidence' => 'Disallow: {paths}',
	'robots-disclosure.fix'      => 'Enforce access control on these paths rather than relying on robots.txt, and consider not listing them at all.',
};

1;

__END__

=encoding utf8

=head1 NAME

App::WebPerimeterScan::Messages - the message catalogue

=head1 VERSION

Version 0.001.0

=head1 SYNOPSIS

Read one message, without the placeholders filled in:

	use App::WebPerimeterScan::Messages;

	my $en = $App::WebPerimeterScan::Messages::CATALOGUE{en};
	print $en->{'csp-missing.title'}, "\n";

Add a partial German catalogue.  Every key it does not have falls back to
English:



( run in 1.352 second using v1.01-cache-2.11-cpan-3f962061493 )