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 )