KissPeter/APIFuzzer

View on GitHub
apifuzzer/custom_fuzzers.py

Summary

Maintainability
A
1 hr
Test Coverage
F
53%
import six
from bitstring import Bits
from kitty.core import kassert
from kitty.model import RandomBits, String, BaseField, Group
from kitty.model.low_level.encoder import ENC_BITS_DEFAULT, strToBytes

from apifuzzer.utils import secure_randint, get_logger


class APIFuzzerGroup(Group):
    def __init__(self, name, value):
        super().__init__(values=value, name=name, fuzzable=True)

    @staticmethod
    def accept_list_as_value():
        return True


class Utf8Chars(BaseField):
    """
    This custom fuzzer iterates through the UTF8 chars and gives back random section between min and max length
    Highly relies on random numbers so most probably will give you different values each time to run it.

    You can generate the chars like this:
    :example:
    >>>for st in range(0, 1114111):
    >>>    try:
    >>>        print(f'{st}-> {chr(st)}')
    >>>    except (UnicodeEncodeError, ValueError):
    >>>        pass
    Above 1114111 chars started to getting unprocessable so this is the upper limit for now.
    """

    MAX = 1114111

    def __init__(
        self,
        value,
        name,
        fuzzable=True,
        min_length=20,
        max_length=None,
        num_mutations=80,
    ):
        super(BaseField, self).__init__(name=name)  # pylint: disable=E1003
        self.logger = self.logger = get_logger(self.__class__.__name__)
        self.name = name
        self.value = value
        self.min_length = min_length
        self.max_length = max_length if max_length else len(value) * 2
        self._num_mutations = num_mutations
        self.position = self.init_position()
        self._initialized = False
        self._default_value = self.to_bits(chr(self.MAX))
        self._encoder = ENC_BITS_DEFAULT
        self._default_rendered = self._encode_value(self._default_value)
        self._hash = None
        self._fuzzable = fuzzable
        self._need_second_pass = False
        self._controlled = False

    def init_position(self):
        return secure_randint(0, self.MAX)

    @staticmethod
    def str_to_bytes(value):
        """
        :type value: ``str``
        :param value: value to encode
        """
        kassert.is_of_types(value, (bytes, bytearray, six.string_types))
        if isinstance(value, six.string_types):
            return value.encode(encoding="utf-8")
        if isinstance(value, bytearray):
            return bytes(value)
        return value

    def to_bits(self, val):
        return Bits(self.str_to_bytes(val))

    def _mutate(self):
        current_value = list()
        current_mutation_length = secure_randint(self.min_length, self.max_length)
        for st in range(self.position, self.position + current_mutation_length):
            current_value.append(chr(st))
        self._current_value = self.to_bits("".join(current_value))
        self.position += current_mutation_length
        if self.position > self.MAX:
            self.position = self.init_position()

    def __str__(self):
        return f"{self.name}->{self.value}"

    def __repr__(self):
        return f"{self.name}->{self.value}"


class RandomBitsField(RandomBits):
    """
    Creates a fields which compatible field with String and Delimiter
    https://lcamtuf.blogspot.hu/2014/08/binary-fuzzing-strategies-what-works.html

    """

    def not_implemented(self, func_name):
        _ = func_name
        pass

    def __init__(self, value, name, fuzzable=True):
        self.name = name
        self.value = value
        super(RandomBitsField, self).__init__(
            name=name,
            value=value,
            min_length=0,
            max_length=len(value) * 2,
            fuzzable=fuzzable,
            num_mutations=80,
        )

    def _mutate(self):
        if self._step:
            length = self._min_length + self._step * self._current_index
        else:
            length = self._random.randint(self._min_length, self._max_length)
        current_bytes = ""
        for _ in range(length // 8 + 1):
            current_bytes += chr(self._random.randint(0, 255))
        self._current_value = Bits(bytes=strToBytes(current_bytes))[:length]

    def __str__(self):
        return f"{self.name}->{self.value}"

    def __repr__(self):
        return f"{self.name}->{self.value}"


class UnicodeStrings(String):
    def __init__(
        self,
        value,
        name,
        min_length=0,
        max_length=None,
        num_mutations=80,
        fuzzable=True,
    ):
        self.min_length = min_length
        self.max_length = max_length if max_length else len(value) * 2
        self._num_mutations = num_mutations
        self.name = name
        self.value = value
        super(UnicodeStrings, self).__init__(name=name, value=value, fuzzable=fuzzable)

    def not_implemented(self, func_name):
        _ = func_name
        pass

    def __str__(self):
        return f"{self.name}->{self.value}"

    def __repr__(self):
        return f"{self.name}->{self.value}"