import itertools
import math
import random
from ..fuzzable import Fuzzable
[docs]
class String(Fuzzable):
"""Primitive that cycles through a library of "bad" strings.
The class variable 'fuzz_library' contains a list of
smart fuzz values global across all instances. The 'this_library' variable contains fuzz values specific to
the instantiated primitive. This allows us to avoid copying the near ~70MB fuzz_library data structure across
each instantiated primitive.
:type name: str, optional
:param name: Name, for referencing later. Names should always be provided, but if not, a default name will be given,
defaults to None
:type default_value: str
:param default_value: Value used when the element is not being fuzzed - should typically represent a valid value.
:type size: int, optional
:param size: Static size of this field, leave None for dynamic, defaults to None
:type padding: chr, optional
:param padding: Value to use as padding to fill static field size, defaults to "\\x00"
:type encoding: str, optional
:param encoding: String encoding, ex: utf_16_le for Microsoft Unicode, defaults to ascii
:type max_len: int, optional
:param max_len: Maximum string length, defaults to None
:type fuzzable: bool, optional
:param fuzzable: Enable/disable fuzzing of this primitive, defaults to true
"""
# store fuzz_library as a class variable to avoid copying the ~70MB structure across each instantiated primitive.
# Has to be sorted to avoid duplicates
_fuzz_library = [
"!@#$%%^#$%#$@#$%$$@#$%^^**(()",
"", # strings ripped from spike (and some others I added)
"$(reboot)",
"$;reboot",
"%00",
"%00/",
"%01%02%03%04%0a%0d%0aADSF",
"%01%02%03@%04%0a%0d%0aADSF",
"%0a reboot %0a",
"%0Areboot",
"%0Areboot%0A",
"%0DCMD=$'reboot';$CMD",
'%0DCMD=$"reboot";$CMD',
"%0Dreboot",
"%0Dreboot%0D",
"%\xfe\xf0%\x00\xff",
"%\xfe\xf0%\x01\xff" * 20,
"%n" * 100, # format strings.
"%n" * 500,
"%s" * 100,
"%s" * 500,
"%u0000",
"& reboot &",
"& reboot",
"&&CMD=$'reboot';$CMD",
'&&CMD=$"reboot";$CMD',
"&&reboot",
"&&reboot&&",
"&CMD=$'reboot';$CMD",
'&CMD=$"reboot";$CMD',
"&reboot",
"&reboot&",
"'reboot'",
"..:..:..:..:..:..:..:..:..:..:..:..:..:",
"/%00/",
"/." * 5000,
"/.../" + "B" * 5000 + "\x00\x00",
"/.../.../.../.../.../.../.../.../.../.../",
"/../../../../../../../../../../../../boot.ini",
"/../../../../../../../../../../../../etc/passwd",
"/.:/" + "A" * 5000 + "\x00\x00",
"/\\" * 5000,
"/index.html|reboot|",
"; reboot",
";CMD=$'reboot';$CMD",
';CMD=$"reboot";$CMD',
";id",
";notepad;",
";reboot",
";reboot/n",
";reboot;",
";reboot|",
";system('reboot')",
";touch /tmp/SULLEY;",
";|reboot|",
'<!--#exec cmd="reboot"-->',
"<>" * 500, # sendmail crackaddr (http://lsd-pl.net/other/sendmail.txt)
"<reboot",
"<reboot%0A",
"<reboot%0D",
"<reboot;",
'"%n"' * 500,
'"%s"' * 500,
"\\\\*",
"\\\\?\\",
"\nnotepad\n",
"\nreboot\n",
"\r\n" * 100, # miscellaneous.
"\x01\x02\x03\x04",
"\xde\xad\xbe\xef" * 10,
"\xde\xad\xbe\xef" * 100,
"\xde\xad\xbe\xef" * 1000,
"\xde\xad\xbe\xef" * 10000,
"\xde\xad\xbe\xef", # some binary strings.
"^CMD=$'reboot';$CMD",
'^CMD=$"reboot";$CMD',
"^reboot",
"`reboot`",
"a);reboot",
"a);reboot;",
"a);reboot|",
"a)|reboot",
"a)|reboot;", # fuzzdb command injection
"a;reboot",
"a;reboot;",
"a;reboot|",
"a|reboot",
"CMD=$'reboot';$CMD",
'CMD=$"reboot";$CMD',
"FAIL||CMD=$'reboot';$CMD",
'FAIL||CMD=$"reboot";$CMD',
"FAIL||reboot",
"id",
"id;",
"id|",
"reboot",
"reboot;",
"reboot|",
"| reboot",
"|CMD=$'reboot';$CMD",
'|CMD=$"reboot";$CMD',
"|nid",
"|notepad",
"|reboot",
"|reboot;",
"|reboot|",
"|touch /tmp/SULLEY", # command injection.
"||reboot;",
"||reboot|",
]
long_string_seeds = [
"C",
"1",
"<",
">",
"'",
'"',
"/",
"\\",
"?",
"=",
"a=",
"&",
".",
",",
"(",
")",
"]",
"[",
"%",
"*",
"-",
"+",
"{",
"}",
"\x14",
"\x00",
"\xFE", # expands to 4 characters under utf1
"\xFF", # expands to 4 characters under utf1
]
_long_string_lengths = [8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 32768, 0xFFFF]
_long_string_deltas = [-2, -1, 0, 1, 2]
_extra_long_string_lengths = [99999, 100000, 500000, 1000000]
_variable_mutation_multipliers = [2, 10, 100]
def __init__(
self, name=None, default_value="", size=None, padding=b"\x00", encoding="utf-8", max_len=None, *args, **kwargs
):
super(String, self).__init__(name=name, default_value=default_value, *args, **kwargs)
self.size = size
self.max_len = max_len
if self.size is not None:
self.max_len = self.size
self.encoding = encoding
self.padding = padding
if isinstance(padding, str):
self.padding = self.padding.encode(self.encoding)
self._static_num_mutations = None
self.random_indices = {}
local_random = random.Random(0) # We want constant random numbers to generate reproducible test cases
previous_length = 0
# For every length add a random number of random indices to the random_indices dict. Prevent duplicates by
# adding only indices in between previous_length and current length.
for length in self._long_string_lengths:
self.random_indices[length] = local_random.sample(
range(previous_length, length), local_random.randint(1, self._long_string_lengths[0])
)
previous_length = length
def _yield_long_strings(self, sequences):
"""
Given a sequence, yield a number of selectively chosen strings lengths of the given sequence.
@type sequences: list(str)
@param sequences: Sequence to repeat for creation of fuzz strings.
"""
for sequence in sequences:
for size in [
length + delta
for length, delta in itertools.product(self._long_string_lengths, self._long_string_deltas)
]:
if self.max_len is None or size <= self.max_len:
data = sequence * math.ceil(size / len(sequence))
yield data[:size]
else:
break
for size in self._extra_long_string_lengths:
if self.max_len is None or size <= self.max_len:
data = sequence * math.ceil(size / len(sequence))
yield data[:size]
else:
break
if self.max_len is not None:
data = sequence * math.ceil(self.max_len / len(sequence))
yield data
for size in self._long_string_lengths:
if self.max_len is None or size <= self.max_len:
s = "D" * size
for loc in self.random_indices[size]:
yield s[:loc] + "\x00" + s[loc + 1 :] # Replace character at loc with terminator
else:
break
def _yield_variable_mutations(self, default_value):
for length in self._variable_mutation_multipliers:
value = default_value * length
if value not in self._fuzz_library:
yield value
if self.max_len is not None and len(value) >= self.max_len:
break
def _adjust_mutation_for_size(self, fuzz_value):
if self.max_len is not None and self.max_len < len(fuzz_value):
return fuzz_value[: self.max_len]
else:
return fuzz_value
def mutations(self, default_value):
"""
Mutate the primitive by stepping through the fuzz library extended with the "this" library, return False on
completion.
Args:
default_value (str): Default value of element.
Yields:
str: Mutations
"""
last_val = None
for val in itertools.chain(
self._fuzz_library,
self._yield_variable_mutations(default_value),
self._yield_long_strings(self.long_string_seeds),
):
current_val = self._adjust_mutation_for_size(val)
if last_val == current_val:
continue
last_val = current_val
yield current_val
# TODO: Add easy and sane string injection from external file/s
def encode(self, value, mutation_context=None):
value = value.encode(self.encoding, "replace")
# pad undersized library items.
if self.size is not None and len(value) < self.size:
value += self.padding * (self.size - len(value))
return value
def num_mutations(self, default_value):
"""
Calculate and return the total number of mutations for this individual primitive.
Args:
default_value:
Returns:
int: Number of mutated forms this primitive can take
"""
variable_num_mutations = sum(1 for _ in self._yield_variable_mutations(default_value=default_value))
if self._static_num_mutations is None:
# Counting the number of mutations with default value "" results in 0 variable_num_mutations 3 * "" = ""
self._static_num_mutations = sum(1 for _ in self.mutations(default_value=""))
return self._static_num_mutations + variable_num_mutations