예제 #1
0
    async def post(self, data):
        if data == []:
            self.write_warning("upload/file - no request body found", 422)
            self.finish()
            return

        if 'file' not in self.request.files:
            self.write_warning("upload/file - no 'file' in part", 422)
            self.finish()
            return

        # Set name if missing
        if 'name' not in data:
            data['name'] = self.request.files['file'][0]['filename']

        # Get the files offset and size
        f_path = self.request.files['file'][0]['body'].decode('utf-8')

        # Extract if required, zip only
        if data['extract']:
            try:
                f_path = await route_support.unzip_file(f_path, data['password'])
            except error.SnakeError as err:
                self.write_warning("upload/file - {}".format(err), 422)
                self.finish()
                return
            # Update name if not overriden
            if data['name'] == self.request.files['file'][0]['filename']:
                data['name'] = path.basename(f_path)

        # Set submission type
        data['submission_type'] = 'upload:file'

        # Hash the file
        sha2 = hashlib.sha256()
        with open(f_path, 'rb') as f:
            chunk = f.read(4096)
            while chunk:
                sha2.update(chunk)
                chunk = f.read(4096)
        sha256_digest = sha2.hexdigest()

        # Check if the file already exists
        document = await db.async_file_collection.select(sha256_digest)
        if document:
            document = schema.FileSchema().dump(schema.FileSchema().load(document))
            self.write_warning("upload/file - file already exists for given sha256 digest", 409, {'sample': document})
            self.finish()
            return

        # Save the file and add it to the database
        document = await route_support.store_file(sha256_digest, f_path, enums.FileType.FILE, data)
        document = schema.FileSchema().dump(schema.FileSchema().load(document))
        self.jsonify({'sample': document})
        self.finish()
예제 #2
0
 def extract(self, args, file, opts):
     samples = []
     with tempfile.TemporaryDirectory(dir=path.abspath(
             path.expanduser(
                 config.snake_config['cache_dir']))) as temp_dir:
         # Extract the samples
         proc = subprocess.run(
             [self.binwalk_path, file.file_path, '-e', '-C', temp_dir],
             stdout=subprocess.PIPE,
             stderr=subprocess.PIPE)
         if not proc:
             raise error.CommandError(
                 "failed to successfully extract from sample")
         # Get file name
         document = db.file_collection.select(file.sha256_digest)
         if not document:
             raise error.SnakeError("failed to get sample's metadata")
         # There will be one output directory connataining files with the offsets as names
         contents = os.listdir(temp_dir)
         if not contents:
             return []
         directory = path.join(temp_dir, contents[0])
         for i in os.listdir(directory):
             file_path = path.join(directory, i)
             name = '{}.{}'.format(document['name'], i)
             file_schema = schema.FileSchema().load({
                 'name':
                 name,
                 'description':
                 'extracted with binwalk'
             })
             new_file = fs.FileStorage()
             new_file.create(file_path)
             new_document = submitter.submit(file_schema,
                                             enums.FileType.FILE, new_file,
                                             file, NAME)
             new_document = schema.FileSchema().dump(
                 schema.FileSchema().load(
                     new_document))  # Required to clean the above
             samples += [new_document]
     return samples
예제 #3
0
 async def patch(self, sha256_digest):
     document = await db.async_file_collection.select(sha256_digest)
     if not document or document['file_type'] != enums.FileType.MEMORY:
         self.write_warning("memory - no sample for given sha256 digest",
                            404, sha256_digest)
         self.finish()
         return
     if not self.request.body:
         self.write_warning("memory - no request body found", 422,
                            sha256_digest)
         self.finish()
         return
     data = escape.json_decode(self.request.body)
     data = schema.FileSchema(only=('description', 'name', 'tags'),
                              partial=True).load(data)
     data = schema.FileSchema(only=('description', 'name',
                                    'tags')).dump(data)
     if data.keys():
         await db.async_file_collection.update(sha256_digest, data)
     document = await db.async_file_collection.select(sha256_digest)
     document = schema.FileSchema().dump(schema.FileSchema().load(document))
     self.jsonify({'memory': document})
     self.finish()
