Exemple #1
0
 def _verify(self, check, actual, expected):
     log_header = "Session: %d, Tree: %d"\
                  % (self.session.session_id, self.tree_connect_id)
     if actual != expected:
         raise SMBException("%s - Secure negotiate failed to verify %s, "
                            "Actual: %s, Expected: %s" %
                            (log_header, check, actual, expected))
Exemple #2
0
    def read(self,
             offset,
             length,
             min_length=0,
             unbuffered=False,
             wait=True,
             send=True):
        """
        Reads from an opened file or pipe

        Supports out of band send function, call this function with send=False
        to return a tuple of (SMB2ReadRequest, receive_func) instead of
        sending the the request and waiting for the response. The receive_func
        can be used to get the response from the server by passing in the
        Request that was used to sent it out of band.

        :param offset: The offset to start the read of the file.
        :param length: The number of bytes to read from the offset.
        :param min_length: The minimum number of bytes to be read for a
            successful operation.
        :param unbuffered: Whether to the server should cache the read data at
            intermediate layers, only value for SMB 3.0.2 or newer
        :param wait: If send=True, whether to wait for a response if
            STATUS_PENDING was received from the server or fail.
        :param send: Whether to send the request in the same call or return the
            message to the caller and the unpack function
        :return: A byte string of the bytes read
        """
        if length > self.connection.max_read_size:
            raise SMBException("The requested read length %d is greater than "
                               "the maximum negotiated read size %d" %
                               (length, self.connection.max_read_size))

        read = SMB2ReadRequest()
        read['length'] = length
        read['offset'] = offset
        read['minimum_count'] = min_length
        read['file_id'] = self.file_id
        read['padding'] = b"\x50"

        if unbuffered:
            if self.connection.dialect < Dialects.SMB_3_0_2:
                raise SMBUnsupportedFeature(self.connection.dialect,
                                            Dialects.SMB_3_0_2,
                                            "SMB2_READFLAG_READ_UNBUFFERED",
                                            True)
            read['flags'].set_flag(ReadFlags.SMB2_READFLAG_READ_UNBUFFERED)

        if not send:
            return read, self._read_response

        log.info("Session: %s, Tree Connect ID: %s - sending SMB2 Read "
                 "Request for file %s" %
                 (self.tree_connect.session.username,
                  self.tree_connect.share_name, self.file_name))
        log.debug(str(read))
        request = self.connection.send(read,
                                       self.tree_connect.session.session_id,
                                       self.tree_connect.tree_connect_id)
        return self._read_response(request, wait)
Exemple #3
0
 def test_exception(self):
     with pytest.raises(SMBException) as exc:
         raise SMBException("smb error")
     assert str(exc.value) == "smb error"
Exemple #4
0
    def write(self, data, offset=0, write_through=False, unbuffered=False,
              wait=True, send=True):
        """
        Writes data to an opened file.

        Supports out of band send function, call this function with send=False
        to return a tuple of (SMBWriteRequest, receive_func) instead of
        sending the the request and waiting for the response. The receive_func
        can be used to get the response from the server by passing in the
        Request that was used to sent it out of band.

        :param data: The bytes data to write.
        :param offset: The offset in the file to write the bytes at
        :param write_through: Whether written data is persisted to the
            underlying storage, not valid for SMB 2.0.2.
        :param unbuffered: Whether to the server should cache the write data at
            intermediate layers, only value for SMB 3.0.2 or newer
        :param wait: If send=True, whether to wait for a response if
            STATUS_PENDING was received from the server or fail.
        :param send: Whether to send the request in the same call or return the
            message to the caller and the unpack function
        :return: The number of bytes written
        """
        data_len = len(data)
        if data_len > self.connection.max_write_size:
            raise SMBException("The requested write length %d is greater than "
                               "the maximum negotiated write size %d"
                               % (data_len, self.connection.max_write_size))

        write = SMB2WriteRequest()
        write['length'] = len(data)
        write['offset'] = offset
        write['file_id'] = self.file_id
        write['buffer'] = data

        if write_through:
            if self.connection.dialect < Dialects.SMB_2_1_0:
                raise SMBUnsupportedFeature(self.connection.dialect,
                                            Dialects.SMB_2_1_0,
                                            "SMB2_WRITEFLAG_WRITE_THROUGH",
                                            True)
            write['flags'].set_flag(WriteFlags.SMB2_WRITEFLAG_WRITE_THROUGH)

        if unbuffered:
            if self.connection.dialect < Dialects.SMB_3_0_2:
                raise SMBUnsupportedFeature(self.connection.dialect,
                                            Dialects.SMB_3_0_2,
                                            "SMB2_WRITEFLAG_WRITE_UNBUFFERED",
                                            True)
            write['flags'].set_flag(WriteFlags.SMB2_WRITEFLAG_WRITE_UNBUFFERED)

        if not send:
            return write, self._write_response

        log.info("Session: %s, Tree Connect: %s - sending SMB2 Write Request "
                 "for file %s" % (self.tree_connect.session.username,
                                  self.tree_connect.share_name,
                                  self.file_name))
        log.debug(str(write))
        request = self.connection.send(write,
                                       self.tree_connect.session.session_id,
                                       self.tree_connect.tree_connect_id)
        return self._write_response(request, wait)
