CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2016-1000217
9.811.40%

Zotpress SQLi: When Academic Citation Becomes Database Exfiltration

Alon Barad
Alon Barad
Software Engineer

Feb 26, 2026·7 min read·9 visits

PoC Available

Executive Summary (TL;DR)

Unauthenticated SQL injection in Zotpress plugin versions <= 6.1.2 allows attackers to dump the WordPress database by manipulating the `api_user_id` parameter in AJAX requests. Rated Critical (CVSS 9.8). Fixed in version 6.2.

A critical SQL Injection vulnerability in the Zotpress WordPress plugin allows unauthenticated attackers to execute arbitrary SQL commands via the `api_user_id` parameter. This flaw, stemming from improper input sanitization in AJAX handling, exposes the entire WordPress database to theft and potential site takeover.

The Hook: Academic rigor meets loose code

In the world of academia, citing your sources is paramount. If you don't verify where your information comes from, you get expelled. Ironically, the Zotpress plugin—a tool designed specifically to help academics display Zotero citations on WordPress—failed its own peer review in the most spectacular way possible. It didn't verify its sources.

We are looking at CVE-2016-1000217, a vulnerability that is practically a museum piece of bad coding practices. It's a classic, unauthenticated SQL Injection (SQLi) that affects every version of the plugin up to 6.1.2. This isn't some complex heap corruption that requires a PhD in memory management to understand. This is the digital equivalent of leaving your front door unlocked because you trust your neighbors. Except your neighbors are the entire internet.

The vulnerable component is the AJAX handler used to fetch account details. In an effort to make the user interface snappy and responsive, the developers opened a direct pipeline between unauthenticated HTTP requests and the backend database. They forgot the golden rule of web security: Input is Evil. By failing to sanitize a simple ID parameter, they turned a citation tool into a database administration tool for attackers.

The Flaw: A Failure to Prepare

The root cause of this vulnerability lies deep within lib/shortcode/shortcode.ajax.php. The specific function at fault is zp_get_account(). Its job is simple: take a user ID, look it up in the database, and return the associated Zotero API credentials. The problem is how it performs this lookup.

PHP and WordPress provide a robust mechanism for handling database queries securely: the $wpdb->prepare() method. It acts as a bouncer, ensuring that data is data and commands are commands. The developer of Zotpress, however, decided to bypass the bouncer. They took the raw input directly from the global $_GET array and concatenated it straight into the SQL query string.

This is the coding equivalent of drinking directly from a swamp. The code blindly trusts that $_GET['api_user_id'] will contain a nice, safe integer. But because HTTP requests can be forged by anyone with a terminal, an attacker can put anything in there. Since the application doesn't use parameterized queries, the database engine can't distinguish between the intended ID and malicious SQL commands appended to it. The logic flow is a straight line to disaster: User Input -> String Concatenation -> Database Execution.

The Code: The Smoking Gun

Let's dissect the vulnerable logic. While I won't paste the entire file, the conceptual flaw looks exactly like the textbook examples we use to teach junior pentesters about SQLi.

The Vulnerable Code (Conceptual):

// lib/shortcode/shortcode.ajax.php
 
// 1. Retrieve the input directly from the request
$api_user_id = $_GET['api_user_id']; 
 
// 2. Concatenate it into the query. THIS IS DEADLY.
$query = "SELECT * FROM " . $wpdb->prefix . "zotpress WHERE api_user_id = '" . $api_user_id . "'";
 
// 3. Execute the query
$results = $wpdb->get_results($query);

Do you see the horror? If $api_user_id is 1, the query is fine. But if $api_user_id is 1' OR 1=1 --, the query becomes SELECT * FROM wp_zotpress WHERE api_user_id = '1' OR 1=1 --'. The -- comments out the rest of the original query (the closing quote), and OR 1=1 makes the condition always true.

The Fix:

The patch was remarkably simple, which makes the existence of the bug even more frustrating. The developer simply needed to ensure the input was an integer before using it. This is often done using intval() or, even better, using WordPress's built-in protection.

// The Secure Approach
$api_user_id = intval($_GET['api_user_id']); // Cast to integer immediately
// OR use prepare()
$query = $wpdb->prepare("SELECT * FROM " . $wpdb->prefix . "zotpress WHERE api_user_id = %d", $api_user_id);