예제 #4
0
    async def get(self, data):
        documents = []
        filter_ = self.create_filter(self.request.arguments, data['operator'])
        if filter_:
            filter_ = {'$and': [filter_]}
            if data['file_type']:
                filter_['$and'] += [{'file_type': data['file_type']}]
        elif data['file_type']:
            filter_ = {'file_type': data['file_type']}
        # NOTE: With async (motor) there is no count() on cursor so we have to work around that
        total = await db.async_file_collection.db.files.count_documents(
            filter_ if filter_ else {})
        cursor = db.async_file_collection.select_all(filter_, data['order'],
                                                     data['sort'],
                                                     data['limit'],
                                                     data['from'])
        while await cursor.fetch_next:
            documents += [cursor.next_object()]

        documents = schema.FileSchema(many=True).dump(
            schema.FileSchema(many=True).load(documents))
        self.jsonify({'samples': documents, 'total': total})
        self.finish()
예제 #5
0
async def store_file(sha256_digest, file_path, file_type, data):
    """Store a file to disk.

    Uses file storage to store the new file to disk. Upon success insert the
    metadata into the database.

    Args:
        sha256_digest (str): The has of the file to store.
        file_path (str): The location of the file to move into the store.
        file_type (:obj:`FileType`): The type of the file being stored.
        data (:obj:`CommandSchema`): The metadata for the file.

    Returns:
        :obj:`CommandSchema`: The updated document metadata.

    Raises:
        SnakeError: When the metadata cannot be inserted into the database.
    """
    # Save the file to the 'filedb' and add it to the database
    file_storage = utils.FileStorage()
    file_storage.create(file_path, sha256_digest)
    if not file_storage.save(move=True):
        raise error.SnakeError("Failed to store file on disk")
    data.update(file_storage.to_dict())
    data['name'] = strip_extensions(data['name'])
    data['timestamp'] = datetime.utcnow()
    data = schema.FileSchema().dump(data)
    data['file_type'] = file_type  # load_only=True
    document = await db.async_file_collection.insert(data)
    if not document:
        file_storage.delete()
        raise error.SnakeError("Failed to insert document")
    document = await db.async_file_collection.select(file_storage.sha256_digest
                                                     )

    # Run any autoruns, if allowed
    await execute_autoruns(sha256_digest, file_type, file_storage.mime)

    return document
예제 #6
0
    async def post(self, scale):  # pylint: disable=too-many-locals
        if not self.request.body:
            self.write_warning("scale/upload - no request body found", 422,
                               scale)
            self.finish()
            return
        data = escape.json_decode(self.request.body)

        # Validate args
        try:
            data = self.UploadSchema().dump(self.UploadSchema().load(data))
        except exceptions.ValidationError as err:
            self.write_warning(
                self.json_decode(('%s' % err.messages).replace("'", '"')), 422)
            self.finish()
            return

        scale_ = scale_manager.get_scale(scale)
        upload = scale_manager.get_component(scale_,
                                             enums.ScaleComponent.UPLOAD)

        # Validate arguments and update
        upld_args = upload.arguments()
        try:
            if upld_args:
                data['args'] = schema.Schema(fields=upld_args).load(
                    data['args'])
        except exceptions.ValidationError as err:
            self.write_warning(
                self.json_decode(
                    ('{"args": %s}' % err.messages).replace("'", '"')), 422)
            self.finish()
            return

        # Get the file
        with tempfile.TemporaryDirectory(dir=path.abspath(
                path.expanduser(snake_config['cache_dir']))) as temp_dir:
            loop = asyncio.get_event_loop()
            f_name = await loop.run_in_executor(None, upload.upload,
                                                data['args'], temp_dir)
            f_path = path.join(temp_dir, f_name)

            # Extract if required, zip only
            if data['extract']:
                f_path = await route_support.unzip_file(
                    f_path, data['password'])
                f_name = path.basename(f_path)

            # Update name if not overriden
            if not data['name']:
                data['name'] = f_name

            # Set submission type
            data['submission_type'] = 'upload:{}'.format(scale)

            # Check that the file is not empty
            if path.getsize(f_path) == 0:
                self.write_warning("scale/upload - sample is empty", 422)
                self.finish()
                return

            # Hash the file
            sha2 = hashlib.sha256()
            with open(f_path, 'rb') as f:
                chunk = f.read(4096)
                while chunk:
                    sha2.update(chunk)
                    chunk = f.read(4096)
            sha256_digest = sha2.hexdigest()

            # Check if the file already exists
            document = await db.async_file_collection.select(sha256_digest)
            if document:
                document = schema.FileSchema().dump(
                    schema.FileSchema().load(document))
                self.write_warning(
                    "scale/upload - sample already exists for given sha256 digest",
                    409, {'sample': document})
                self.finish()
                return

            # Save the file and add it to the database
            document = await route_support.store_file(sha256_digest, f_path,
                                                      data['file_type'], data)
            document = schema.FileSchema().dump(
                schema.FileSchema().load(document))
            self.jsonify({'sample': document})
            self.finish()
