Security Review Catches Exploitable Arbitrary File Viewing Vulnerability in Eventin WordPress Plugin
As part of starting a security review of the WordPress plugin Eventin after it was chosen by our customers to receive a review, we ran the plugin through our Plugin Security Checker. That identified the possibility of a server-side request forgery (SSRF) issue in the plugin:
That type of vulnerability isn’t a big deal, as it simply allows an attacker to cause HTTP requests to be made through the website. Looking at the rest of the code to see if the code did cause that type of vulnerability, we found the situation was a lot more serious.
The code was in the function proxy_image() in the file /core/Admin/Hooks.php. That is registered to run during “init”, so it is accessible to even those not logged in to WordPress:
57 | add_action( 'init', [ $this, 'proxy_image' ] ); |
The start of the code in that function shows that SSRF is possible as there are no restrictions in place before a value specified by user input is passed to the file_get_contents() function:
450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 | public function proxy_image() { $action = isset( $_GET['action'] ) ? $_GET['action'] : ''; if ( $action !== 'proxy_image' ) { return; } ob_start(); if ( $_SERVER['REQUEST_METHOD'] === 'OPTIONS' ) { http_response_code(200); ob_end_flush(); exit; } $imageUrl = isset( $_GET['url'] ) ? $_GET['url'] : null; if ( $imageUrl ) { $imageContent = file_get_contents( $imageUrl ); |
While the code appears intended to request a URL, the function used to request it, file_get_contents(), could also be used to read files on the website. That becomes a big problem as the rest of the code outputs the file or URL requested:
470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 | if ( $imageContent !== false ) { $finfo = finfo_open( FILEINFO_MIME_TYPE ); $mimeType = finfo_buffer( $finfo, $imageContent ); finfo_close( $finfo ); header("Content-Type: $mimeType"); $tempStream = fopen('php://temp', 'r+'); fwrite( $tempStream, $imageContent ); rewind( $tempStream ); fpassthru( $tempStream ); fclose( $tempStream ); } else { http_response_code(404); } } else { http_response_code(400); } ob_end_flush(); // End output buffering } |
So an attacker can send a request to the website and view the contents of the WordPress configuration file and see the database credentials of the website. That is confirmed with the proof of concept below.
The issue with reading arbitrary files on the website and SSRF could be mostly addressed by using wp_safe_remote_get() instead of file_get_contents() (there is an issue with wp_safe_remote_get(), which will discuss in an upcoming post). But that would still leave you with code that allows the contents of arbitrary URLs to be presented as coming from the website using the plugin. That could be abused by spammers to have spam content be accessed through someone else’s website or to load malicious JavaScript content in the context of the websites.
We notified the developer of the issue last Wednesday. We received a response under 24 hours later telling us that the vulnerability had already been fixed and we should update the plugin. No update had been released. After we replied explaining that was the case, over two days later we were told it was going to be fixed.
Today the developer released an incomplete fix for the issue, which they didn’t present to us before the update was released. So we couldn’t explain that is not how to fix this. They also didn’t follow the advice we had already given them. The code has been changed to limit what file extensions the file that can be requested can have and to stops requesting URLs from the website:
465 466 467 468 469 470 471 472 473 474 475 476 477 | $imageUrl = isset( $_GET['url'] ) ? $_GET['url'] : null; if ( $imageUrl ) { $allowed_extensions = ['jpg', 'jpeg', 'png', 'gif', 'webp']; $file_ext = strtolower( pathinfo( $imageUrl, PATHINFO_EXTENSION ) ); if ( ! in_array( $file_ext, $allowed_extensions, true ) ) { return http_response_code(404); } if ( $this->is_same_origin($imageUrl) ) { return http_response_code(404); } |
621 622 623 | public function is_same_origin( $url ) { return strpos( $url, site_url() ) !== false; } |
It doesn’t replace usage file_get_contents() and doesn’t address the ability for spammers to abuse this.
The changelog for the new version doesn’t mention the fix as it is a copy of the changelog for the previous version.
We recommend not using the plugin until that is fully addressed. (Our review has identified another serious vulnerability still in the plugin.)
Worth noting here, is that despite competing WordPress security providers claiming that the security of WordPress plugins keeps getting better. This incredibly insecure code was only introduced in to the plugin in December. It also notably hadn’t been spotted already by any of those competitors bug bounty programs that are supposed to be making WordPress plugin more secure. That is, despite an automated tool being able to flag the possibility of the lesser vulnerability here.
Unlike one of those providers, we didn’t sell information about it to hackers or withhold the information for weeks before working with the developer to get it fixed.
We confirmed that our customers were already protected against the vulnerability through the zero-day protection offered by our Plugin Vulnerabilities Firewall.
Proof of Concept
The following proof of concept will show the contents of the WordPress configuration file.
Make sure to replace “[path to WordPress]” with the location of WordPress.
http://[path to WordPress]/?action=proxy_image&url=wp-config.php
Timeline
- April 23, 2025 – Developer notified.
- April 24, 2025 – Developer responds that the vulnerability had already been fixed.
- April 27, 2025 – Developer now claims fix is being worked on.
- April 30, 2025 – Incomplete fix is released in version 4.0.27 of the plugin.