Exemple #5
0
    def connect(self):
        log.debug("Decoding SPNEGO token containing supported auth mechanisms")
        context = spnego.client(self.username,
                                self.password,
                                service='cifs',
                                hostname=self.connection.server_name,
                                options=spnego.NegotiateOptions.session_key)

        self.connection.preauth_session_table[self.session_id] = self
        in_token = self.connection.gss_negotiate_token

        while not context.complete or not in_token:
            try:
                out_token = context.step(in_token)
            except spnego.exceptions.SpnegoError as err:
                raise SMBAuthenticationError(
                    "Failed to authenticate with server: %s" %
                    str(err.message))

            if not out_token:
                break

            session_setup = SMB2SessionSetupRequest()
            session_setup[
                'security_mode'] = self.connection.client_security_mode
            session_setup['buffer'] = out_token

            log.info("Sending SMB2_SESSION_SETUP request message")
            request = self.connection.send(session_setup,
                                           sid=self.session_id,
                                           credit_request=256)

            log.info("Receiving SMB2_SESSION_SETUP response message")
            try:
                response = self.connection.receive(request)
            except MoreProcessingRequired as exc:
                mid = request.message['message_id'].get_value()
                del self.connection.outstanding_requests[mid]
                response = exc.header

            # If this is the first time we received the actual session_id, update the preauth table with the server
            # assigned id.
            session_id = response['session_id'].get_value()
            if self.session_id < 0:
                del self.connection.preauth_session_table[self.session_id]
                self.connection.preauth_session_table[session_id] = self

            self.session_id = session_id

            setup_response = SMB2SessionSetupResponse()
            setup_response.unpack(response['data'].get_value())

            in_token = setup_response['buffer'].get_value()

            status = response['status'].get_value()
            if status == NtStatus.STATUS_MORE_PROCESSING_REQUIRED:
                log.info("More processing is required for SMB2_SESSION_SETUP")
                preauth_value = self.connection.preauth_session_table.pop(
                    response['message_id'].get_value())
                self.preauth_integrity_hash_value.append(preauth_value)

        log.info("Setting session id to %s" % self.session_id)
        self._connected = True

        # Move the session from the preauth table to the actual session table.
        self.connection.session_table[
            self.session_id] = self.connection.preauth_session_table.pop(
                self.session_id)

        # session_key is the first 16 bytes, padded 0 if less than 16
        self.session_key = context.session_key[:16].ljust(16, b"\x00")

        if self.connection.dialect >= Dialects.SMB_3_1_1:
            preauth_hash = b"\x00" * 64
            hash_al = self.connection.preauth_integrity_hash_id
            for hash_list in [
                    self.connection.preauth_integrity_hash_value,
                    self.preauth_integrity_hash_value
            ]:
                for message in hash_list:
                    preauth_hash = hash_al(preauth_hash + message).digest()

            self.signing_key = self._smb3kdf(self.session_key,
                                             b"SMBSigningKey\x00",
                                             preauth_hash)
            self.application_key = self._smb3kdf(self.session_key,
                                                 b"SMBAppKey\x00",
                                                 preauth_hash)
            self.encryption_key = self._smb3kdf(self.session_key,
                                                b"SMBC2SCipherKey\x00",
                                                preauth_hash)
            self.decryption_key = self._smb3kdf(self.session_key,
                                                b"SMBS2CCipherKey\x00",
                                                preauth_hash)

        elif self.connection.dialect >= Dialects.SMB_3_0_0:
            self.signing_key = self._smb3kdf(self.session_key,
                                             b"SMB2AESCMAC\x00",
                                             b"SmbSign\x00")
            self.application_key = self._smb3kdf(self.session_key,
                                                 b"SMB2APP\x00", b"SmbRpc\x00")
            self.encryption_key = self._smb3kdf(self.session_key,
                                                b"SMB2AESCCM\x00",
                                                b"ServerIn \x00")
            self.decryption_key = self._smb3kdf(self.session_key,
                                                b"SMB2AESCCM\x00",
                                                b"ServerOut\x00")

        else:
            self.signing_key = self.session_key
            self.application_key = self.session_key

        flags = setup_response['session_flags']
        if flags.has_flag(SessionFlags.SMB2_SESSION_FLAG_ENCRYPT_DATA
                          ) or self.require_encryption:
            # make sure the connection actually supports encryption
            if not self.connection.supports_encryption:
                raise SMBException(
                    "SMB encryption is required but the connection does not support it"
                )

            self.encrypt_data = True
            self.signing_required = False  # encryption covers signing

        else:
            self.encrypt_data = False

        if flags.has_flag(SessionFlags.SMB2_SESSION_FLAG_IS_GUEST) or \
                flags.has_flag(SessionFlags.SMB2_SESSION_FLAG_IS_NULL):
            self.session_key = None
            self.signing_key = None
            self.application_key = None
            self.encryption_key = None
            self.decryption_key = None

            if self.signing_required or self.encrypt_data:
                self.session_id = None
                raise SMBException(
                    "SMB encryption or signing was required but session was authenticated as a guest "
                    "which does not support encryption or signing")

        if self.signing_required:
            log.info(
                "Verifying the SMB Setup Session signature as auth is successful"
            )
            self.connection.verify_signature(response,
                                             self.session_id,
                                             force=True)
