2017-11-13 14:58:33
阅读:1256次
点赞(0)
收藏
来源: edoverflow.com

作者:興趣使然的小胃

译者:興趣使然的小胃
预估稿费:100RMB
投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿
一、简介
我在Ruby的Resolv::getaddresses中发现了一个漏洞,利用这个漏洞,攻击者可以绕过多个SSRF(Server-Side Request Forgery,服务端请求伪造)过滤器。诸如GitLab以及HackerOne之类的应用程序会受此漏洞影响。这份公告中披露的所有报告细节均遵循HackerOne的漏洞披露指南。
此漏洞编号为CVE-2017-0904。
二、漏洞细节
Resolv::getaddresses的执行结果与具体的操作系统有关,因此输入不同的IP格式时,该函数可能会返回空值。在防御SSRF攻击时,常用的方法是使用黑名单机制,而利用这个漏洞可以绕过这种机制。
实验环境为:
环境1:ruby 2.3.3p222 (2016-11-21) [x86_64-linux-gnu] 环境2:ruby 2.3.1p112 (2016-04-26) [x86_64-linux-gnu]在环境1中的实验结果如下所示:
irb(main):002:0>Resolv.getaddresses("127.0.0.1") =>["127.0.0.1"] irb(main):003:0>Resolv.getaddresses("localhost") =>["127.0.0.1"] irb(main):004:0>Resolv.getaddresses("127.000.000.1") =>["127.0.0.1"]在环境2中的实验结果如下所示:
irb(main):008:0>Resolv.getaddresses("127.0.0.1") =>["127.0.0.1"] irb(main):009:0>Resolv.getaddresses("localhost") =>["127.0.0.1"] irb(main):010:0>Resolv.getaddresses("127.000.000.1") =>[]在最新稳定版的Ruby中我们也能复现这个问题:
$ruby-v ruby2.4.3p201(2017-10-11revision60168)[x86_64-linux] $irb irb(main):001:0>require'resolv' =>true irb(main):002:0>Resolv.getaddresses("127.000.001") =>[]三、PoC
irb(main):001:0>require'resolv' =>true irb(main):002:0>uri="0x7f.1" =>"0x7f.1" irb(main):003:0>server_ips=Resolv.getaddresses(uri) =>[]#Thebug! irb(main):004:0>blocked_ips=["127.0.0.1","::1","0.0.0.0"] =>["127.0.0.1","::1","0.0.0.0"] irb(main):005:0>(blocked_ips&server_ips).any? =>false#Bypass
四、根本原因
接下来我们来分析导致这个漏洞的根本原因。
我在代码片段中添加了一些注释语句,以便读者理顺代码逻辑。
getaddresses函数的输入参数(name)为待解析的某个地址,在函数内部,该参数会传递给each_address函数。
#Filelib/resolv.rb,line100 defgetaddresses(name) ret=[] each_address(name){|address|ret<<address}#Here! returnret endeach_address函数内部通过@resolvers来处理name。
#Filelib/resolv.rb,line109 defeach_address(name) ifAddressRegex=~name yieldname return end yielded=false @resolvers.each{|r|#Here! r.each_address(name){|address| yieldaddress.to_s yielded=true } returnifyielded } end #Filelib/resolv.rb,line109 definitialize(resolvers=[Hosts.new,DNS.new]) @resolvers=resolvers end进一步跟下去,initialize实际的初始化代码如下所示(我保留了源代码中的注释语句,这些语句能提供许多有价值的信息):
#Filelib/resolv.rb,line308 ## #CreatesanewDNSresolver. # #+config_info+canbe: # #nil::Uses/etc/resolv.conf. #String::Pathtoafileusing/etc/resolv.conf'sformat. #Hash::Mustcontain:nameserver,:searchand:ndotskeys. #:nameserver_portcanbeusedtospecifyportnumberofnameserveraddress. # #Thevalueof:nameservershouldbeanaddressstringor #anarrayofaddressstrings. #-:nameserver=>'8.8.8.8' #-:nameserver=>['8.8.8.8','8.8.4.4'] # #Thevalueof:nameserver_portshouldbeanarrayof #pairofnameserveraddressandportnumber. #-:nameserver_port=>[['8.8.8.8',53],['8.8.4.4',53]] # #Example: # #Resolv::DNS.new(:nameserver=>['210.251.121.21'], #:search=>['ruby-lang.org'], #:ndots=>1) #Setto/etc/resolv.conf\_(ツ)_/ definitialize(config_info=nil) @mutex=Thread::Mutex.new @config=Config.new(config_info) @initialized=nil end这些代码表明,Resolv::getaddresses的执行结果与具体操作系统有关,当输入不常见的IP编码格式时,getaddresses就会返回一个空的ret值。
五、缓解措施
我建议弃用Resolv::getaddresses,选择Socket库。
irb(main):002:0>Resolv.getaddresses("127.1") =>[] irb(main):003:0>Socket.getaddrinfo("127.1",nil).sample[3] =>"127.0.0.1"Ruby Core开发团队也给出了相同的建议:
“如果待解析地址由操作系统的解析器负责解析,那么检查地址的正确方式是使用操作系统的解析器,而非使用resolv.rb。比如,我们可以使用socket库的Addrinfo.getaddrinfo函数。
——Tanaka Akira”
%ruby-rsocket-e' as=Addrinfo.getaddrinfo("192.168.0.1",nil) pas pas.map{|a|a.ipv4_private?} ' [#<Addrinfo:192.168.0.1TCP>,#<Addrinfo:192.168.0.1UDP>,#<Addrinfo:192.168.0.1SOCK_RAW>] [true,true,true]六、受影响的应用及gem
6.1 GitLab社区版及企业版
相关报告请参考此处链接。
Mustafa Hasan在提交给HackerOne的报告中描述了GitLab的一个SSRF漏洞,利用本文介绍的这个漏洞,可以轻松绕过前面的补丁。GitLab引入了一个排除列表(即黑名单),但会先使用Resolv::getaddresses来解析用户提供的地址,然后将解析结果与排除列表中的值进行比较。这意味着用户再也不能使用诸如http://127.0.0.1以及http://localhost/这样的地址,这些地址正是Mustafa Hasan在原始报告中提到的地址。绕过排除列表限制后,我就可以扫描GitLab的内部网络。



GitLab提供了新的补丁:
https://about.gitlab.com/2017/11/08/gitlab-10-dot-1-dot-2-security-release/
6.2 private_address_check
相关报告请参考此处链接。
private_address_check是John Downey开发的一个Ruby gem,可以用来防止SSRF攻击。真正的过滤代码位于lib/private_address_check.rb文件中。private_address_check的工作原理是先使用Resolv::getaddresses来解析用户提供的URL地址,然后将返回值与黑名单中的值进行对比。这种场景中,我可以使用GitLab案例中用过的技术再一次绕过这个过滤器。
#Filelib/private_address_check.rb,line32 defresolves_to_private_address?(hostname) ips=Resolv.getaddresses(hostname) ips.any?do|ip| private_address?(ip) end endHackerOne在“Integrations”页面中使用了private_address_check来防止SSRF攻击,因此HackerOne也会受这种绕过技术影响。
该页面地址为:
https://hackerone.com/{BBP}/integrations



不幸的是,我无法利用这个SSRF漏洞,因此这个问题只是一个过滤器绕过问题。HackerOne还是鼓励我提交问题报告,因为他们会把任何潜在的安全问题纳入考虑范围,而这个绕过技术正好落在这类问题中。
private_address_check在0.4.0版中修复了这个漏洞。
七、不受影响的应用及gem
7.1 ssrf_filter
Arkadiy Tetelman开发的ssrf_filter不受此漏洞影响,因为这个gem会检查返回的值是否为空。
#Filelib/ssrf_filter/ssrf_filter.rb,line116 raiseUnresolvedHostname,"Couldnotresolvehostname'#{hostname}'"ifip_addresses.empty? irb(main):001:0>require'ssrf_filter' =>true irb(main):002:0>SsrfFilter.get("http://127.1/") SsrfFilter::UnresolvedHostname:Couldnotresolvehostname'127.1' from/var/lib/gems/2.3.0/gems/ssrf_filter-1.0.2/lib/ssrf_filter/ssrf_filter.rb:116:in`block(3levels)in<class:SsrfFilter>' from/var/lib/gems/2.3.0/gems/ssrf_filter-1.0.2/lib/ssrf_filter/ssrf_filter.rb:107:in`times' from/var/lib/gems/2.3.0/gems/ssrf_filter-1.0.2/lib/ssrf_filter/ssrf_filter.rb:107:in`block(2levels)in<class:SsrfFilter>' from(irb):2 from/usr/bin/irb:11:in`<main>'7.2 faraday-restrict-ip-addresses
Ben Lavender开发的faraday-restrict-ip-addresses也不受此漏洞影响,其遵循了Ruby Code开发团队提供的建议。
#Filelib/faraday/restrict_ip_addresses.rb,line61 defaddresses(hostname) Addrinfo.getaddrinfo(hostname,nil,:UNSPEC,:STREAM).map{|a|IPAddr.new(a.ip_address)} rescueSocketError=>e #Incaseofinvalidhostname,returnanemptylistofaddresses [] end八、总结
感谢Tom Hudson以及Yasin Soliman在挖掘这个漏洞过程中提供的帮助。John Downey以及Arkadiy Tetelman的反应都非常敏锐。John Downey第一时间提供了修复补丁,Arkadiy Tetelman帮我理清了为何他们开发的gem不受此问题影响。


本文由 安全客 翻译,转载请注明“转自安全客”,并附上链接。
原文链接:https://edoverflow.com/2017/ruby-resolv-bug/