Implementation

This commit is contained in:
Mateusz Grzegorzewski 2024-06-21 21:49:55 +02:00
parent 31774712fb
commit 170459121e
9 changed files with 300497 additions and 1 deletions

144
Fuzzer/Fuzzer.ipynb Normal file
View 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

File diff suppressed because it is too large Load Diff

View 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;"
}
}}

View 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" }
]
}

View 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" }
]
}
]

View 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"}}}

View File

@ -0,0 +1,3 @@
{
"welcome": -0.5e2,
}

View 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()

View File

@ -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.