예제 #7
0
    def oleobj(self, args, file, opts):

        # Monkeypatch 1 - This is to force the script argument to the appropriate file locaiton for analysis
        def temp_args(_a, _b, _c):
            return [file.file_path]

        # Deploy Monkeypatch 1
        import optparse
        get_args = optparse.OptionParser._get_args
        optparse.OptionParser._get_args = temp_args
        output = []
        fname_prefix = oleobj.sanitize_filename(file.file_path)
        index = 1
        DUMP_CHUNK_SIZE = 4096
        result = dict()
        for ole in oleobj.find_ole(file.file_path, None):
            if ole is None:    # no ole file found
                continue

            for path_parts in ole.listdir():
                stream_path = '/'.join(path_parts)
                if path_parts[-1] == '\x01Ole10Native':
                    stream = None
                    try:
                        stream = ole.openstream(path_parts)
                        #print('extract file embedded in OLE object from stream %r:'
                        #      % stream_path)
                        #print('Parsing OLE Package')
                        opkg = oleobj.OleNativeStream(stream)
                        # leave stream open until dumping is finished
                    except Exception:
                        raise error.CommandWarning("*** Not an OLE 1.0 Object")
                        if stream is not None:
                            stream.close()
                        continue
                    if opkg.is_link:
                        raise error.CommandWarning('Object is not embedded but only linked to '
                                  '- skip')
                        continue
                    result['SHA256_AnalyzedFile'] = fname_prefix
                    result['Extracted_file.NAME'] = opkg.filename
                    result['Source_path'] = opkg.src_path
                    result['Temp_path'] = opkg.temp_path
                    if opkg.filename:
                        fname = '%s_%s' % (fname_prefix,
                                           oleobj.sanitize_filename(opkg.filename))
                    else:
                        fname = '%s_object_%03d.noname' % (fname_prefix, index)
                    try:
                        result['Saved_Filename'] = fname

                        samples = []
                        with tempfile.TemporaryDirectory(dir=path.abspath(path.expanduser(config.snake_config['cache_dir']))) as temp_dir:
                            file_path = path.join(temp_dir, fname)
                            with open(file_path, 'wb') as writer:
                                n_dumped = 0
                                next_size = min(DUMP_CHUNK_SIZE, opkg.actual_size)
                                while next_size:
                                    data = stream.read(next_size)
                                    writer.write(data)
                                    n_dumped += len(data)
                                    if len(data) != next_size:
                                        raise error.CommandWarning('Wanted to read {0}, got {1}'
                                                    .format(next_size, len(data)))
                                        break
                                    next_size = min(DUMP_CHUNK_SIZE,
                                                    opkg.actual_size - n_dumped)
                            file_schema = schema.FileSchema().load({
                                'name': fname,
                                'description': 'extracted with oleobj from ' + fname
                            })
                            
                            new_file = fs.FileStorage()
                            new_file.create(file_path)
                            new_document = submitter.submit(file_schema, enums.FileType.FILE, new_file, file, NAME)
                            new_document = schema.FileSchema().dump(schema.FileSchema().load(new_document))
                            samples += [new_document]
                            for i in samples:
                                i['name'] = fname
                            result['samples'] = samples

                    except Exception as exc:
                        raise error.CommandWarning('error dumping to {0} ({1})'
                                    .format(fname, exc))
                    finally:
                        stream.close()

                    index += 1
            output.append(result)
        if not output:
            raise error.CommandWarning("No ole object was found")
        return output
