
Eventin
Unauthenticated Privilege Escalation
The vulnerability in the Eventin plugin was originally reported by Patchstack Alliance community member Denver Jackson to the Patchstack Zero Day bug bounty program for WordPress.
The Patchstack Zero Day program has awarded the researcher $600 USD in cash. If you wish to participate in the program, you can join the community here.
This blog post is about the Eventin plugin vulnerability. If you're an Eventin user, please update the plugin to at least 4.0.27.
If you are a Patchstack customer, you protected from this vulnerability already, and no further action is required from you.
For plugin developers, we have security audit services and Enterprise API for hosting companies.
About the Eventin plugin
The Eventin plugin, which has over 10k active installations, is a popular event management plugin for WordPress. The plugin is developed by Themewinter.

According to their plugin page, "Eventin is the ultimate event manager that comes with with event calendar, event booking, event registrations, and event listings where you can manage event RSVP and sell event tickets in just a few clicks".
The security vulnerability
The plugin suffered from an unauthenticated Privilege Escalation vulnerability. The vulnerability occured in the /wp-json/eventin/v2/speakers/import
REST API endpoint due to the lack of permission check in the endpoint along with the processing of the users roles while importing the user. This vulnerability is fixed in version 4.0.27 and has been tracked with CVE-2025-47539.
The registration of the REST API endpoint looks like this:
register_rest_route( $this->namespace, $this->rest_base . '/import', [
[
'methods' => WP_REST_Server::CREATABLE,
'callback' => [$this, 'import_items'],
'permission_callback' => [$this, 'import_item_permissions_check'],
]
] );
The permission_callback
is set to the function import_item_permissions_check()
. But when we actually check the function, it doesn't check anything and just returns true. It means any unauthenticated user will be able to hit this endpoint.
public function import_item_permissions_check( $request ) {
return true;
}
Analyzing the callback
parameter in the route registration, it is set to the function import_items()
which looks like this:
public function import_items( $request ) {
$data = $request->get_file_params();
$file = ! empty( $data['speaker_import'] ) ? $data['speaker_import'] : '';
if ( ! $file ) {
return new WP_Error( 'empty_file', __( 'You must provide a valid file.', 'eventin' ), ['status' => 409] );
}
$importer = new SpeakerImporter();
$importer->import( $file );
$response = [
'message' => __( 'Successfully imported speaker', 'eventin' ),
];
return rest_ensure_response( $response );
}
The file is read and $importer->import( $file );
is called with the user-input $file
. The import
() function of the class SpeakerImporter
looks like this:
public function import( $file ) {
$this->file = $file;
$file_reader = ReaderFactory::get_reader( $file );
$this->data = $file_reader->read_file();
$this->create_speaker();
}
After reading the file, $this->create_speaker();
is called which marks as the sink for the vulnerability.
private function create_speaker() {
$file_type = ! empty( $this->file['type'] ) ? $this->file['type'] : '';
$rows = $this->data;
foreach( $rows as $row ) {
$speaker = new User_Model();
$social = ! empty( $row['social'] ) ? $row['social'] : '';
$group = ! empty( $row['speaker_group'] ) ? $row['speaker_group'] : '';
if ( 'text/csv' == $file_type ) {
$social = json_decode( $social, true );
$group = json_decode( $group, true );
}
$args = [
'first_name' => ! empty( $row['name'] ) ? $row['name'] : '',
//TRIMMED
'author_url' => ! empty( $row['author_url'] ) ? $row['author_url'] : '',
'role' => ! empty( $row['role'] ) ? $row['role'] : '',
];
$args['user_login'] = $row['email'];
$speaker->create( $args );
}
}
In the create_speaker()
function, role
is also taken as the argument for the user creation.
Taking all the code tracing into account, an unauthenticated user can hit POST /wp-json/eventin/v2/speakers/import
with a CSV file that contains the details of the attacker with role set to administrator
. It results in a user account being created with the set role leading to a full privilege escalation. The attacker just needs to reset the password of the account and access their account to fully compromise the site.
The patch
The vendor implemented a patch for this vulnerability in version 4.0.27 by adding permission check in the import_item_permissions_check()
function along with a whitelist check for the roles of the imported users. The patch can be seen below:


Conclusion
While the permission check using import_item_permissions_check()
function made it look secure on the plain-sight, it was doing nothing but return true allowing unauthenticated users to hit the endpoint. Similarly, the user import functionality allowed role to be set allowing users to create administrator without zero user-interaction.
For hackers, never make assumptions- something that might look secure on the first sight might actually be vulnerable. For developers, it is important to ensure the all the sensitive functionalities are implemented with proper permission checking.
Want to learn more about finding and fixing vulnerabilities?
Explore our Academy to master the art of finding and patching vulnerabilities within the WordPress ecosystem. Dive deep into detailed guides on various vulnerability types, from discovery tactics for researchers to robust fixes for developers. Join us and contribute to our growing knowledge base.
Timeline
Help us make the Internet a safer place
Making the WordPress ecosystem more secure is a team effort, and we believe that plugin developers and security researchers should work together.
- If you're a plugin developer, join our mVDP program that makes it easier to report, manage and address vulnerabilities in your software.
- If you're a security researcher, join Patchstack Alliance to report vulnerabilities & earn rewards.