Writing custom modules

Since Artemis uses the Karton framework (https://github.com/CERT-Polska/karton) underneath, modules are karton services:

from karton.core import Task
from artemis.binds import Service, TaskStatus, TaskType
from artemis.module_base import ArtemisBase
from artemis.task_utils import get_target_url

class CustomScanner(ArtemisBase):
    """
    My first custom Artemis module
    """

    # Module name that will be displayed
    identity = "custom"

    # Types of tasks that will be consumed by the module - here, open ports that were identified
    # to contain a HTTP/HTTPS service. To know what types are possible, look at other modules' source:
    # https://github.com/CERT-Polska/Artemis/tree/main/artemis/modules
    filters = [
        {"type": TaskType.SERVICE, "service": Service.HTTP},
    ]

    def run(self, current_task: Task) -> None:
        url = get_target_url(current_task)
        self.log.info(f"custom module running {url}")

        status = TaskStatus.OK
        status_reason = None

        if "sus" in url:
            # On the default task result view only the interesting task results will be displayed
            status = TaskStatus.INTERESTING
            status_reason = "suspicious link detected!"

        # In the data dictionary, you may provide any additional results - the user will be able to view them
        # in the interface on the single task result page.
        self.db.save_task_result(task=current_task, status=status, status_reason=status_reason, data={})

if __name__ == "__main__":
    CustomScanner().loop()

Warning

If you know how to use Karton you might know the self.send_task method on Karton producers that creates a new task.

Since Artemis saves all task information in the database, you need to use a wrapper - self.add_task.

However, Artemis adds a few helpers to make your job easier.

Database

Artemis uses MongoDB to save task results - this feature is available via self.save_task_result.

Cache

Modules often perform long running tasks, which we want to cache. Such example may be port scanning. Artemis provides simple Redis-based cache API for each module. The cache is available under self.cache. Rather than describing how it works it’s easier to read redis_cache.py.

Resource Locking

You may want to lock a resource. An example can be the Shodan module (https://github.com/CERT-Polska/Artemis/blob/main/artemis/modules/shodan_vulns.py), which requests a lock using self.lock to prevent hitting API request limits.

HTTP requests

To perform a HTTP request, use the artemis.http_requests module that:

  • provides correct user-agent,

  • doesn’t verify the SSL certificate as many interesting findings are on sites with expired SSL certificates,

  • reads only the first Config.CONTENT_PREFIX_SIZE to save bandwidth.

To perform a request use http_requests.get or http_requests.post functions.

DNS requests

DNS requests should be performed using ip_lookup from resolvers.py.

Outgoing requests limiting

To prevent Artemis from disrupting scanned services, Artemis introduces request limiting. This is why all modules should use throttle_request function from artemis.utils while performing requests.

throttle_request(lambda: ftp.login(username, password))

This method ensures that it will take at least Config.SECONDS_PER_REQUEST seconds, sleeping if needed.