예제 #8
0
    async def post(self):
        # XXX: Does not support extraction atm
        #  curl 'http://127.0.0.1:5000/upload/files' -F '0=@./file1' -F '1=@./file2' -F 'data={0:{"name": "file1"}, 1:{"name": "file2"}}'
        #
        data = {}
        try:
            data = self.get_argument('data')
        except Exception:  # noqa
            data = {}
        if data == {}:
            missing_fields = {}
            missing_fields['data'] = ["Missing data for required field."]
            self.write_warning(missing_fields, 422)
            self.finish()
            return
        try:
            data = escape.json_decode(data)
        except Exception:  # noqa
            self.write_warning("upload/files - must be content type application/json", 422, data)
            self.finish()
            return

        # Data is optional we do not check that it keys correctly, to avoid
        # some errors later down the line prevalidate the data dictionaries
        data_arrays = []
        for k, v in data.items():
            if 'name' not in v:
                v['name'] = self.request.files[k][0]['filename']
            data_arrays += [v]

        # Validate with discard we need the keys
        data_arrays = schema.FileSchema(many=True).load(data_arrays)
        schema.FileSchema(many=True).dump(data_arrays)

        # Upload the files
        documents = []
        for k, v in data.items():
            # Set submission type
            v['submission_type'] = 'upload:file'

            # Get the files offset and size
            f_path = self.request.files[k][0]['body'].decode('utf-8')

            # Hash the file
            sha2 = hashlib.sha256()
            with open(f_path, 'rb') as f:
                chunk = f.read(4096)
                while chunk:
                    sha2.update(chunk)
                    chunk = f.read(4096)
            sha256_digest = sha2.hexdigest()

            # Check if the file already exists, if so add to documents, but there is no need to upload it
            document = await db.async_file_collection.select(sha256_digest)
            if document:
                documents += [document]
            else:
                documents += [await route_support.store_file(sha256_digest, f_path, enums.FileType.FILE, v)]
        documents = schema.FileSchema(many=True).dump(schema.FileSchema(many=True).load(documents))
        self.jsonify({'samples': documents})
        self.finish()
예제 #9
0
def submit(file_schema, file_type, file, parent, scale_name):  # pylint: disable=too-many-branches
    """Submit a new file to Snake.

    This is used generally by the command component of scales to submit a new
    file into snake.

    Args:


    """

    # We need to be safe here so instance check the above
    if not isinstance(file_schema, dict):
        raise TypeError("file_schema must be of type dict")
    if not isinstance(file, fs.FileStorage):
        raise TypeError("file must be of type FileSchema")
    if not isinstance(parent, fs.FileStorage):
        raise TypeError("parent must be of type FileStorage")

    # If the hashes are the same, just stop
    if file.sha256_digest == parent.sha256_digest:
        return db.file_collection.select(file.sha256_digest)

    # Create submission type
    submission_type = 'scale:{}'.format(scale_name)

    # Check if the file to submit is already in Snake, if not lets add it
    document = db.file_collection.select(file.sha256_digest)
    if not document:
        # Validate
        data = schema.FileSchema().dump(schema.FileSchema().load(file_schema))
        # Save the file
        if not file.save(move=True):
            raise error.SnakeError("could not save new file to disk for hash {}".format(file.sha256_digest))
        data.update(file.to_dict())
        # NOTE: Don't set the parent we will do this later, so blank them out
        # if the scale tried to be smart
        data['children'] = {}
        data['parents'] = {}
        data['submission_type'] = submission_type
        data['timestamp'] = datetime.utcnow()
        data = schema.FileSchema().dump(data)
        data['file_type'] = file_type  # load_only=True
        # Save
        db.file_collection.insert(data)

    # Update the parent child relationships
    document = db.file_collection.select(file.sha256_digest)
    if document:
        # HACK: This is needed to get submission_type of parent
        p = db.file_collection.select(parent.sha256_digest)

        # Check if the parent and type already exist
        if 'parents' not in document:
            document['parents'] = {}
        if parent.sha256_digest in document['parents']:
            if submission_type in document['parents'][parent.sha256_digest]:
                return document
            else:
                document['parents'][parent.sha256_digest] += [p["submission_type"]]
        else:
            document['parents'][parent.sha256_digest] = [p["submission_type"]]
        # Validate
        document = schema.FileSchema().dump(schema.FileSchema().load(document))
        # Update
        db.file_collection.update(file.sha256_digest, document)

        # Update the parents children
        document = db.file_collection.select(parent.sha256_digest)
        if not document:  # Parent does not exist it has been delete, don't update it
            return db.file_collection.select(file.sha256_digest)
        if 'children' not in document:
            document['children'] = {}
        if file.sha256_digest in document['children']:
            if submission_type in document['children'][file.sha256_digest]:
                return db.file_collection.select(file.sha256_digest)
            else:
                document['children'][file.sha256_digest] += [submission_type]
        else:
            document['children'][file.sha256_digest] = [submission_type]
        # Validate
        document = schema.FileSchema().dump(schema.FileSchema().load(document))
        # Update
        db.file_collection.update(parent.sha256_digest, document)
    else:
        raise error.SnakeError("could not submit new file for hash {}".format(file.sha256_digest))

    return db.file_collection.select(file.sha256_digest)