Exemple #6
0
    def connect(self):
        log.debug("Decoding SPNEGO token containing supported auth mechanisms")
        token, rdata = decoder.decode(self.connection.gss_negotiate_token,
                                      asn1Spec=InitialContextToken())
        server_mechs = list(
            token['innerContextToken']['negTokenInit']['mechTypes']
        )
        if MechTypes.MS_KRB5 in server_mechs and MechTypes.KRB5:
            log.debug("Both MS_KRB5 and KRB5 received in the initial SPNGEO "
                      "token, removing MS_KRB5 to avoid duplication of work")
            server_mechs.remove(MechTypes.MS_KRB5)

        # loop through the Mechs until we get a successful auth
        response = session_key = None
        errors = {}
        for mech in server_mechs:
            mech_key = "Unknown (%s)" % str(mech)
            for name, value in vars(MechTypes).items():
                if isinstance(value, ObjectIdentifier) and value == mech:
                    mech_key = "%s (%s)" % (name, str(value))
                    break

            log.info("Attempting auth with mech %s" % mech_key)
            try:
                response, session_key = self._authenticate_session(mech)
                break
            except Exception as exc:
                log.warning("Failed auth for mech %s: %s"
                            % (mech_key, str(exc)))
                errors[str(mech_key)] = str(exc)
                pass

        if response is None:
            raise SMBAuthenticationError("Failed to authenticate with server: "
                                         "%s" % str(errors))

        log.info("Setting session id to %s" % self.session_id)
        setup_response = SMB2SessionSetupResponse()
        setup_response.unpack(response['data'].get_value())
        self._connected = True

        # TODO: remove from preauth session table and move to session_table
        self.connection.session_table[self.session_id] = self

        # session_key is the first 16 bytes, padded 0 if less than 16
        padding_len = 16 - len(session_key) if len(session_key) < 16 else 0
        session_key += b"\x00" * padding_len
        self.session_key = session_key[:16]

        if self.connection.dialect >= Dialects.SMB_3_1_1:
            preauth_hash = b"\x00" * 64
            hash_al = self.connection.preauth_integrity_hash_id
            for message in self.preauth_integrity_hash_value:
                preauth_hash = hash_al(preauth_hash + message.pack()).digest()

            self.signing_key = self._smb3kdf(self.session_key,
                                             b"SMBSigningKey\x00",
                                             preauth_hash)
            self.application_key = self._smb3kdf(self.session_key,
                                                 b"SMBAppKey\x00",
                                                 preauth_hash)
            self.encryption_key = self._smb3kdf(self.session_key,
                                                b"SMBC2SCipherKey\x00",
                                                preauth_hash)
            self.decryption_key = self._smb3kdf(self.session_key,
                                                b"SMBS2CCipherKey\x00",
                                                preauth_hash)
        elif self.connection.dialect >= Dialects.SMB_3_0_0:
            self.signing_key = self._smb3kdf(self.session_key,
                                             b"SMB2AESCMAC\x00",
                                             b"SmbSign\x00")
            self.application_key = self._smb3kdf(self.session_key,
                                                 b"SMB2APP\x00", b"SmbRpc\x00")
            self.encryption_key = self._smb3kdf(self.session_key,
                                                b"SMB2AESCCM\x00",
                                                b"ServerIn \x00")
            self.decryption_key = self._smb3kdf(self.session_key,
                                                b"SMB2AESCCM\x00",
                                                b"ServerOut\x00")
        else:
            self.signing_key = self.session_key
            self.application_key = self.session_key

        flags = setup_response['session_flags']
        if flags.has_flag(SessionFlags.SMB2_SESSION_FLAG_ENCRYPT_DATA) or \
                self.require_encryption:
            # make sure the connection actually supports encryption
            if not self.connection.supports_encryption:
                raise SMBException("SMB encryption is required but the "
                                   "connection does not support it")
            self.encrypt_data = True
            self.signing_required = False  # encryption covers signing
        else:
            self.encrypt_data = False

        if flags.has_flag(SessionFlags.SMB2_SESSION_FLAG_IS_GUEST) or \
                flags.has_flag(SessionFlags.SMB2_SESSION_FLAG_IS_NULL):
            self.session_key = None
            self.signing_key = None
            self.application_key = None
            self.encryption_key = None
            self.decryption_key = None
            if self.signing_required or self.encrypt_data:
                self.session_id = None
                raise SMBException("SMB encryption or signing was required "
                                   "but session was authenticated as a guest "
                                   "which does not support encryption or "
                                   "signing")

        if self.signing_required:
            log.info("Verifying the SMB Setup Session signature as auth is "
                     "successful")
            self.connection._verify(response, self.session_id, True)
