-
Notifications
You must be signed in to change notification settings - Fork 2
fix: address security vulnerabilities in webhooks plugin #336
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
--- | ||
"@wpengine/wpgraphql-webhooks-wordpress-plugin": patch | ||
--- | ||
|
||
fix: security improvements for webhooks plugin | ||
|
||
- Enhanced input validation and sanitization | ||
- Improved output escaping | ||
- Strengthened authorization checks | ||
- Added additional security hardening measures |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -22,12 +22,14 @@ class WebhookHandler implements Handler { | |||||||||||||||||||||||
public function handle( Webhook $webhook, array $payload ): void { | ||||||||||||||||||||||||
// Log webhook dispatch initiation | ||||||||||||||||||||||||
$dispatch_timestamp = current_time( 'mysql' ); | ||||||||||||||||||||||||
error_log( "\n========== WEBHOOK DISPATCH ==========" ); | ||||||||||||||||||||||||
error_log( "Timestamp: {$dispatch_timestamp}" ); | ||||||||||||||||||||||||
error_log( "Webhook: {$webhook->name} (ID: {$webhook->id})" ); | ||||||||||||||||||||||||
error_log( "Event: {$webhook->event}" ); | ||||||||||||||||||||||||
error_log( "Target URL: {$webhook->url}" ); | ||||||||||||||||||||||||
error_log( "Method: {$webhook->method}" ); | ||||||||||||||||||||||||
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { | ||||||||||||||||||||||||
error_log( "\n========== WEBHOOK DISPATCH ==========" ); | ||||||||||||||||||||||||
error_log( "Timestamp: {$dispatch_timestamp}" ); | ||||||||||||||||||||||||
error_log( "Webhook: {$webhook->name} (ID: {$webhook->id})" ); | ||||||||||||||||||||||||
error_log( "Event: {$webhook->event}" ); | ||||||||||||||||||||||||
error_log( "Target URL: {$webhook->url}" ); | ||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Logging the target URL may expose sensitive endpoints; wrap this in the
Suggested change
Copilot uses AI. Check for mistakes. Positive FeedbackNegative Feedback |
||||||||||||||||||||||||
error_log( "Method: {$webhook->method}" ); | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
$args = [ | ||||||||||||||||||||||||
'headers' => $webhook->headers ?: [ 'Content-Type' => 'application/json' ], | ||||||||||||||||||||||||
|
@@ -52,7 +54,9 @@ public function handle( Webhook $webhook, array $payload ): void { | |||||||||||||||||||||||
if ( strtoupper( $webhook->method ) === 'GET' ) { | ||||||||||||||||||||||||
$url = add_query_arg( $payload, $webhook->url ); | ||||||||||||||||||||||||
$args['method'] = 'GET'; | ||||||||||||||||||||||||
error_log( "Payload (GET query params): " . wp_json_encode( $payload ) ); | ||||||||||||||||||||||||
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { | ||||||||||||||||||||||||
error_log( "Payload (GET query params): " . wp_json_encode( $payload ) ); | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||
$url = $webhook->url; | ||||||||||||||||||||||||
$args['method'] = strtoupper( $webhook->method ); | ||||||||||||||||||||||||
|
@@ -63,20 +67,28 @@ public function handle( Webhook $webhook, array $payload ): void { | |||||||||||||||||||||||
$args['headers']['Content-Type'] = 'application/json'; | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
error_log( "Payload ({$args['method']} body): " . $args['body'] ); | ||||||||||||||||||||||||
error_log( "Payload size: " . strlen( $args['body'] ) . " bytes" ); | ||||||||||||||||||||||||
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { | ||||||||||||||||||||||||
error_log( "Payload ({$args['method']} body): " . $args['body'] ); | ||||||||||||||||||||||||
error_log( "Payload size: " . strlen( $args['body'] ) . " bytes" ); | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
// Log headers | ||||||||||||||||||||||||
error_log( "Headers: " . wp_json_encode( $args['headers'] ) ); | ||||||||||||||||||||||||
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { | ||||||||||||||||||||||||
error_log( "Headers: " . wp_json_encode( $args['headers'] ) ); | ||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Logging headers can reveal sensitive authentication tokens; redact or omit header values unless explicitly needed for debugging behind
Suggested change
Copilot uses AI. Check for mistakes. Positive FeedbackNegative Feedback |
||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
// For test mode or debugging, optionally use blocking mode | ||||||||||||||||||||||||
if ( apply_filters( 'graphql_webhooks_test_mode', false, $webhook ) ) { | ||||||||||||||||||||||||
$args['blocking'] = true; | ||||||||||||||||||||||||
error_log( "Test mode enabled - using blocking request" ); | ||||||||||||||||||||||||
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { | ||||||||||||||||||||||||
error_log( "Test mode enabled - using blocking request" ); | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
error_log( "====================================\n" ); | ||||||||||||||||||||||||
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { | ||||||||||||||||||||||||
error_log( "====================================\n" ); | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
// Send the webhook | ||||||||||||||||||||||||
$start_time = microtime( true ); | ||||||||||||||||||||||||
|
@@ -85,7 +97,7 @@ public function handle( Webhook $webhook, array $payload ): void { | |||||||||||||||||||||||
$duration = round( ( $end_time - $start_time ) * 1000, 2 ); | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
// Log response if in blocking mode | ||||||||||||||||||||||||
if ( $args['blocking'] ) { | ||||||||||||||||||||||||
if ( $args['blocking'] && defined( 'WP_DEBUG' ) && WP_DEBUG ) { | ||||||||||||||||||||||||
if ( is_wp_error( $response ) ) { | ||||||||||||||||||||||||
error_log( "\n========== WEBHOOK ERROR ==========" ); | ||||||||||||||||||||||||
error_log( "❌ ERROR: " . $response->get_error_message() ); | ||||||||||||||||||||||||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -86,15 +86,16 @@ public function test_webhook( WP_REST_Request $request ): WP_REST_Response|WP_Er | |||||
|
||||||
// Log test initiation | ||||||
$test_timestamp = current_time( 'mysql' ); | ||||||
error_log( "\n========== WEBHOOK TEST INITIATED ==========" ); | ||||||
error_log( "Timestamp: {$test_timestamp}" ); | ||||||
error_log( "Webhook ID: {$webhook_id}" ); | ||||||
error_log( "Webhook Name: {$webhook->name}" ); | ||||||
error_log( "Target URL: {$webhook->url}" ); | ||||||
error_log( "HTTP Method: {$webhook->method}" ); | ||||||
error_log( "Event: {$webhook->event}" ); | ||||||
error_log( "Headers: " . wp_json_encode( $webhook->headers ) ); | ||||||
error_log( "==========================================\n" ); | ||||||
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { | ||||||
error_log( "\n========== WEBHOOK TEST INITIATED ==========" ); | ||||||
error_log( "Timestamp: {$test_timestamp}" ); | ||||||
error_log( "Webhook ID: {$webhook_id}" ); | ||||||
error_log( "Webhook Name: {$webhook->name}" ); | ||||||
// Do not log sensitive URL and headers | ||||||
error_log( "HTTP Method: {$webhook->method}" ); | ||||||
error_log( "Event: {$webhook->event}" ); | ||||||
error_log( "==========================================\n" ); | ||||||
} | ||||||
|
||||||
// Create test payload | ||||||
$test_payload = [ | ||||||
|
@@ -107,7 +108,7 @@ public function test_webhook( WP_REST_Request $request ): WP_REST_Response|WP_Er | |||||
], | ||||||
'test_data' => [ | ||||||
'message' => 'This is a test webhook payload', | ||||||
'triggered_by' => wp_get_current_user()->user_login, | ||||||
'triggered_by' => 'admin', | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hardcoding 'admin' for triggered_by loses the actual user identity; consider using the sanitized current user login or ID to preserve accurate audit data.
Suggested change
Copilot uses AI. Check for mistakes. Positive FeedbackNegative Feedback |
||||||
'site_url' => get_site_url(), | ||||||
], | ||||||
]; | ||||||
|
@@ -124,11 +125,13 @@ public function test_webhook( WP_REST_Request $request ): WP_REST_Response|WP_Er | |||||
$end_time = microtime( true ); | ||||||
$duration = round( ( $end_time - $start_time ) * 1000, 2 ); // Convert to milliseconds | ||||||
|
||||||
error_log( "\n========== WEBHOOK TEST COMPLETED ==========" ); | ||||||
error_log( "✅ SUCCESS: Test webhook dispatched" ); | ||||||
error_log( "Duration: {$duration}ms" ); | ||||||
error_log( "Completed at: " . current_time( 'mysql' ) ); | ||||||
error_log( "==========================================\n" ); | ||||||
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { | ||||||
error_log( "\n========== WEBHOOK TEST COMPLETED ==========" ); | ||||||
error_log( "✅ SUCCESS: Test webhook dispatched" ); | ||||||
error_log( "Duration: {$duration}ms" ); | ||||||
error_log( "Completed at: " . current_time( 'mysql' ) ); | ||||||
error_log( "==========================================\n" ); | ||||||
} | ||||||
|
||||||
return new WP_REST_Response( | ||||||
[ | ||||||
|
@@ -147,10 +150,12 @@ public function test_webhook( WP_REST_Request $request ): WP_REST_Response|WP_Er | |||||
200 | ||||||
); | ||||||
} catch ( \Exception $e ) { | ||||||
error_log( "\n========== WEBHOOK TEST ERROR ==========" ); | ||||||
error_log( "❌ ERROR: " . $e->getMessage() ); | ||||||
error_log( "Stack trace: " . $e->getTraceAsString() ); | ||||||
error_log( "========================================\n" ); | ||||||
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { | ||||||
error_log( "\n========== WEBHOOK TEST ERROR ==========" ); | ||||||
error_log( "❌ ERROR: " . $e->getMessage() ); | ||||||
// Do not log stack trace as it may contain sensitive information | ||||||
error_log( "========================================\n" ); | ||||||
} | ||||||
|
||||||
return new WP_Error( | ||||||
'webhook_test_failed', | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,7 +26,7 @@ public function has(string $name): bool { | |
|
||
public function get(string $name) { | ||
if (!isset($this->factories[$name])) { | ||
throw new UnexpectedValueException("Service not found: {$name}"); | ||
throw new UnexpectedValueException( esc_html( "Service not found: {$name}" ) ); | ||
Comment on lines
28
to
+29
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using esc_html inside exception logic is meant for HTML output escaping; consider sanitizing the input earlier (e.g., with sanitize_text_field) or omitting HTML-specific escaping here. Copilot uses AI. Check for mistakes. Positive FeedbackNegative Feedback |
||
} | ||
|
||
if (!isset($this->instances[$name])) { | ||
|
This comment was marked as resolved.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.