Source code for mastf.MASTF.scanners.mixins

# This file is part of MAST-F's Frontend API
# Copyright (c) 2024 Mobile Application Security Testing Framework
#
# 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.

# This file defines default mixins that can be used within each
# extension of a scanner.
from django.db.models import Count

from mastf.MASTF.models import (
    Scan,
    Details,
    namespace,
    File,
    PermissionFinding,
    Vulnerability,
    Finding,
    Scanner,
    FindingTemplate,
    Host,
    Component,
)
from mastf.MASTF.serializers import (
    HostSerializer,
    PermissionFindingSerializer,
    VulnerabilitySerializer,
    FindingSerializer,
    ComponentSerializer,
)
from mastf.MASTF.utils.enum import HostType

__all__ = [
    "DetailsMixin",
    "PermissionsMixin",
    "VulnerabilitiesMixin",
    "FindingsMixins",
    "HostsMixin",
    "ComponentsMixin",
]


[docs] class DetailsMixin: """Add-on to generate app details If you use this mixin and you enable chart-rendering, they will be displayed on the front page of scan results. """ charts: bool = True """Defines whether summary charts should be displayed on the details page."""
[docs] def ctx_details(self, scan: Scan, file: File, scanner: Scanner) -> dict: """Returns the details context for the desired extension. :param scan: the scan to view :type scan: Scan :return: all relevant context information :rtype: dict """ context = namespace() context.details = Details.objects.get(scan=scan, file=file) context.charts = self.charts return context
[docs] class PermissionsMixin: """Add-on to generate permission lists according to the selected file The returned data will be a list of ``PermissionFinding`` instances that store information where the permission has been found and the actual ``AppPermission`` reference. """
[docs] def ctx_permissions(self, scan: Scan, file: File, scanner: Scanner) -> list: """Returns all permissions mapped to a specific file.""" return PermissionFinding.objects.filter( scan=scan, scan__file=file, scanner=scanner )
def res_permissions(self, scan: Scan, scanner: Scanner) -> list: data = self.ctx_permissions(scan, scan.file, scanner) return PermissionFindingSerializer(data, many=True).data
[docs] class VulnerabilitiesMixin: """Add-on to generate vulnerabilites according to the selected file."""
[docs] def ctx_vulnerabilities(self, scan: Scan, file: File, scanner: Scanner) -> list: """Returns all vulnerabilities that have been identified in the scan target. :param project: the project instance :type project: Project :param file: the scan target :type file: File :return: a list of vulnerabilities :rtype: list """ vuln = Vulnerability.objects.filter(scan=scan, scanner=scanner) data = [] languages = ( vuln.values("snippet__language") .annotate(lcount=Count("snippet__language")) .order_by() ) if len(languages) == 0: return data for language in languages: lang = {"name": language["snippet__language"], "count": language["lcount"]} categories = [] templates = ( vuln.filter(snippet__language=lang["name"]) .values("template") .annotate(tcount=Count("template")) .order_by() ) for category in templates: template_pk = category["template"] template = FindingTemplate.objects.get(pk=template_pk) cat = { "name": template.title if template else "Untitled", "count": category["tcount"], } vuln_data = vuln.filter( snippet__language=lang["name"], template=template ) cat["vuln_data"] = VulnerabilitySerializer(vuln_data, many=True).data categories.append(cat) categories.sort(key=lambda x: x["name"]) lang["categories"] = categories data.append(lang) return data
def res_vulnerabilities(self, scan: Scan, scanner: Scanner) -> list: return self.ctx_vulnerabilities(scan, scan.file, scanner)
[docs] class FindingsMixins: """Add-on to generate a finding list according to the selected file."""
[docs] def ctx_findings(self, scan: Scan, file: File, scanner: Scanner) -> list: """Returns all findings that have been identified in the scan target. :param project: the project instance :type project: Project :param file: the scan target :type file: File :return: a list of vulnerabilities :rtype: list """ data = [] findings = Finding.objects.filter(scan=scan, scanner=scanner) templates = ( findings.values("template").annotate(tcount=Count("template")).order_by() ) if len(templates) == 0: return data for category in templates: pk = category["template"] template = FindingTemplate.objects.get(pk=pk) filtered = findings.filter(template=template) data.append( { "name": template.title if template else "Untitled", "internal_id": template.template_id, "count": category["tcount"], "finding_data": FindingSerializer(filtered, many=True).data, } ) return data
def res_findings(self, scan: Scan, scanner: Scanner) -> list: return self.ctx_findings(scan, scan.file, scanner)
[docs] class HostsMixin: """Mixin class for working with hosts in a scan. This mixin provides methods for retrieving and manipulating hosts within a scan. Usage: ~~~~~~ - Use ``ctx_hosts()`` to get all hosts identified within the scan target. - Use ``res_hosts()`` to get a serialized representation of hosts within the scan. Example: ~~~~~~~~ .. code-block:: python mixin = HostsMixin() ctx_hosts_data = mixin.ctx_hosts(scan, file, scanner) res_hosts_data = mixin.res_hosts(scan, scanner) """
[docs] def ctx_hosts(self, scan: Scan, file: File, scanner: Scanner) -> list: """ Get all hosts identified within the scan target. :param scan: The scan instance. :type scan: Scan :param file: The scan target. :type file: File :param scanner: The scanner instance. :type scanner: Scanner :return: A list of hosts. :rtype: list """ data = namespace() data.hosts = Host.objects.filter(scan=scan, scanner=scanner) data.host_types = [str(x) for x in HostType] return data
[docs] def res_hosts(self, scan: Scan, scanner: Scanner) -> list: """ Get a serialized representation of hosts within the scan. :param scan: The scan instance. :type scan: Scan :param scanner: The scanner instance. :type scanner: Scanner :return: A list of serialized hosts. :rtype: list """ data = Host.objects.filter(scan=scan, scanner=scanner) return HostSerializer(data, many=True).data
[docs] class ComponentsMixin: """Mixin class for working with components in a scan. This mixin provides methods for retrieving and manipulating components within a scan. Usage: ~~~~~~ - Use ``ctx_components()`` to get components statistics and elements for a scan. - Use ``res_components()`` to get a serialized representation of components within the scan. Example: ~~~~~~~~ .. code-block:: python mixin = ComponentsMixin() ctx_components_data = mixin.ctx_components(scan, file, scanner) res_components_data = mixin.res_components(scan, scanner) """
[docs] def ctx_components(self, scan: Scan, file: File, scanner: Scanner): """ Get components statistics and elements for a scan. :param scan: The scan instance. :type scan: Scan :param file: The scan target. :type file: File :param scanner: The scanner instance. :type scanner: Scanner :return: A namespace object containing component statistics and elements. """ data = namespace(stats=Component.stats(scan)) data.elements = Component.objects.filter(scanner=scanner) return data
[docs] def res_hosts(self, scan: Scan, scanner: Scanner) -> list: """ Get a serialized representation of components within the scan. :param scan: The scan instance. :type scan: Scan :param scanner: The scanner instance. :type scanner: Scanner :return: A list of serialized components. :rtype: list """ data = Component.objects.filter(scanner=scanner) return ComponentSerializer(data, many=True).data