Implementation
This commit is contained in:
parent
31774712fb
commit
170459121e
144
Fuzzer/Fuzzer.ipynb
Normal file
144
Fuzzer/Fuzzer.ipynb
Normal file
@ -0,0 +1,144 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"import sys\n",
|
||||
"import random\n",
|
||||
"import string\n",
|
||||
"\n",
|
||||
"sys.path.append(\"ProjectToFuzzTest\")\n",
|
||||
"from jsonparse import value_parser"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Total tests: 100000\n",
|
||||
"Successful tests: 94361\n",
|
||||
"Failed tests: 5639\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"def generate_random_json(depth=0, max_depth=5, valid=True):\n",
|
||||
" if depth >= max_depth:\n",
|
||||
" choice = random.choice([\"null\", \"boolean\", \"number\", \"string\"])\n",
|
||||
" else:\n",
|
||||
" choice = random.choice([\"null\", \"boolean\", \"number\", \"string\", \"array\", \"object\"])\n",
|
||||
"\n",
|
||||
" if choice == \"null\":\n",
|
||||
" return \"null\"\n",
|
||||
" elif choice == \"boolean\":\n",
|
||||
" return random.choice([\"true\", \"false\"])\n",
|
||||
" elif choice == \"number\":\n",
|
||||
" return str(random.uniform(-1e6, 1e6))\n",
|
||||
" elif choice == \"string\":\n",
|
||||
" return '\"' + ''.join(random.choices(string.ascii_letters + string.digits, k=10)) + '\"'\n",
|
||||
" elif choice == \"array\":\n",
|
||||
" return '[' + ', '.join(generate_random_json(depth + 1, max_depth, valid) for _ in range(random.randint(0, 5))) + ']'\n",
|
||||
" elif choice == \"object\":\n",
|
||||
" return '{' + ', '.join(f'\"{random.choice(string.ascii_letters)}\": {generate_random_json(depth + 1, max_depth, valid)}' for _ in range(random.randint(0, 5))) + '}'\n",
|
||||
"\n",
|
||||
"def introduce_common_json_errors(json_str):\n",
|
||||
" error_choice = random.choice([\"missing_quote\", \"missing_bracket\", \"extra_comma\", \"trailing_comma\"])\n",
|
||||
" \n",
|
||||
" if error_choice == \"missing_quote\":\n",
|
||||
" pos = json_str.find('\"')\n",
|
||||
" if pos != -1:\n",
|
||||
" return json_str[:pos] + json_str[pos+1:]\n",
|
||||
" elif error_choice == \"missing_bracket\":\n",
|
||||
" if \"{\" in json_str or \"[\" in json_str:\n",
|
||||
" return json_str[:-1]\n",
|
||||
" elif error_choice == \"extra_comma\":\n",
|
||||
" if \"{\" in json_str or \"[\" in json_str:\n",
|
||||
" return json_str[:-1] + \",\"\n",
|
||||
" elif error_choice == \"trailing_comma\":\n",
|
||||
" if \"{\" in json_str or \"[\" in json_str:\n",
|
||||
" parts = json_str.split(\",\")\n",
|
||||
" return \",\".join(parts[:-1]) + \",\"\n",
|
||||
"\n",
|
||||
" return json_str\n",
|
||||
"\n",
|
||||
"def generate_test_json():\n",
|
||||
" if random.choice([True, False]):\n",
|
||||
" return generate_random_json(valid=True)\n",
|
||||
" else:\n",
|
||||
" valid_json = generate_random_json(valid=True)\n",
|
||||
" return introduce_common_json_errors(valid_json)\n",
|
||||
"\n",
|
||||
"def log_result(test_input, result, error, log_file):\n",
|
||||
" with open(log_file, 'a') as f:\n",
|
||||
" if error:\n",
|
||||
" f.write(f\"Input: {test_input}\\nException: {error}\\n\\n\")\n",
|
||||
" else:\n",
|
||||
" f.write(f\"Input: {test_input}\\nOutput: {result}\\n\\n\")\n",
|
||||
"\n",
|
||||
"def analyze_logs(log_file):\n",
|
||||
" total_tests = 0\n",
|
||||
" successful_tests = 0\n",
|
||||
" failed_tests = 0\n",
|
||||
" with open(log_file, 'r') as f:\n",
|
||||
" logs = f.read().split(\"\\n\\n\")\n",
|
||||
" total_tests = len(logs) - 1\n",
|
||||
" for log in logs:\n",
|
||||
" if \"Exception\" in log:\n",
|
||||
" failed_tests += 1\n",
|
||||
" elif \"Output\" in log:\n",
|
||||
" successful_tests += 1\n",
|
||||
" return total_tests, successful_tests, failed_tests\n",
|
||||
"\n",
|
||||
"def run_fuzzer(parser, num_tests=100, log_file='fuzzer_log.txt'):\n",
|
||||
" if os.path.exists(log_file):\n",
|
||||
" os.remove(log_file)\n",
|
||||
" for _ in range(num_tests):\n",
|
||||
" test_input = generate_test_json()\n",
|
||||
" try:\n",
|
||||
" result = parser(test_input)\n",
|
||||
" if result is None:\n",
|
||||
" log_result(test_input, \"None\", None, log_file)\n",
|
||||
" else:\n",
|
||||
" log_result(test_input, result[0], None, log_file)\n",
|
||||
" except Exception as e:\n",
|
||||
" log_result(test_input, None, e, log_file)\n",
|
||||
" total_tests, successful_tests, failed_tests = analyze_logs(log_file)\n",
|
||||
" print(f\"Total tests: {total_tests}\")\n",
|
||||
" print(f\"Successful tests: {successful_tests}\")\n",
|
||||
" print(f\"Failed tests: {failed_tests}\")\n",
|
||||
"\n",
|
||||
"run_fuzzer(value_parser, num_tests=100_000)"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "base",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.9.19"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
300000
Fuzzer/fuzzer_log.txt
Normal file
300000
Fuzzer/fuzzer_log.txt
Normal file
File diff suppressed because it is too large
Load Diff
26
ProjectToFuzzTest/Examples/ex1.json
Normal file
26
ProjectToFuzzTest/Examples/ex1.json
Normal file
@ -0,0 +1,26 @@
|
||||
{"widget": {
|
||||
"debug": "on",
|
||||
"window": {
|
||||
"title": "Sample Konfabulator Widget",
|
||||
"name": "main_window",
|
||||
"width": 500,
|
||||
"height": 500
|
||||
},
|
||||
"image": {
|
||||
"src": "Images/Sun.png",
|
||||
"name": "sun1",
|
||||
"hOffset": 250,
|
||||
"vOffset": 250,
|
||||
"alignment": "center"
|
||||
},
|
||||
"text": {
|
||||
"data": "Click Here",
|
||||
"size": 36,
|
||||
"style": "bold",
|
||||
"name": "text1",
|
||||
"hOffset": 250,
|
||||
"vOffset": 100,
|
||||
"alignment": "center",
|
||||
"onMouseUp": "sun1.opacity = (sun1.opacity / 100) * 90;"
|
||||
}
|
||||
}}
|
26
ProjectToFuzzTest/Examples/ex2.json
Normal file
26
ProjectToFuzzTest/Examples/ex2.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"id": "0001",
|
||||
"type": "donut",
|
||||
"name": "Cake",
|
||||
"ppu": 0.55,
|
||||
"batters":
|
||||
{
|
||||
"batter":
|
||||
[
|
||||
{ "id": "1001", "type": "Regular" },
|
||||
{ "id": "1002", "type": "Chocolate" },
|
||||
{ "id": "1003", "type": "Blueberry" },
|
||||
{ "id": "1004", "type": "Devil's Food" }
|
||||
]
|
||||
},
|
||||
"topping":
|
||||
[
|
||||
{ "id": "5001", "type": "None" },
|
||||
{ "id": "5002", "type": "Glazed" },
|
||||
{ "id": "5005", "type": "Sugar" },
|
||||
{ "id": "5007", "type": "Powdered Sugar" },
|
||||
{ "id": "5006", "type": "Chocolate with Sprinkles" },
|
||||
{ "id": "5003", "type": "Chocolate" },
|
||||
{ "id": "5004", "type": "Maple" }
|
||||
]
|
||||
}
|
70
ProjectToFuzzTest/Examples/ex3.json
Normal file
70
ProjectToFuzzTest/Examples/ex3.json
Normal file
@ -0,0 +1,70 @@
|
||||
[
|
||||
{
|
||||
"id": "0001",
|
||||
"type": "donut",
|
||||
"name": "Cake",
|
||||
"ppu": 0.55,
|
||||
"batters":
|
||||
{
|
||||
"batter":
|
||||
[
|
||||
{ "id": "1001", "type": "Regular" },
|
||||
{ "id": "1002", "type": "Chocolate" },
|
||||
{ "id": "1003", "type": "Blueberry" },
|
||||
{ "id": "1004", "type": "Devil's Food" }
|
||||
]
|
||||
},
|
||||
"topping":
|
||||
[
|
||||
{ "id": "5001", "type": "None" },
|
||||
{ "id": "5002", "type": "Glazed" },
|
||||
{ "id": "5005", "type": "Sugar" },
|
||||
{ "id": "5007", "type": "Powdered Sugar" },
|
||||
{ "id": "5006", "type": "Chocolate with Sprinkles" },
|
||||
{ "id": "5003", "type": "Chocolate" },
|
||||
{ "id": "5004", "type": "Maple" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "0002",
|
||||
"type": "donut",
|
||||
"name": "Raised",
|
||||
"ppu": 0.55,
|
||||
"batters":
|
||||
{
|
||||
"batter":
|
||||
[
|
||||
{ "id": "1001", "type": "Regular" }
|
||||
]
|
||||
},
|
||||
"topping":
|
||||
[
|
||||
{ "id": "5001", "type": "None" },
|
||||
{ "id": "5002", "type": "Glazed" },
|
||||
{ "id": "5005", "type": "Sugar" },
|
||||
{ "id": "5003", "type": "Chocolate" },
|
||||
{ "id": "5004", "type": "Maple" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "0003",
|
||||
"type": "donut",
|
||||
"name": "Old Fashioned",
|
||||
"ppu": 0.55,
|
||||
"batters":
|
||||
{
|
||||
"batter":
|
||||
[
|
||||
{ "id": "1001", "type": "Regular" },
|
||||
{ "id": "1002", "type": "Chocolate" }
|
||||
]
|
||||
},
|
||||
"topping":
|
||||
[
|
||||
{ "id": "5001", "type": "None" },
|
||||
{ "id": "5002", "type": "Glazed" },
|
||||
{ "id": "5003", "type": "Chocolate" },
|
||||
{ "id": "5004", "type": "Maple" }
|
||||
]
|
||||
}
|
||||
]
|
88
ProjectToFuzzTest/Examples/ex4.json
Normal file
88
ProjectToFuzzTest/Examples/ex4.json
Normal file
@ -0,0 +1,88 @@
|
||||
{"web-app": {
|
||||
"servlet": [
|
||||
{
|
||||
"servlet-name": "cofaxCDS",
|
||||
"servlet-class": "org.cofax.cds.CDSServlet",
|
||||
"init-param": {
|
||||
"configGlossary:installationAt": "Philadelphia, PA",
|
||||
"configGlossary:adminEmail": "ksm@pobox.com",
|
||||
"configGlossary:poweredBy": "Cofax",
|
||||
"configGlossary:poweredByIcon": "/images/cofax.gif",
|
||||
"configGlossary:staticPath": "/content/static",
|
||||
"templateProcessorClass": "org.cofax.WysiwygTemplate",
|
||||
"templateLoaderClass": "org.cofax.FilesTemplateLoader",
|
||||
"templatePath": "templates",
|
||||
"templateOverridePath": "",
|
||||
"defaultListTemplate": "listTemplate.htm",
|
||||
"defaultFileTemplate": "articleTemplate.htm",
|
||||
"useJSP": false,
|
||||
"jspListTemplate": "listTemplate.jsp",
|
||||
"jspFileTemplate": "articleTemplate.jsp",
|
||||
"cachePackageTagsTrack": 200,
|
||||
"cachePackageTagsStore": 200,
|
||||
"cachePackageTagsRefresh": 60,
|
||||
"cacheTemplatesTrack": 100,
|
||||
"cacheTemplatesStore": 50,
|
||||
"cacheTemplatesRefresh": 15,
|
||||
"cachePagesTrack": 200,
|
||||
"cachePagesStore": 100,
|
||||
"cachePagesRefresh": 10,
|
||||
"cachePagesDirtyRead": 10,
|
||||
"searchEngineListTemplate": "forSearchEnginesList.htm",
|
||||
"searchEngineFileTemplate": "forSearchEngines.htm",
|
||||
"searchEngineRobotsDb": "WEB-INF/robots.db",
|
||||
"useDataStore": true,
|
||||
"dataStoreClass": "org.cofax.SqlDataStore",
|
||||
"redirectionClass": "org.cofax.SqlRedirection",
|
||||
"dataStoreName": "cofax",
|
||||
"dataStoreDriver": "com.microsoft.jdbc.sqlserver.SQLServerDriver",
|
||||
"dataStoreUrl": "jdbc:microsoft:sqlserver://LOCALHOST:1433;DatabaseName=goon",
|
||||
"dataStoreUser": "sa",
|
||||
"dataStorePassword": "dataStoreTestQuery",
|
||||
"dataStoreTestQuery": "SET NOCOUNT ON;select test='test';",
|
||||
"dataStoreLogFile": "/usr/local/tomcat/logs/datastore.log",
|
||||
"dataStoreInitConns": 10,
|
||||
"dataStoreMaxConns": 100,
|
||||
"dataStoreConnUsageLimit": 100,
|
||||
"dataStoreLogLevel": "debug",
|
||||
"maxUrlLength": 500}},
|
||||
{
|
||||
"servlet-name": "cofaxEmail",
|
||||
"servlet-class": "org.cofax.cds.EmailServlet",
|
||||
"init-param": {
|
||||
"mailHost": "mail1",
|
||||
"mailHostOverride": "mail2"}},
|
||||
{
|
||||
"servlet-name": "cofaxAdmin",
|
||||
"servlet-class": "org.cofax.cds.AdminServlet"},
|
||||
|
||||
{
|
||||
"servlet-name": "fileServlet",
|
||||
"servlet-class": "org.cofax.cds.FileServlet"},
|
||||
{
|
||||
"servlet-name": "cofaxTools",
|
||||
"servlet-class": "org.cofax.cms.CofaxToolsServlet",
|
||||
"init-param": {
|
||||
"templatePath": "toolstemplates/",
|
||||
"log": 1,
|
||||
"logLocation": "/usr/local/tomcat/logs/CofaxTools.log",
|
||||
"logMaxSize": "",
|
||||
"dataLog": 1,
|
||||
"dataLogLocation": "/usr/local/tomcat/logs/dataLog.log",
|
||||
"dataLogMaxSize": "",
|
||||
"removePageCache": "/content/admin/remove?cache=pages&id=",
|
||||
"removeTemplateCache": "/content/admin/remove?cache=templates&id=",
|
||||
"fileTransferFolder": "/usr/local/tomcat/webapps/content/fileTransferFolder",
|
||||
"lookInContext": 1,
|
||||
"adminGroupID": 4,
|
||||
"betaServer": true}}],
|
||||
"servlet-mapping": {
|
||||
"cofaxCDS": "/",
|
||||
"cofaxEmail": "/cofaxutil/aemail/*",
|
||||
"cofaxAdmin": "/admin/*",
|
||||
"fileServlet": "/static/*",
|
||||
"cofaxTools": "/tools/*"},
|
||||
|
||||
"taglib": {
|
||||
"taglib-uri": "cofax.tld",
|
||||
"taglib-location": "/WEB-INF/tlds/cofax.tld"}}}
|
3
ProjectToFuzzTest/Examples/simple.json
Normal file
3
ProjectToFuzzTest/Examples/simple.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"welcome": -0.5e2,
|
||||
}
|
110
ProjectToFuzzTest/jsonparse.py
Normal file
110
ProjectToFuzzTest/jsonparse.py
Normal file
@ -0,0 +1,110 @@
|
||||
from functools import reduce
|
||||
import re
|
||||
import pprint
|
||||
|
||||
|
||||
def array_parser(data):
|
||||
if data[0] != "[":
|
||||
return None
|
||||
parse_list = []
|
||||
data = data[1:].strip()
|
||||
while len(data):
|
||||
res = value_parser(data)
|
||||
if res is None:
|
||||
return None
|
||||
parse_list.append(res[0])
|
||||
data = res[1].strip()
|
||||
if data[0] == "]":
|
||||
return [parse_list, data[1:].strip()]
|
||||
res = comma_parser(data)
|
||||
if res is None:
|
||||
return None
|
||||
data = res[1].strip()
|
||||
|
||||
|
||||
def boolean_parser(data):
|
||||
if data[0:4] == "true":
|
||||
return [True, data[4:].strip()]
|
||||
elif data[0:5] == "false":
|
||||
return [False, data[5:].strip()]
|
||||
|
||||
|
||||
def colon_parser(data):
|
||||
if data[0] == ":":
|
||||
return [data[0], data[1:].lstrip()]
|
||||
|
||||
|
||||
def comma_parser(data):
|
||||
if data and data[0] == ",":
|
||||
return [data[0], data[1:].strip()]
|
||||
|
||||
|
||||
def null_parser(data):
|
||||
if data[0:4] == "null":
|
||||
return [None, data[4:].strip()]
|
||||
|
||||
|
||||
def number_parser(data):
|
||||
regex_find = re.findall("^(-?(?:[0-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?)",
|
||||
data)
|
||||
if not regex_find:
|
||||
return None
|
||||
pos = len(regex_find[0])
|
||||
try:
|
||||
return [int(regex_find[0]), data[pos:].strip()]
|
||||
except ValueError:
|
||||
return [float(regex_find[0]), data[pos:].strip()]
|
||||
|
||||
|
||||
def object_parser(data):
|
||||
if data[0] != "{":
|
||||
return None
|
||||
parse_dict = {}
|
||||
data = data[1:].strip()
|
||||
while data[0] != "}":
|
||||
res = string_parser(data)
|
||||
if res is None:
|
||||
return None
|
||||
id = res[0]
|
||||
res = colon_parser(res[1].strip())
|
||||
if res is None:
|
||||
return None
|
||||
res = value_parser(res[1].strip())
|
||||
if res is None:
|
||||
return None
|
||||
parse_dict[id] = res[0]
|
||||
data = res[1].lstrip()
|
||||
res = comma_parser(data)
|
||||
if res:
|
||||
data = res[1].strip()
|
||||
return [parse_dict, data[1:]]
|
||||
|
||||
|
||||
def string_parser(data):
|
||||
if data[0] == '"':
|
||||
data = data[1:]
|
||||
pos = data.find('"')
|
||||
return [data[:pos], data[pos + 1:].strip()]
|
||||
|
||||
|
||||
def all_parsers(*args):
|
||||
return lambda data: (reduce(lambda f, g: f if f(data) else g, args)(data))
|
||||
|
||||
|
||||
value_parser = all_parsers(null_parser, number_parser, boolean_parser,
|
||||
string_parser, object_parser, array_parser)
|
||||
|
||||
|
||||
def main():
|
||||
file_name = "PBK\Fuzzing\ProjectToFuzzTest\Examples\ex1.json"
|
||||
with open(file_name, "r") as f:
|
||||
data = f.read()
|
||||
res = value_parser(data.strip())
|
||||
try:
|
||||
pprint.pprint(res[0])
|
||||
except TypeError:
|
||||
print(None)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
31
README.md
31
README.md
@ -1,2 +1,31 @@
|
||||
# Fuzzer
|
||||
# Projekt - Podstawy bezpieczeństwa komputerowego
|
||||
|
||||
W ramach projektu zaimplementowany zostały fuzzer do testowania JSON parsera. <br>
|
||||
Implementacja JSON parsera znajduje się na githubie: https://github.com/geekskool/python-json-parser
|
||||
|
||||
## Opis działania fuzzera
|
||||
Fuzzer w losowy sposób generuje poprawne JSON-y, a następnie wprowadza do nich popularne błędy, aby sprawdzić, jak parser radzi sobie z niepoprawnymi danymi. Popularne błędy obejmują:
|
||||
- brakujące zamknięcie cudzysłowu.
|
||||
- brakujące zamknięcie nawiasu klamrowego lub kwadratowego.
|
||||
- dodatkowy przecinek na końcu listy lub obiektu.
|
||||
- brakujący dwukropek w obiekcie.
|
||||
|
||||
Wyniki testów są zapisywane do pliku logów, a następnie analizowane, aby ocenić skuteczność parsera.
|
||||
|
||||
## Wynik przeprowadzonych testów
|
||||
Przeprowadzono 100,000 testów, które trwały 28 sekund.
|
||||
- Powiodło się 94,361 testów.
|
||||
- Nie przeszło 5,639 testów.
|
||||
|
||||
## Wnioski
|
||||
Wszystkie JSON-y, które nie zostały celowo "zepsute" poprzez wprowadzenie popularnych błędów, przeszły testy pomyślnie. Oznacza to, że algorytm jest w stanie poprawnie sparsować każdy poprawny JSON niezależnie od danych wewnątrz.
|
||||
|
||||
JSON-y zawierające typowe błędy, takie jak:
|
||||
- niedomknięty cudzysłów,
|
||||
- podwójny przecinek,
|
||||
- brakujący nawias klamrowy,
|
||||
- brakujący dwukropek w obiekcie,
|
||||
|
||||
spowodowały niepowodzenie testów.
|
||||
|
||||
Aby algorytm przeszedł wszystkie testy, należałoby dodać odpowiednią obsługę błędnych JSON-ów, co pozwoliłoby na wykrywanie i raportowanie tych błędów zamiast generowania wyjątków lub niepoprawnych wyników.
|
Loading…
Reference in New Issue
Block a user