cve-2017-7494 分析

====================================================================
== Subject:     Remote code execution from a writable share.
==
== CVE ID#:     CVE-2017-7494
==
== Versions:    All versions of Samba from 3.5.0 onwards.
==
== Summary:     Malicious clients can upload and cause the smbd server
==              to execute a shared library from a writable share.
==
====================================================================

CVE-2017-7494

通过观察 github 提交记录,确定产生漏洞位置。

https://github.com/samba-team/samba/commit/02a76d86db0cbe79fcaf1a500630e24d961fa149

5  source3/rpc_server/srv_pipe.c
 @@ -481,6 +481,11 @@ bool is_known_pipename(const char *pipename, struct ndr_syntax_id *syntax)
  {
  	NTSTATUS status;
  
 +	if (strchr(pipename, '/')) {
 +		DEBUG(1, ("Refusing open on pipe %s\n", pipename));
 +		return false;
 +	}
 +
  	if (lp_disable_spoolss() && strequal(pipename, "spoolss")) {
  		DEBUG(10, ("refusing spoolss access\n"));
  		return false;

使用 MSF PoC 复现该漏洞:

https://github.com/hdm/metasploit-framework/blob/0520d7cf76f8e5e654cb60f157772200c1b9e230/modules/exploits/linux/samba/is_known_pipename.rb

调试 SMB 对 is_known_pipename 下断电,触发漏洞:

WX20170525-181651

观察调用:

#11 0x00007f815c4390c1 in __dlopen (
    file=file@entry=0x7f816b8f7950 "/home/shares/anonymous/lqkjzuaX.so",
    mode=mode@entry=2) at dlopen.c:87
#12 0x00007f81618f2eab in load_module (
    path=path@entry=0x7f816b8f7950 "/home/shares/anonymous/lqkjzuaX.so",
    is_probe=is_probe@entry=true, handle_out=handle_out@entry=0x7fff6f529168)
    at ../lib/util/modules.c:40
#13 0x00007f81618f30f9 in do_smb_load_module (
    subsystem=subsystem@entry=0x7f816a026d0d "rpc",
    module_name=module_name@entry=0x7f816b8f7950 "/home/shares/anonymous/lqkjzuaX.so", is_probe=is_probe@entry=true) at ../lib/util/modules.c:188
#14 0x00007f81618f34fe in smb_probe_module (
    subsystem=subsystem@entry=0x7f816a026d0d "rpc",
    module=module@entry=0x7f816b8f7950 "/home/shares/anonymous/lqkjzuaX.so")
    at ../lib/util/modules.c:228
#15 0x00007f8169ef3037 in is_known_pipename (
    pipename=pipename@entry=0x7f816b8f7950 "/home/shares/anonymous/lqkjzuaX.so",
    syntax=syntax@entry=0x7fff6f529230) at ../source3/rpc_server/srv_pipe.c:488

对比源码,samba会动态加载模块:

https://github.com/samba-team/samba/blob/02a76d86db0cbe79fcaf1a500630e24d961fa149/source3/rpc_server/srv_pipe.c#L498

status = smb_probe_module("rpc", pipename);

smb_probe_moduledo_smb_load_module 会调用 load_module 加载 so 文件并执行 init_module_fn 方法

https://github.com/samba-team/samba/blob/306783d6f5d577a0b8bd31d659d8c802f22f0333/lib/util/modules.c#L52

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);

	/* This call should reset any possible non-fatal errors that
	   occurred 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);

	/* 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_INIT_MODULE 的函数的动态链接库即可进行exploit。