Skip to content

Commit

Permalink
WIP #4 and #5
Browse files Browse the repository at this point in the history
  • Loading branch information
KissPeter committed Feb 5, 2018
1 parent 69fa56a commit 3981163
Show file tree
Hide file tree
Showing 8 changed files with 84 additions and 28 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
![Dependencies](https://www.versioneye.com/user/projects/5a4ca3490fb24f05139b8d06/badge.svg?style=flat-square) ![Known Vulnerabilities](https://snyk.io/test/github/KissPeter/APIFuzzer/badge.svg)
[![Join the chat at https://gitter.im/API-Fuzzer/Lobby](https://badges.gitter.im/API-Fuzzer/Lobby.svg)](https://gitter.im/API-Fuzzer/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/eab6434d9bd742e3880d8f589a9cc0a6)](https://www.codacy.com/app/KissPeter/APIFuzzer?utm_source=github.com&utm_medium=referral&utm_content=KissPeter/APIFuzzer&utm_campaign=badger)
[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/1620/badge)](https://bestpractices.coreinfrastructure.org/projects/1620)

# APIFuzzer — HTTP API Testing Framework

[![Codacy Badge](https://api.codacy.com/project/badge/Grade/eab6434d9bd742e3880d8f589a9cc0a6)](https://www.codacy.com/app/KissPeter/APIFuzzer?utm_source=github.com&utm_medium=referral&utm_content=KissPeter/APIFuzzer&utm_campaign=badger)
[![Join the chat at https://gitter.im/API-Fuzzer/Lobby](https://badges.gitter.im/API-Fuzzer/Lobby.svg)](https://gitter.im/API-Fuzzer/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)

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.
Expand Down
5 changes: 5 additions & 0 deletions apifuzzer/base_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,17 @@
class BaseTemplate(object):
url = None
method = None
headers = None
fuzz_params = None

def __init__(self):
self.fuzz_place = None

def compile_template(self):
_url = Static(name='url', value=self.url.encode())
_method = Static(name='method', value=self.method.encode())
template = Template(name='{}_{}'.format(self.url.replace('/', '_'), self.method), fields=[_url, _method])
self.fuzz_place = get_field_type_by_method(self.method)
template.append_fields([Container(name='headers', fields=self.headers)])
template.append_fields([Container(name='{}'.format(self.fuzz_place), fields=self.fuzz_params)])
return template
19 changes: 18 additions & 1 deletion apifuzzer/custom_fuzzers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from kitty.model import RandomBits
from kitty.model import RandomBits, String


# https://lcamtuf.blogspot.hu/2014/08/binary-fuzzing-strategies-what-works.html

class RandomBitsField(RandomBits):
"""Creates a fields which compatible field with String and Delimiter"""

Expand All @@ -9,3 +11,18 @@ def not_implemented(self, func_name):

def __init__(self, value, name, fuzzable=True):
super(RandomBitsField, self).__init__(name=name, value=value, min_length=20, max_length=100, fuzzable=fuzzable, num_mutations=80)


class UnicodeStrings(String):

def __init__(self, value, name, min_length=20, max_length=100, num_mutations=80, fuzzable=True):
self.min_length = min_length
self.max_length = max_length
self.num_mutations = num_mutations
super(UnicodeStrings, self).__init__(name=name, value=value, fuzzable=fuzzable)

def not_implemented(self, func_name):
pass

def _mutate(self):
pass
17 changes: 3 additions & 14 deletions apifuzzer/fuzzer_target.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import logging
from logging import handlers
import json
from time import time

import requests
from urllib import urlencode

from kitty.data.report import Report
from kitty.targets.server import ServerTarget
from requests.exceptions import RequestException
Expand All @@ -17,13 +14,7 @@ def not_implemented(self, func_name):
def __init__(self, name, base_url, report_dir, logger=None):
super(FuzzerTarget, self).__init__(name, logger)
self.base_url = base_url
formtter = logging.Formatter(' [%(levelname)s] %(name)s: %(message)s')
self.logger = logging.getLogger('HTTPFuzzer')
handler = logging.handlers.SysLogHandler(address='/dev/log',
facility=logging.handlers.SysLogHandler.LOG_LOCAL2)
handler.setFormatter(formtter)
self.logger.addHandler(handler)
self.logger.setLevel(logging.WARNING)
self.logger = logger
self._last_sent_request = None
self.accepted_status_codes = list(range(200, 300)) + list(range(400, 500))
self.report_dir = report_dir
Expand All @@ -45,16 +36,14 @@ def save_report_to_disc(self):
report_dump_file.write(json.dumps(self.report.to_dict()))
except Exception as e:
self.logger.error(
'Failed to save report "{}" to {} because: {}'
.format(self.report.to_dict(), self.report_dir, e)
'Failed to save report "{}" to {} because: {}'.format(self.report.to_dict(), self.report_dir, e)
)

def transmit(self, **kwargs):
try:
_req_url = list()
for url_part in self.base_url, kwargs['url']:
_req_url.append(url_part.strip('/'))
#kwargs['url'] = urlencode('/'.join(_req_url))
kwargs['url'] = '/'.join(_req_url)
_return = requests.request(**kwargs)
status_code = _return.status_code
Expand Down
2 changes: 1 addition & 1 deletion apifuzzer/server_fuzzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def _end_message(self):

def _transmit(self, node):
payload = {}
for key in ('url', 'method'):
for key in ('url', 'method', 'headers'):
payload[key] = node.get_field_by_name(key).render().tobytes()
fuzz_place = get_field_type_by_method(node.get_field_by_name('method')._default_value)
payload[fuzz_place] = self._recurse_params(node.get_field_by_name(fuzz_place))
Expand Down
29 changes: 24 additions & 5 deletions apifuzzer/swagger_template_generator.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,50 @@
from base_template import BaseTemplate
from utils import get_sample_data_by_type, get_fuzz_type_by_param_type
from template_generator_base import TemplateGenerator
from enum import Enum


class ParamTypes(Enum):
PATH = 'path'
QUERY = 'query'
HEADER = 'header'
COOKIE = 'cookie'
BODY = 'body'
FORM_DATA = 'formData'


class SwaggerTemplateGenerator(TemplateGenerator):
def __init__(self, api_resources):
def __init__(self, api_resources, logger):
self.api_resources = api_resources
self.logger = logger
self.templates = list()

def process_api_resources(self):
for resource in self.api_resources['paths'].keys():
print(resource)
for method in self.api_resources['paths'][resource].keys():
print(method)
self.logger.debug(method)
template = BaseTemplate()
template.url = resource
template.method = method.upper()
params = list()
for param in self.api_resources['paths'][resource][method].get('parameters',[]):
print(param['name'])
headers = list()
for param in self.api_resources['paths'][resource][method].get('parameters', []):
if param.get('in') in [ParamTypes.HEADER, ParamTypes.COOKIE]:
self.logger.info('{} - {} headers'.format(param['name'], param.get('in')))
list_to_add = headers
else:
self.logger.info('{} - headers'.format(param['name']))
list_to_add = params
fuzz_type = get_fuzz_type_by_param_type(param.get('type'))
params.append(
list_to_add.append(
fuzz_type(
name=param['name'],
value=get_sample_data_by_type(param.get('type'))
)
)

template.headers = headers
template.fuzz_params = params
self.templates.append(template)

Expand Down
12 changes: 12 additions & 0 deletions apifuzzer/utils.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from logging import Formatter
from logging.handlers import SysLogHandler
from kitty.model import *
from custom_fuzzers import RandomBitsField

Expand All @@ -22,3 +24,13 @@ def get_sample_data_by_type(param_type):
'string': 'asd'
}
return types.get(param_type, 'asd')


def set_logger(level='warning'):
syslog = SysLogHandler(address='/dev/log', facility=SysLogHandler.LOG_LOCAL2)
syslog.setFormatter(Formatter('%(pathname)s [%(process)d]: %(levelname)s %(message)s'))
logger = logging.getLogger('APIFuzzer')

logger.setLevel(level=level.upper())
logger.addHandler(syslog)
return logger
23 changes: 18 additions & 5 deletions fuzzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import sys
import argparse
import json
import logging

if sys.version_info[:2] == (2, 7):
from kitty.interfaces import WebInterface
Expand All @@ -11,27 +12,31 @@
from apifuzzer.swagger_template_generator import SwaggerTemplateGenerator
from apifuzzer.fuzzer_target import FuzzerTarget
from apifuzzer.server_fuzzer import OpenApiServerFuzzer
from apifuzzer.utils import set_logger


class Fuzzer(object):
def __init__(self, api_resources, report_dir, test_level, alternate_url=None, test_result_dst=None):

def __init__(self, api_resources, report_dir, test_level, log_level, alternate_url=None, test_result_dst=None):
self.api_resources = api_resources
self.base_url = alternate_url
self.templates = None
self.test_level = test_level
self.report_dir = report_dir
self.test_result_dst = test_result_dst
self.logger = set_logger(log_level)
self.logger.info('APIFuzzer initialized')

def prepare(self):
# here we will be able to branch the template generator if we would like to support other than Swagger
template_generator = SwaggerTemplateGenerator(self.api_resources)
template_generator = SwaggerTemplateGenerator(self.api_resources, self.logger)
template_generator.process_api_resources()
self.templates = template_generator.templates
if not self.base_url:
self.base_url = template_generator.compile_base_url()

def run(self):
target = FuzzerTarget(name='target', base_url=self.base_url, report_dir=self.report_dir)
target = FuzzerTarget(name='target', base_url=self.base_url, report_dir=self.report_dir, logger=self.logger)
interface = WebInterface()
model = GraphModel()
for template in self.templates:
Expand Down Expand Up @@ -61,7 +66,7 @@ def run(self):
help='Directory where error reports will be saved, default: /tmp/',
dest='report_dir',
default='/tmp/')
parser.add_argument('-l', '--level',
parser.add_argument('--level',
type=int,
required=False,
help='Test deepness: [1,2], higher is the deeper !!!Not implemented!!!',
Expand All @@ -79,6 +84,13 @@ def run(self):
help='JUnit test result xml save path !!!Not implemented!!!',
dest='test_result_dst',
default=None)
parser.add_argument('--log',
type=str,
required=False,
help='Use different log level than the default WARNING',
dest='log_level',
default='warning',
choices=[level.lower() for level in logging._levelNames if isinstance(level, str)])
args = parser.parse_args()
api_definition_json = dict()
try:
Expand All @@ -91,7 +103,8 @@ def run(self):
report_dir=args.report_dir,
test_level=args.level,
alternate_url=args.alternate_url,
test_result_dst=args.test_result_dst
test_result_dst=args.test_result_dst,
log_level=args.log_level
)
prog.prepare()
prog.run()

0 comments on commit 3981163

Please sign in to comment.