You've already forked wp-prometheus
security: Fix XSS, insecure token generation, and harden import/export (v0.4.9)
Security audit findings addressed: - Replace jQuery .html() with safe .text() DOM construction (XSS prevention) - Use crypto.getRandomValues() instead of Math.random() for token generation - Add 1MB import size limit to prevent DoS via large JSON payloads - Remove site_url from metric exports (information disclosure) - Add import mode allowlist validation Refactoring: - Extract shared wp_prometheus_authenticate_request() function (DRY) - Extract showNotice() helper in admin.js (DRY) - Extract is_hpos_enabled() helper in Collector (DRY) Performance: - Optimize WooCommerce product counting with paginate COUNT query Housekeeping: - Add missing options to Installer::uninstall() cleanup Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -107,36 +107,8 @@ function wp_prometheus_isolated_metrics_handler(): void {
|
||||
return; // Let normal flow handle unlicensed state.
|
||||
}
|
||||
|
||||
// Authenticate.
|
||||
$auth_token = get_option( 'wp_prometheus_auth_token', '' );
|
||||
if ( empty( $auth_token ) ) {
|
||||
status_header( 401 );
|
||||
header( 'WWW-Authenticate: Bearer realm="WP Prometheus Metrics"' );
|
||||
header( 'Content-Type: text/plain; charset=utf-8' );
|
||||
echo 'Unauthorized';
|
||||
exit;
|
||||
}
|
||||
|
||||
// Check Bearer token.
|
||||
$auth_header = '';
|
||||
if ( isset( $_SERVER['HTTP_AUTHORIZATION'] ) ) {
|
||||
$auth_header = sanitize_text_field( wp_unslash( $_SERVER['HTTP_AUTHORIZATION'] ) );
|
||||
} elseif ( isset( $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ) ) {
|
||||
$auth_header = sanitize_text_field( wp_unslash( $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ) );
|
||||
}
|
||||
|
||||
$authenticated = false;
|
||||
if ( ! empty( $auth_header ) && preg_match( '/Bearer\s+(.*)$/i', $auth_header, $matches ) ) {
|
||||
$authenticated = hash_equals( $auth_token, $matches[1] );
|
||||
}
|
||||
|
||||
// Check query parameter fallback.
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Auth token check.
|
||||
if ( ! $authenticated && isset( $_GET['token'] ) ) {
|
||||
$authenticated = hash_equals( $auth_token, sanitize_text_field( wp_unslash( $_GET['token'] ) ) );
|
||||
}
|
||||
|
||||
if ( ! $authenticated ) {
|
||||
// Authenticate using shared helper.
|
||||
if ( ! wp_prometheus_authenticate_request() ) {
|
||||
status_header( 401 );
|
||||
header( 'WWW-Authenticate: Bearer realm="WP Prometheus Metrics"' );
|
||||
header( 'Content-Type: text/plain; charset=utf-8' );
|
||||
@@ -161,6 +133,64 @@ function wp_prometheus_isolated_metrics_handler(): void {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate a metrics request using Bearer token or query parameter.
|
||||
*
|
||||
* Shared authentication logic used by both the MetricsEndpoint class
|
||||
* and the isolated mode handler to avoid code duplication.
|
||||
*
|
||||
* @return bool True if authenticated, false otherwise.
|
||||
*/
|
||||
function wp_prometheus_authenticate_request(): bool {
|
||||
$auth_token = get_option( 'wp_prometheus_auth_token', '' );
|
||||
|
||||
// If no token is set, deny access.
|
||||
if ( empty( $auth_token ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for Bearer token in Authorization header.
|
||||
$auth_header = wp_prometheus_get_authorization_header();
|
||||
if ( ! empty( $auth_header ) && preg_match( '/Bearer\s+(.*)$/i', $auth_header, $matches ) ) {
|
||||
return hash_equals( $auth_token, $matches[1] );
|
||||
}
|
||||
|
||||
// Check for token in query parameter (less secure but useful for testing).
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Auth token check.
|
||||
if ( isset( $_GET['token'] ) && hash_equals( $auth_token, sanitize_text_field( wp_unslash( $_GET['token'] ) ) ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Authorization header from the request.
|
||||
*
|
||||
* Checks multiple sources for the Authorization header to support
|
||||
* different server configurations (Apache, nginx, CGI, etc.).
|
||||
*
|
||||
* @return string The Authorization header value, or empty string if not found.
|
||||
*/
|
||||
function wp_prometheus_get_authorization_header(): string {
|
||||
if ( isset( $_SERVER['HTTP_AUTHORIZATION'] ) ) {
|
||||
return sanitize_text_field( wp_unslash( $_SERVER['HTTP_AUTHORIZATION'] ) );
|
||||
}
|
||||
|
||||
if ( isset( $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ) ) {
|
||||
return sanitize_text_field( wp_unslash( $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ) );
|
||||
}
|
||||
|
||||
if ( function_exists( 'apache_request_headers' ) ) {
|
||||
$headers = apache_request_headers();
|
||||
if ( isset( $headers['Authorization'] ) ) {
|
||||
return sanitize_text_field( $headers['Authorization'] );
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
// Try early metrics handling before full plugin initialization.
|
||||
wp_prometheus_early_metrics_check();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user