mquery

YARA malware query accelerator (web frontend)

View on GitHub

Plugins

Plugins can be used to extend mquery in organisation-specific ways.

There are two types of plugins:

Configuration

Visit the /config endpoint to change configuration variables required by plugins.

To add a new plugin to the system, you need to change mquery.plugins key in the config. For example:

[mquery]
plugins=plugins.mwdb_uploads:MalwarecageUploadsMetadata

To load a plugin MalwarecageUploadsMetadata from plugins.mwdb_uploads module.

Remember that you can also use environment variable MQUERY_PLUGINS to do the same thing - this may be useful for docker-based deployments.

Filter plugins

Filter plugins can be used to discard files quickly (before even running yara rules), or to process paths returned from Ursadb.

The very simple example of a filter plugin is RegexBlacklistPlugin

class RegexBlacklistPlugin(MetadataPlugin):
    is_filter = True
    config_fields = {
        "blacklist_pattern": "Regular expression for files that should be ignored",
    }

    def __init__(self, db: Database, config: MetadataPluginConfig) -> None:
        super().__init__(db, config)
        self.blacklist_pattern = config["blacklist_pattern"]

    def filter(self, orig_name: str, file_path: str) -> Optional[str]:
        if re.search(self.blacklist_pattern, orig_name):
            return None
        return file_path

When the filter method returns None, it means that checked file should be discarded. This plugin can be configured using the /config endpoint.

This API allows filter plugins to change the returned file path, which can be useful for more advanced uses of the filter plugin. For example, GzipPlugin which extracts contents of files ending with .gz (so that yara is run on the uncompressed contents):

class GzipPlugin(MetadataPlugin):
    is_filter = True

    def __init__(self, db: Database, config: MetadataPluginConfig) -> None:
        super().__init__(db, config)
        self.tmpfiles: List[IO[bytes]] = []

    def filter(self, orig_name: str, file_path: str) -> Optional[str]:
        tmp = tempfile.NamedTemporaryFile()
        self.tmpfiles.append(tmp)
        if orig_name.endswith(".gz"):
            with gzip.open(file_path, "rb") as f_in:
                with open(tmp.name, "wb") as f_out:
                    shutil.copyfileobj(f_in, f_out)
            return tmp.name
        return file_path

    def clean(self):
        for tmp in self.tmpfiles:
            tmp.close()
        self.tmpfiles = [] 

The same method can be used to, for example, automatically download and extract files from s3 automatically.

Filter plugins are ran before yara matching, and before file downloads. To avoid unexpected behaviour, the same set of plugins should be active in the web UI and in the daemon.

Warning: if you have multiple backends either ensure that all backends and the web frontend use the same set of plugins, or be very careful about how they interact.

For example, imagine that of the backends does gzip decompression and the other doesn’t. Without any filter plugins installed on the frontend, download results will contain a mix of compressed and uncompressed files. Right now the answer to this is to write a plugin that does conditional decompression depending on which backend a file came from. It’s not handled automatically.

Metadata plugins

Metadata plugins are used to enrich results with additional metadata. For example, we use them to display mwdb tags in mquery. They can also be used as post processing hooks, for example to report all matched files to some other system. This can be used to integrate systems with each other (for example, we plan to use it to add mquery matches to mwdb.

The very simple and probably useless metadata plugin is ExampleTagPlugin:

class ExampleTagPlugin(MetadataPlugin):
    cacheable = True
    config_fields = {
        "tag": "Everything will be tagged using that tag",
        "tag_url": "Tag URL e.g. http://google.com?q={tag}",
    }

    def __init__(self, db: Database, config: MetadataPluginConfig) -> None:
        super().__init__(db, config)
        self.tag = config["tag"]
        self.tag_url = config["tag_url"]

    def extract(
        self, identifier: str, matched_fname: str, current_meta: Metadata
    ) -> Metadata:
        return {
            "example_tag": {
                "display_text": self.tag,
                "url": self.tag_url,
            }
        }