Exemple #7
0
    def _verify_dialect_negotiate(self):
        log_header = "Session: %s, Tree: %s" \
                     % (self.session.username, self.share_name)
        log.info("%s - Running secure negotiate process" % log_header)

        if not self.session.signing_key:
            # This will only happen if we authenticated with the guest or anonymous user.
            raise SMBException(
                'Cannot verify negotiate information without a session signing key. Authenticate with '
                'a non-guest or anonymous account or set require_secure_negotiate=False to disable the '
                'negotiation info verification checks.')

        dialect = self.session.connection.dialect
        if dialect >= Dialects.SMB_3_1_1:
            # SMB 3.1.1+ uses the negotiation info to generate the signing key so doesn't need this extra exchange.
            return

        ioctl_request = SMB2IOCTLRequest()
        ioctl_request['ctl_code'] = \
            CtlCode.FSCTL_VALIDATE_NEGOTIATE_INFO
        ioctl_request['file_id'] = b"\xff" * 16

        val_neg = SMB2ValidateNegotiateInfoRequest()
        val_neg['capabilities'] = \
            self.session.connection.client_capabilities
        val_neg['guid'] = self.session.connection.client_guid
        val_neg['security_mode'] = \
            self.session.connection.client_security_mode
        val_neg['dialects'] = \
            self.session.connection.negotiated_dialects

        ioctl_request['buffer'] = val_neg
        ioctl_request['max_output_response'] = len(val_neg)
        ioctl_request['flags'] = IOCTLFlags.SMB2_0_IOCTL_IS_FSCTL
        log.info("%s - Sending Secure Negotiate Validation message" %
                 log_header)
        log.debug(ioctl_request)
        request = self.session.connection.send(ioctl_request,
                                               sid=self.session.session_id,
                                               tid=self.tree_connect_id,
                                               force_signature=True)

        log.info("%s - Receiving secure negotiation response" % log_header)
        try:
            response = self.session.connection.receive(request)

        except (FileClosed, InvalidDeviceRequest, NotSupported) as e:
            # https://docs.microsoft.com/en-us/archive/blogs/openspecification/smb3-secure-dialect-negotiation
            # Older dialects may respond with these exceptions, this is expected and we only want to fail if
            # they are not signed. Check that header signature was signed, fail if it wasn't. The signature, if
            # present, would have been verified when the connection received the data.
            if e.header['signature'].get_value() == b'\x00' * 16:
                raise

            return

        # If we received an actual response we want to validate the info provided matches with what was negotiated.
        ioctl_resp = SMB2IOCTLResponse()
        ioctl_resp.unpack(response['data'].get_value())
        log.debug(ioctl_resp)

        log.info("%s - Unpacking secure negotiate response info" % log_header)
        val_resp = SMB2ValidateNegotiateInfoResponse()
        val_resp.unpack(ioctl_resp['buffer'].get_value())
        log.debug(val_resp)

        self._verify("server capabilities",
                     val_resp['capabilities'].get_value(),
                     self.session.connection.server_capabilities.get_value())
        self._verify("server guid", val_resp['guid'].get_value(),
                     self.session.connection.server_guid)
        self._verify("server security mode",
                     val_resp['security_mode'].get_value(),
                     self.session.connection.server_security_mode)
        self._verify("server dialect", val_resp['dialect'].get_value(),
                     self.session.connection.dialect)
        log.info("Session: %d, Tree: %d - Secure negotiate complete" %
                 (self.session.session_id, self.tree_connect_id))