diff --git a/README.md b/README.md index 1c078d1..107780e 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,7 @@ optional arguments: --ignore-exceptions Ignores all exceptions raised during fuzzing (aka. only fails when vulnerabilities are found). --disable-unicode Disable unicode characters in fuzzing, only use ASCII. + --depth DEPTH Maximum depth for generating nested fuzz parameters. ``` diff --git a/fuzz_lightyear/config.py b/fuzz_lightyear/config.py new file mode 100644 index 0000000..44cd64e --- /dev/null +++ b/fuzz_lightyear/config.py @@ -0,0 +1,4 @@ +ENABLE_COLOR = True +MAX_FUZZ_DEPTH = 6 +SEED_RANDOM_LENGTH = 128 +UNICODE_ENABLED = True diff --git a/fuzz_lightyear/fuzzer.py b/fuzz_lightyear/fuzzer.py index fc09f35..1471bd7 100644 --- a/fuzz_lightyear/fuzzer.py +++ b/fuzz_lightyear/fuzzer.py @@ -30,6 +30,7 @@ def _fuzz_parameter( parameter: Dict[str, Any], operation_id: str = None, required: bool = False, + depth: int = 0, ) -> SearchStrategy: """ :param required: for object types, the required parameter is in a @@ -78,7 +79,12 @@ def _fuzz_parameter( } fuzz_fn = mapping[_type] if fuzz_fn in (_fuzz_object, _fuzz_array): - strategy = fuzz_fn(parameter, operation_id, required=required) # type: ignore + strategy = fuzz_fn( + parameter, + operation_id, + depth + 1, + required=required, + ) # type: ignore else: strategy = fuzz_fn(parameter, required=required) # type: ignore @@ -168,6 +174,7 @@ def _fuzz_boolean( def _fuzz_array( parameter: Dict[str, Any], operation_id: str = None, + depth: int = 0, required: bool = False, ) -> SearchStrategy: item = parameter['items'] @@ -175,12 +182,14 @@ def _fuzz_array( # TODO: Handle `oneOf` strategy = st.lists( - elements=_fuzz_parameter(item, operation_id, required=required), + elements=_fuzz_parameter(item, operation_id, required=required, depth=depth + 1), min_size=parameter.get( 'minItems', 0 if not required else 1, ), - max_size=parameter.get('maxItems', None), + max_size=(0 + if depth > get_settings().max_fuzz_depth + else parameter.get('maxItems', None)), ) if not required: return st.one_of(st.none(), strategy) @@ -191,10 +200,18 @@ def _fuzz_array( def _fuzz_object( parameter: Dict[str, Any], operation_id: str = None, + depth: int = 0, **kwargs: Any, ) -> SearchStrategy: # TODO: Handle `additionalProperties` output = {} + + if ( + depth > get_settings().max_fuzz_depth or + 'properties' not in parameter + ): + return st.none() + for name, specification in parameter['properties'].items(): try: strategy = _get_strategy_from_factory( @@ -224,6 +241,7 @@ def _fuzz_object( specification, operation_id, bool(required), + depth + 1, ) return st.fixed_dictionaries(output) diff --git a/fuzz_lightyear/main.py b/fuzz_lightyear/main.py index 5399dc3..a75f1a3 100644 --- a/fuzz_lightyear/main.py +++ b/fuzz_lightyear/main.py @@ -12,6 +12,7 @@ from hypothesis.errors import NonInteractiveExampleWarning from swagger_spec_validator.common import SwaggerValidationError # type: ignore +from .config import MAX_FUZZ_DEPTH from .datastore import get_excluded_operations from .datastore import get_non_vulnerable_operations from .datastore import get_setup_fixtures @@ -56,6 +57,7 @@ def main(argv: Optional[List[str]] = None) -> int: seed=args.seed, ignore_exceptions=args.ignore_exceptions, disable_unicode=args.disable_unicode, + max_fuzz_depth=args.depth, ) outputter.show_results() @@ -112,6 +114,7 @@ def run_tests( seed: int = None, ignore_exceptions: bool = False, disable_unicode: bool = False, + max_fuzz_depth: int = MAX_FUZZ_DEPTH, ) -> ResultFormatter: """ :param tests: list of tests to run. @@ -121,12 +124,14 @@ def run_tests( :param seed: used for random generation of test input :param ignore_exceptions: if True, ignores HTTP exceptions to requests. :param disable_unicode: if True, only use ASCII characters to fuzz strings + :param max_fuzz_depth: used for preventing recursion Objects with self reference """ if seed is not None: get_settings().seed = seed if disable_unicode: get_settings().unicode_enabled = False + get_settings().max_fuzz_depth = max_fuzz_depth outputter = ResultFormatter() for result in generate_sequences( n=iterations, diff --git a/fuzz_lightyear/settings.py b/fuzz_lightyear/settings.py index 6518616..a7e285a 100644 --- a/fuzz_lightyear/settings.py +++ b/fuzz_lightyear/settings.py @@ -3,12 +3,18 @@ from hypothesis import core +from fuzz_lightyear.config import ENABLE_COLOR +from fuzz_lightyear.config import MAX_FUZZ_DEPTH +from fuzz_lightyear.config import SEED_RANDOM_LENGTH +from fuzz_lightyear.config import UNICODE_ENABLED + class Settings: def __init__(self) -> None: - self.seed = random.getrandbits(128) # type: int - self.unicode_enabled = True # type: bool - self.enable_color = True # type: bool + self.seed = random.getrandbits(SEED_RANDOM_LENGTH) # type: int + self.unicode_enabled = UNICODE_ENABLED # type: bool + self.enable_color = ENABLE_COLOR # type: bool + self.max_fuzz_depth = MAX_FUZZ_DEPTH # type: int @property def seed(self) -> int: diff --git a/fuzz_lightyear/usage.py b/fuzz_lightyear/usage.py index 3afaec6..2d3b7c5 100644 --- a/fuzz_lightyear/usage.py +++ b/fuzz_lightyear/usage.py @@ -104,6 +104,13 @@ def parse_args(argv: Optional[List[str]] = None) -> argparse.Namespace: ), ) + parser.add_argument( + '--depth', + default=6, + type=int, + help='Maximum depth for generating nested fuzz parameters.', + ) + return parser.parse_args(argv)