
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.
✌️ Our users are protected from this vulnerability. Are yours?
Automatically mitigate vulnerabilities in real-time without changing code.
See pricingIdentify vulnerabilities in your plugins and get recommendations for fixes.
Request auditProtect your users, improve server health and earn additional revenue.
Patchstack for hostsAbout 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
🤝 You can help us make the Internet a safer place
Streamline your disclosure process to fix vulnerabilities faster and comply with CRA.
Get started for freeProtect your users too! Improve server health and earn added revenue with proactive security.
Patchstack for hostsReport vulnerabilities to our gamified bug bounty program to earn monthly cash rewards.
Learn more