By forcing the type to be an integer (%d), any SQL commands an attacker tries to inject are either stripped out or treated as a literal number 0, neutralizing the attack.

The Exploit: Dumping the Database

Exploiting this is trivially easy. We don't need authentication. We don't need a user account. We just need to hit the admin-ajax.php endpoint with the right action. The zp_shortcode_ajax action hooks into the vulnerable code.

The Attack Chain:

  1. Reconnaissance: Identify a WordPress site running Zotpress. You can usually tell by looking at the page source for wp-content/plugins/zotpress.
  2. Injection: Craft a GET request. We use a Union-Based SQL injection to append the results of our own malicious query to the legitimate results.

The Payload:

GET /wp-admin/admin-ajax.php?action=zp_shortcode_ajax&api_user_id=-1 UNION SELECT 1, user_login, user_pass, 4, 5 FROM wp_users -- - HTTP/1.1
Host: target-university.edu

Here is what happens:

  • We pass -1 as the ID to ensure the first part of the query returns nothing (unless there is a user -1, which is unlikely).
  • UNION SELECT joins our results to the empty set.
  • We select user_login and user_pass from the wp_users table.
  • We fill the other columns (1, 4, 5) with dummy data to match the column count of the original zotpress table query.

The server, thinking it's returning Zotero account details, will dutifully respond with a JSON or HTML blob containing the admin username and their hashed password.

The Impact: From Citation to Domination

Why is this a CVSS 9.8? Because it's the keys to the kingdom. In the context of WordPress, an SQL Injection is rarely just about reading data—it's about total site compromise.

Once I have the admin password hash from wp_users, I have two paths. If it's a weak password, I crack it offline. If I can't crack it, I might be able to use the SQL injection to modify the database (if the database user has sufficient privileges, though get_results is usually read-focused, successful SQLi often implies broader issues).

However, reading is usually enough. With admin credentials, I log in. Once logged in as an admin on WordPress, I can edit theme files or upload a new plugin. This allows me to execute PHP code on the server. That means Remote Code Execution (RCE).

From there, I own the web server. I can pivot to the internal network, install ransomware, or—perhaps most damaging for a university—alter research data, deface the site, or steal intellectual property. All because one variable wasn't cast to an integer.

The Fix: Sanitize Your Inputs

The remediation here is straightforward: Update. The plugin developer patched this in version 6.2. If you are running Zotpress 6.1.2 or older, you are currently exposing your database to the world.

For Developers: This serves as a critical reminder. Never trust $_GET, $_POST, or $_COOKIE. Always assume the user is trying to destroy you.

  • Use Prepared Statements ($wpdb->prepare in WordPress, PDO in raw PHP).
  • Use Type Casting (intval()) when you expect numbers.
  • Use Input Validation libraries.

If you cannot update the plugin immediately (why?), you can mitigate this at the WAF level. A Web Application Firewall rule blocking the string zp_shortcode_ajax combined with common SQL keywords like UNION or SELECT in the query string will stop most script kiddies. But really, just update the plugin.

Official Patches

WordPress Plugin RepositoryOfficial Changelog noting the fix in 6.2

Technical Appendix

CVSS Score
9.8/ 10
CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
EPSS Probability
11.40%
Top 7% most exploited

Affected Systems

WordPressZotpress Plugin

Affected Versions Detail

Product
Affected Versions
Fixed Version
Zotpress
Zotpress Project
<= 6.1.26.2
AttributeDetail
CWE IDCWE-89 (SQL Injection)
CVSS v3.09.8 (Critical)
Attack VectorNetwork (AV:N)
Privileges RequiredNone (PR:N)
EPSS Score0.11 (11%)
Exploit StatusProof of Concept (PoC) Available

MITRE ATT&CK Mapping

T1190Exploit Public-Facing Application
Initial Access
T1059Command and Scripting Interpreter
Execution
T1505.003Server Software Component: Web Shell
Persistence
CWE-89
Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')

The software constructs all or part of an SQL command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended SQL command when it is sent to a downstream component.

Known Exploits & Detection

WordPress Support ForumOriginal disclosure and discussion of the payload

Vulnerability Timeline

Vulnerability reported by uberspot on support forums
2016-05-01
Issue resolved by developer
2016-08-31
CVE-2016-1000217 Published
2016-10-06

References & Sources

  • [1]NVD - CVE-2016-1000217 Detail
  • [2]CISA Vulnerability Summary SB16-284

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.