内容简介:做过脚本的同学都知道提权的苦楚。平时在做定向渗透,溯源反制的时候,经常遇到进得去,系统却拿不下来的情况。
0x00 背景
做过脚本的同学都知道提权的苦楚。
平时在做定向渗透,溯源反制的时候,经常遇到进得去,系统却拿不下来的情况。
14号看到 Snap < 2.37.1 提权漏洞,测了下,异常好用。在 ubuntu 18.04之后版本默认安装,只需要有文件写入权限和 python 环境,即可完美提权。几个vps一打一个准。
想了想,最末分析过的 linux 平台漏洞还是 “脏牛”。近半年多一直在搞其他方面,许久没做漏洞分析了,正好有个提权漏洞换换脑子。
0x01 linux提权姿势梳理
首先梳理一下 linux 提权的种类。我所知道的所有提权思路有这么几种:
- 内核漏洞利用
- 服务、程序漏洞利用
- 权限配置不当
内核漏洞利用
内核漏洞利用是最常见的提权方式,渗透提权的时候首先想到的就是查看系统版本、内核版本。根据环境找提权 exp。
内核漏洞利用的常规利用方式,有这么几步:
- 通过漏洞将 payload 打入到内核模式下
- 操纵内存数据,比如将用户空间映射到内核空间
- 启动新权限的shell,获得root权限
这种提权方法,需要找到对应内核版本的漏洞利用工具,并且具有运行利用 工具 的能力。
即使能够运行工具,也不一定完全提权成功。许多公开的漏洞利用工具都不稳定。运行提权工具有可能造成目标主机宕机重启。
服务、程序漏洞利用
权限具有继承性,高权限运行的服务、程序,他的执行能力也是高权限。一些web服务,数据库应用,系统服务组件往往都在高权限下运行。
例如,运维人员通常使用root权限运行mysql。这时可以尝试使用 mysql 提权漏洞,将低权限的mysql用户提权至mysql root 权限。
mysql 本身具有 shell 执行环境。系统root身份运行的mysql,其 mysql root 权限接近系统 root。
权限配置不当
这种要具体情况具体分析,常见的比如:
- 弱口令
- suid配置错误
- sudo权限滥用
- 路径配置不当
- 配置不当的Cron jobs 等
这部分,可以参照这篇 blog 。
本次分析的snap提权漏洞,属于root权限运行的服务漏洞。
snapd 在使用api的时候,身份鉴权存在问题,允许地权限用户调用高权限api,从而造成提权。
一篇完整的漏洞分析必须要包括:
- 漏洞背景
- 漏洞成因分析
- 漏洞上下文分析
- 利用方式分析
- 补丁分析
- 漏洞验证
- 安全建议
下面是正文
0x02 漏洞背景
snap是一个Linux系统上的包管理软件。在Ubuntu18.04后默认预安装到了系统中。
snapd 是负责管理本地安装服务与在线应用商店通信的程序,随着snap一起安装,并且在root权限下运行,这是提权的基本条件。
根据官方描述,服务进程snapd中提供的REST API服务对请求客户端身份鉴别存在问题,从而导致了提权。Chris Moberly 已经公开了 细节 。
0x03 漏洞成因分析
漏洞相关的更改 :
漏洞位置在:
func ucrednetGet(remoteAddr string) (pid uint32, uid uint32, socket string, err error) { pid = ucrednetNoProcess uid = ucrednetNobody for _, token := range strings.Split(remoteAddr, ";") { var v uint64 if strings.HasPrefix(token, "pid=") { if v, err = strconv.ParseUint(token[4:], 10, 32); err == nil { pid = uint32(v) } else { break } } else if strings.HasPrefix(token, "uid=") { if v, err = strconv.ParseUint(token[4:], 10, 32); err == nil { uid = uint32(v) } else { break } } if strings.HasPrefix(token, "socket=") { socket = token[7:] } } if pid == ucrednetNoProcess || uid == ucrednetNobody { err = errNoID } return pid, uid, socket, err }
该函数,对 remoteAddr进行分割,标志符为 “;” ,将分割后得到的数组,for 循环。通过 HasPrefix 判别内容,对pid、uid、socket进行赋值。
这里存在一个问题: for循环中,有可能会对变量重复赋值。Split分割后的数组,如果存在多个 uid= 开头的值,则 uid 的值将被后者覆盖。
例如,”uid=1000;pid=1100;uid=0″,通过 ; 进行分割,得到[‘uid=1000′,’pid=1100′,’uid=0’],该数组在迭代的时候:
} else if strings.HasPrefix(token, "uid=") { if v, err = strconv.ParseUint(token[4:], 10, 32); err == nil { uid = uint32(v) } else { break } }
第一次执行到这里的时候,uid被赋值为1000,因为后面还有一个以uid为开头的值(uid=0),所以程序还会进入这个代码段,将uid 重置为0。这是,漏洞形成的基本逻辑。
如果是发漏洞预警,分析到这里已经可以了。但如果是做漏洞研究,还远远不够,还要进行漏洞上下文和利用技术分析。
0x04 漏洞上下文分析
除了找到漏洞成因,还要知道”漏洞从哪来,到哪去”。
从哪来:
漏洞逻辑函数:
func ucrednetGet(remoteAddr string) (pid int32, uid uint32, socket string, err error) { pid = ucrednetNoProcess uid = ucrednetNobody for _, token := range strings.Split(remoteAddr, ";") { var v uint64 ......
漏洞处理函数,ucrednetGet() ,传入变量为 remoteAddr,该变量即是Split处理对象。则查找该函数调用关系。
可以看到有n多调用,在api.go 文件中,有丰富逻辑代码。随进入分析。
ucrednetGet() 被重命名为 postCreateUserUcrednetGet() 和 runSnapctlUcrednetGet(), 查看调用逻辑:
func getUsers(c *Command, r *http.Request, user *auth.UserState) Response { _, uid, _, err := postCreateUserUcrednetGet(r.RemoteAddr) if err != nil { return BadRequest("cannot get ucrednet uid: %v", err) } if uid != 0 { return BadRequest("cannot get users as non-root") } ......
postCreateUserUcrednetGet() 传入的参数为 r.RemoteAddr 。r 为 http.Request对象。由此可得,漏洞逻辑代码,处理的对象来自,http.Request.RemoteAddr ,即:
传入漏洞逻辑函数 ucrednetGet() 的参数 remoteAddr 为 http.Request.RemoteAddr。
查了下,http.Request.RemoteAddr 为 go 内建结构,之后查看 go 代码 。
这里,分析了go中整个 SockaddrUnix 调用过程。这里只简单写下要点:
- coon.go:123 声明 RemoteAddr(),调用Conn.conn.RemoteAddr()
- coon.go:27 声明结构体 Conn,其中 conn 为 net.Conn
- net.go:221 声明net.conn.RemoteAddr(),返回c.fd.raddr,c 为 conn指针
- net.go:164 声明conn结构体,fd 为 netFD 指针
- fd_unix.go:19 声明 netFD 结构体。
- fd_unix.go:45 声明 setAddr 函数,对 netFD.raddr进行赋值, 此处即为漏洞传入参数 RemoteAddr,首次声明位置。 找到这里还不够,我们需要知道这个传入的值,究竟从哪来的。
- file_unix.go:66 调用 setAddr() :fd.setAddr(laddr, raddr),第二个参数,是我们需找的。
- file_unix.go:60 设置raddr:addr := fd.addrFunc()(rsa)
- sock_posi.go:92 声明 addrFunc(),可以看到根据套接字族设定进行不同的操作,返回sockaddrToXXX
func (fd *netFD) addrFunc() func(syscall.Sockaddr) Addr { switch fd.family { case syscall.AF_INET, syscall.AF_INET6: switch fd.sotype { case syscall.SOCK_STREAM: return sockaddrToTCP case syscall.SOCK_DGRAM: return sockaddrToUDP case syscall.SOCK_RAW: return sockaddrToIP } case syscall.AF_UNIX: switch fd.sotype { case syscall.SOCK_STREAM: return sockaddrToUnix case syscall.SOCK_DGRAM: return sockaddrToUnixgram case syscall.SOCK_SEQPACKET: return sockaddrToUnixpacket } } return func(syscall.Sockaddr) Addr { return nil } }
- 查阅 资料 ,原来 AF_UNIX 用于进程间通信,绑定的文件,可以通过 sockaddrToUnix 取得。下面是说明:
……. Address format A UNIX domain socket address is represented in the following structure: struct sockaddr_un { sa_family_t sun_family; /* AF_UNIX */ char sun_path[108]; /* pathname */ }; The sun_family field always contains AF_UNIX. On Linux sun_path is 108 bytes in size; see also NOTES, below. Various systems calls (for example, bind(2), connect(2), and sendto(2)) take a sockaddr_un argument as input. Some other system calls (for example, getsockname(2), getpeername(2), recvfrom(2), and accept(2)) return an argument of this type. Three types of address are distinguished in the sockaddr_un struc‐ ture: * pathname: a UNIX domain socket can be bound to a null-terminated filesystem pathname using bind(2). When the address of a pathname socket is returned (by one of the system calls noted above), its length is offsetof(struct sockaddr_un, sun_path) + strlen(sun_path) + 1 and sun_path contains the null-terminated pathname. (On Linux, the above offsetof() expression equates to the same value as sizeof(sa_family_t), but some other implementations include other fields before sun_path, so the offsetof() expression more portably describes the size of the address structure.) For further details of pathname sockets, see below. ……
- unixsock_posix.go:52 定义了 sockaddrToUnix(),可以看到,是通过 syscall.SockaddrUnix获得的绑定文件名。
分析到这里,RemoteAddr 怎么来的我们算整明白了:根据不同的套接字族,返回不同的地址。如果是通过 AF_UNIX 创建的套接字,将返回绑定的文件名。
那么,哪里调用了存在漏洞的函数?该漏洞有多大影响呢?
之前看到,漏洞函数在api.go 中进行调用:
ucrednetGet 重命名为 postCreateUserUcrednetGet, 在postCreateUser有调用:
...... func postCreateUser(c *Command, r *http.Request, user *auth.UserState) Response { _, uid, _, err := postCreateUserUcrednetGet(r.RemoteAddr) if err != nil { return BadRequest("cannot get ucrednet uid: %v", err) } if uid != 0 { return BadRequest("cannot use create-user as non-root") } ......
而该函数,对应的是创建本地用户的API:
...... createUserCmd = &Command{ Path: "/v2/create-user", POST: postCreateUser, } ......
了解下 snap API:
功能是创建本地用户,使用权限是root。结合漏洞会将uid 覆盖为 0(root)的可能,则该漏洞可以通过调用api,创建用户,如果 sudoer 设置为true,则创建的为特权用户。
0x05 漏洞利用分析
之上,将漏洞分析的明明白白。此时其实可以自己写出exp:
- 创建 AF_UNIX 族套接字
- 绑定一个文件,文件名为;uid=0,“;”用于截取字符串,获取覆盖uid的能力。
- 调用API,且sudoer 设为true
- snapd在鉴权的时候会获取远程地址,如果是 AF_UNIX 类型套接字。将返回绑定的文件,触发漏洞。
- 鉴权的到uid=0,认为是root权限调用,执行生成本地用户操作,且调用API,且sudoer=true,则生成的用户具有特权。
漏洞作者给的 exp ,确实是这么写的。
0x06 补丁分析
漏洞修补的很粗暴,之前:
return fmt.Sprintf("pid=%s;uid=%s;socket=%s;%s", wa.pid, wa.uid, wa.socket, wa.Addr)
现在定义了一个结构体 ucrednet ,并且现在
return fmt.Sprintf("pid=%d;uid=%d;socket=%s;", un.pid, un.uid, un.socket)
不再返回 wa.Addr ,即不再处理远程连接地址。通过 AF_UNIX 套接字向RemoteAddr 注入文本已经行不通。从而修补了漏洞。
0x07 漏洞验证
漏洞十分的好用,snap < 2.37.1 以下版本均受影响。
因为在ubuntu 18.04 以后版本,默认安装 snap ,并且测试时发现,一些vps 供应商 Ubuntu 16.04 同样默认安装snap。
以下vps服务商的 ubuntu 安装镜像均存在问题:
- 腾讯云
- 谷歌云
- 亚马逊云
- vultr
- 搬瓦工
- ……
除了阿里云外,一打一个准。阿里云ubuntu 镜像中,不带有snap,是我测的主机中,唯一不受漏洞影响的云服务商。
0x08 安全建议
修补很简单,将 snap 升级到最新版就好了。
有 ubuntu vps 的同学,建议查看一下自己主机上snap的版本。
0x09 后记
很多漏洞作者,都会公布漏洞详情。建议做漏洞分析的同学,不要先去看漏洞详情。成长的过程在于对漏洞的摸索。
拿着分析文章,看一步调一步。没有太大意义,沉淀不了自己的经验。
本次分析的snap漏洞,唯一卡住的地方,是套接字那里。去看漏洞作者详细分析,才知道原来还有 AF_UNIX 用于本地进程通信。这个是我知识盲区,卡在这确实没办法。
ps:其实整篇下来,最难得部分是逆 go 的net标准库。(笑
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 使用动态分析技术分析 Java
- 使用动态分析技术分析 Java
- 案例分析:如何进行需求分析?
- 深度分析ConcurrentHashMap原理分析
- 如何分析“数据分析师”的岗位?
- EOS源码分析(3)案例分析
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
機器,平台,群眾
安德魯‧麥克費(Andrew McAfee)、艾瑞克‧布林優夫森(Erik Brynjolfsson) / 李芳齡 / 天下文化 / 2017-12-27 / TWD550
★★Amazon.com商業理財Top1 ★★ 全球暢銷書《第二次機器時代》作者最新力作 兩位MIT數位頂尖科學家歷時三年時間 走訪矽谷、華府、劍橋、紐約、倫敦、舊金山等科技政經重鎮 拜會許多領域精英進行交流,結合宏觀趨勢觀察, 指出人人都應關注的三重革命 科技正以空前速度改變每個產業及每個人的生活, 你該如何做,才能保持領先? 我們生活在一個奇特的......一起来看看 《機器,平台,群眾》 这本书的介绍吧!