From ccc4f5dcf076bfe3a4b603bc2141c36b8debad6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Kiss?= Date: Sun, 7 Feb 2021 19:15:02 +0100 Subject: [PATCH] small improvements --- APIFuzzer | 10 +- README.md | 19 +++- apifuzzer/__init__.py | 2 +- ...apifuzzer_report.py => apifuzzerreport.py} | 12 +- apifuzzer/custom_fuzzers.py | 16 +-- .../fuzzer_target/fuzz_request_sender.py | 2 +- docs/Makefile | 10 ++ docs/apifuzzer.rst | 104 +++--------------- docs/index.rst | 1 + docs/requirements.readthedocs.txt | 1 + setup.py | 37 ++++--- 11 files changed, 93 insertions(+), 121 deletions(-) rename apifuzzer/{apifuzzer_report.py => apifuzzerreport.py} (77%) diff --git a/APIFuzzer b/APIFuzzer index 4be855e..4c706ec 100755 --- a/APIFuzzer +++ b/APIFuzzer @@ -6,6 +6,7 @@ import sys import tempfile from logging import _nameToLevel as levelNames +from apifuzzer.fuzz_utils import FailedToParseFileException from apifuzzer.fuzzer import Fuzzer from apifuzzer.utils import json_data, str2bool from apifuzzer.version import get_version @@ -88,6 +89,13 @@ if __name__ == '__main__': api_definition_file=args.src_file, junit_report_path=args.test_result_dst ) - prog.prepare() + try: + prog.prepare() + except FailedToParseFileException: + print('Failed to parse API definition') + exit(1) + except Exception as e: + print(f'Unexpected exception happened during fuzz test preparation: {e}. Feel free to report the issue') + exit(1) signal.signal(signal.SIGINT, signal_handler) prog.run() diff --git a/README.md b/README.md index 88cfd39..8871c19 100644 --- a/README.md +++ b/README.md @@ -13,10 +13,21 @@ APIFuzzer reads your API description and step by step fuzzes the fields to validate if you application can cope with the fuzzed parameters. Does not require coding. -### Supported API Description Formats +## Main features +*Parse API definition from local file or remote URL +* JSON and YAML file format support +* GET, POST, PUT, POST, DELETE methods are supported +* Fuzzing of request body, query string, path parameter and request header are supported +* Support CI integration + * Generate JUnit XML test report format + * Send request to alternative URL + * Support HTTP basic auth from configuration + * Save report of failed test in JSON format into the pre-configured folder + * Log to stdout instead of syslog +* Configurable log level + +### Supported API definition formats - [Swagger][] - -### Work in progress - [OpenAPI][] ### Planned @@ -61,7 +72,7 @@ optional arguments: --src_url SRC_URL API definition url. JSON and YAML format is supported -r REPORT_DIR, --report_dir REPORT_DIR Directory where error reports will be saved. Default is temporally generated directory - --level LEVEL Test deepness: [1,2], higher is the deeper !!!Not implemented!!! + --level LEVEL Test deepness: [1,2], the higher is the deeper (In progress) -u ALTERNATE_URL, --url ALTERNATE_URL Use CLI defined url instead compile the url from the API definition. Useful for testing -t TEST_RESULT_DST, --test_report TEST_RESULT_DST diff --git a/apifuzzer/__init__.py b/apifuzzer/__init__.py index 17ea304..1b1a934 100644 --- a/apifuzzer/__init__.py +++ b/apifuzzer/__init__.py @@ -1 +1 @@ -__version__ = '0.9.8' +__version__ = '0.9.9' diff --git a/apifuzzer/apifuzzer_report.py b/apifuzzer/apifuzzerreport.py similarity index 77% rename from apifuzzer/apifuzzer_report.py rename to apifuzzer/apifuzzerreport.py index 9641d17..9161e28 100644 --- a/apifuzzer/apifuzzer_report.py +++ b/apifuzzer/apifuzzerreport.py @@ -4,14 +4,18 @@ from apifuzzer.utils import try_b64encode -class Apifuzzer_Report(Report): - - def is_failed(self): - pass +class ApifuzzerReport(Report): def __init__(self, name): super().__init__(name) + def is_failed(self): + """ + .. deprecated:: 0.6.7 + use :func:`~kitty.data.report.Report.get_status` + """ + raise NotImplementedError('API was changed, use get_status instead') + def to_dict(self, encoding='base64'): """ Return a dictionary version of the report diff --git a/apifuzzer/custom_fuzzers.py b/apifuzzer/custom_fuzzers.py index 22e1757..de8eb74 100644 --- a/apifuzzer/custom_fuzzers.py +++ b/apifuzzer/custom_fuzzers.py @@ -8,19 +8,20 @@ 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: - for st in range(0, 1114111): - try: - print('{}-> {}'.format(st, chr(st))) - except (UnicodeEncodeError, ValueError): - pass + :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=100, num_mutations=80): @@ -61,7 +62,6 @@ 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): diff --git a/apifuzzer/fuzzer_target/fuzz_request_sender.py b/apifuzzer/fuzzer_target/fuzz_request_sender.py index 3290c34..4966c28 100644 --- a/apifuzzer/fuzzer_target/fuzz_request_sender.py +++ b/apifuzzer/fuzzer_target/fuzz_request_sender.py @@ -9,7 +9,7 @@ from junit_xml import TestSuite, TestCase, to_xml_report_file from kitty.targets.server import ServerTarget -from apifuzzer.apifuzzer_report import Apifuzzer_Report as Report +from apifuzzer.apifuzzerreport import ApifuzzerReport as Report from apifuzzer.fuzzer_target.request_base_functions import FuzzerTargetBase from apifuzzer.utils import try_b64encode, init_pycurl, get_logger diff --git a/docs/Makefile b/docs/Makefile index c853e0d..7870b40 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -2,11 +2,21 @@ SHELL:=$(shell which bash) parent_dir:=$(shell dirname $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))) .PHONY: all all: clean +all: readme all: apidoc all: sphinx + SPHINXOPTS= -j 4 -d _build/doctrees . + +.PHONY: readme +readme: + rm -f readme.md readme.rst + sed -e '1,/APIFuzzer — HTTP API Testing Framework/d' ../README.md > readme.md + m2r readme.md + + .PHONY: sphinx sphinx: sphinx-build -b html $(SPHINXOPTS) _build/html diff --git a/docs/apifuzzer.rst b/docs/apifuzzer.rst index 195b6c7..5560f85 100644 --- a/docs/apifuzzer.rst +++ b/docs/apifuzzer.rst @@ -4,93 +4,23 @@ apifuzzer package Submodules ---------- -apifuzzer.apifuzzer\_report module ----------------------------------- - -.. automodule:: apifuzzer.apifuzzer_report - :members: - :undoc-members: - :show-inheritance: - -apifuzzer.base\_template module -------------------------------- - -.. automodule:: apifuzzer.base_template - :members: - :undoc-members: - :show-inheritance: - -apifuzzer.custom\_fuzzers module --------------------------------- - -.. automodule:: apifuzzer.custom_fuzzers - :members: - :undoc-members: - :show-inheritance: - -apifuzzer.exceptions module ---------------------------- - -.. automodule:: apifuzzer.exceptions - :members: - :undoc-members: - :show-inheritance: - -apifuzzer.fuzz\_utils module ----------------------------- - -.. automodule:: apifuzzer.fuzz_utils - :members: - :undoc-members: - :show-inheritance: - -apifuzzer.fuzzer module ------------------------ - -.. automodule:: apifuzzer.fuzzer - :members: - :undoc-members: - :show-inheritance: - -apifuzzer.openapi\_template\_generator module ---------------------------------------------- - -.. automodule:: apifuzzer.openapi_template_generator - :members: - :undoc-members: - :show-inheritance: - -apifuzzer.server\_fuzzer module -------------------------------- - -.. automodule:: apifuzzer.server_fuzzer - :members: - :undoc-members: - :show-inheritance: - -apifuzzer.template\_generator\_base module ------------------------------------------- - -.. automodule:: apifuzzer.template_generator_base - :members: - :undoc-members: - :show-inheritance: - -apifuzzer.utils module ----------------------- - -.. automodule:: apifuzzer.utils - :members: - :undoc-members: - :show-inheritance: - -apifuzzer.version module ------------------------- - -.. automodule:: apifuzzer.version - :members: - :undoc-members: - :show-inheritance: +.. toctree:: + :maxdepth: 4 + + apifuzzer.apifuzzerreport + apifuzzer.base_template + apifuzzer.custom_fuzzers + apifuzzer.exceptions + apifuzzer.fuzz_model + apifuzzer.fuzz_utils + apifuzzer.fuzzer + apifuzzer.move_json_parts + apifuzzer.openapi_template_generator + apifuzzer.resolve_json_reference + apifuzzer.server_fuzzer + apifuzzer.template_generator_base + apifuzzer.utils + apifuzzer.version Module contents --------------- diff --git a/docs/index.rst b/docs/index.rst index 4c0cab2..9252e8f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -4,4 +4,5 @@ Table of contents ================= .. toctree:: + readme modules diff --git a/docs/requirements.readthedocs.txt b/docs/requirements.readthedocs.txt index 0bdbb7e..3e7cb24 100644 --- a/docs/requirements.readthedocs.txt +++ b/docs/requirements.readthedocs.txt @@ -2,3 +2,4 @@ kittyfuzzer==0.7.4 ruamel.yaml==0.16.12 junit-xml==1.9 jsonpath-ng==1.5.2 +m2r diff --git a/setup.py b/setup.py index 87d158c..380b4a2 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,6 @@ """ Preparation instructions: https://packaging.python.org/tutorials/packaging-projects/ """ -import codecs import os.path import re @@ -11,38 +10,46 @@ here = os.path.abspath(os.path.dirname(__file__)) __version__ = re.search( - r'__version__\s*=\s*[\'"]([^\'"]*)[\'"]', # It excludes inline comment too + r'__version__\s*=\s*[\'"]([^\'"]*)[\'"]', open("apifuzzer/__init__.py").read(), ).group(1) -REQUIREMENTS_FILE_PATH = os.path.join( - os.path.abspath(os.path.dirname(__file__)), "requirements.txt" -) +def get_readme(): + readme = list() + with open(os.path.join(here, "README.md"), "r") as f: + skip_lines = True + for line in f.read().splitlines(): + if line.startswith('# APIFuzzer — HTTP API Testing Framework'): + skip_lines = False + if skip_lines: + continue + else: + readme.append(line) + return '\n'.join(readme) -def read(*parts): - return codecs.open(os.path.join(here, *parts), 'r').read() +def get_requirements(): + requirements = list() + with open(os.path.join(here, "requirements.txt"), "r") as f: + for line in f.read().splitlines(): + if not line.startswith("#") and not line.startswith("--"): + requirements.append(line) + return '\n'.join(requirements) -with open(REQUIREMENTS_FILE_PATH, "r") as f: - REQUIREMENTS_FILE = [ - line - for line in f.read().splitlines() - if not line.startswith("#") and not line.startswith("--") - ] setup_options = dict( name='APIFuzzer', version=__version__, description='Fuzz test your application using Swagger or OpenAPI definition without coding', - long_description=read('README.md'), + long_description=get_readme(), long_description_content_type="text/markdown", author='Peter Kiss', author_email='peter.kiss@linuxadm.hu', url='https://github.com/KissPeter/APIFuzzer/', scripts=['APIFuzzer'], packages=find_packages(exclude=["test"]), - install_requires=REQUIREMENTS_FILE, + install_requires=get_requirements(), license="GNU General Public License v3.0", classifiers=[ # https://pypi.org/classifiers/ 'Development Status :: 4 - Beta',