diff --git a/pyt/argument_helpers.py b/pyt/argument_helpers.py index a2ecee35..45e6d130 100644 --- a/pyt/argument_helpers.py +++ b/pyt/argument_helpers.py @@ -15,7 +15,7 @@ default_trigger_word_file = os.path.join( os.path.dirname(__file__), 'vulnerability_definitions', - 'flask_trigger_words.pyt' + 'flask_trigger_words.json' ) diff --git a/pyt/definition_chains.py b/pyt/definition_chains.py index 8b813e8b..0aee97aa 100644 --- a/pyt/definition_chains.py +++ b/pyt/definition_chains.py @@ -1,4 +1,5 @@ import ast +from collections import defaultdict from .constraint_table import constraint_table from .lattice import Lattice @@ -52,23 +53,20 @@ def build_use_def_chain(cfg_nodes): def build_def_use_chain(cfg_nodes): - def_use = dict() + def_use = defaultdict(list) lattice = Lattice(cfg_nodes, ReachingDefinitionsAnalysis) # For every node for node in cfg_nodes: # That's a definition if isinstance(node, AssignmentNode): - # Make an empty list for it in def_use dict - def_use[node] = list() - # Get its uses - for variable in node.right_hand_side_variables: + for i, variable in enumerate(node.right_hand_side_variables, start=1): # Loop through most of the nodes before it for earlier_node in get_constraint_nodes(node, lattice): # and add to the 'uses list' of each earlier node, when applicable # 'earlier node' here being a simplification if variable in earlier_node.left_hand_side: - def_use[earlier_node].append(node) + def_use[earlier_node].append((i, node)) return def_use diff --git a/pyt/trigger_definitions_parser.py b/pyt/trigger_definitions_parser.py index 7515da4a..63ec3a65 100644 --- a/pyt/trigger_definitions_parser.py +++ b/pyt/trigger_definitions_parser.py @@ -1,11 +1,7 @@ -import os +import json from collections import namedtuple -SANITISER_SEPARATOR = '->' -SOURCES_KEYWORD = 'sources:' -SINKS_KEYWORD = 'sinks:' - Definitions = namedtuple( 'Definitions', ( @@ -15,31 +11,6 @@ ) -def parse_section(iterator): - """Parse a section of a file. Stops at empty line. - - Args: - iterator(File): file descriptor pointing at a definition file. - - Returns: - Iterator of all definitions in the section. - """ - try: - line = next(iterator).rstrip() - while line: - if line.rstrip(): - if SANITISER_SEPARATOR in line: - line = line.split(SANITISER_SEPARATOR) - sink = line[0].rstrip() - sanitisers = list(map(str.strip, line[1].split(','))) - yield (sink, sanitisers) - else: - yield (line, list()) - line = next(iterator).rstrip() - except StopIteration: - return - - def parse(trigger_word_file): """Parse the file for source and sink definitions. @@ -48,11 +19,12 @@ def parse(trigger_word_file): """ sources = list() sinks = list() - with open(trigger_word_file, 'r') as fd: - for line in fd: - line = line.rstrip() - if line == SOURCES_KEYWORD: - sources = list(parse_section(fd)) - elif line == SINKS_KEYWORD: - sinks = list(parse_section(fd)) + + with open(trigger_word_file) as fd: + trigger_dict = json.load(fd) + sources = trigger_dict['sources'] + for sink in trigger_dict['sinks']: + print(f'sink is {sink}') + sinks.append(sink) + return Definitions(sources, sinks) diff --git a/pyt/vulnerability_definitions/django_trigger_words.json b/pyt/vulnerability_definitions/django_trigger_words.json new file mode 100644 index 00000000..65131648 --- /dev/null +++ b/pyt/vulnerability_definitions/django_trigger_words.json @@ -0,0 +1,40 @@ +{ + "sources": [ + "POST.get(", + "GET.get(", + "META.get(", + "POST[", + "GET[", + "META[", + "FILES[", + ".data", + "form[", + "form(", + "mark_safe(", + "cookies[", + "files[", + "SQLAlchemy" + ], + "sinks": { + "replace(": [ + "escape" + ], + "send_file(": [ + "..", + "'..' in" + ], + "execute(": [], + "system(": [], + "filter(": [], + "subprocess.call(": [], + "render_template(": [], + "set_cookie(": [], + "redirect(": [], + "url_for(": [], + "flash(": [], + "jsonify(": [], + "render(": [], + "render_to_response(": [], + "Popen(": [] + } +} diff --git a/pyt/vulnerability_definitions/django_trigger_words.pyt b/pyt/vulnerability_definitions/django_trigger_words.pyt deleted file mode 100644 index 53b54f66..00000000 --- a/pyt/vulnerability_definitions/django_trigger_words.pyt +++ /dev/null @@ -1,32 +0,0 @@ -sources: -POST.get( -GET.get( -META.get( -POST[ -GET[ -META[ -FILES[ -.data -form[ -form( -mark_safe( -cookies[ -files[ -SQLAlchemy - -sinks: -replace( -> escape -send_file( -> '..', '..' in -execute( -system( -filter( -subprocess.call( -render_template( -set_cookie( -redirect( -url_for( -flash( -jsonify( -render( -render_to_response( -Popen( \ No newline at end of file diff --git a/pyt/vulnerability_definitions/flask_trigger_words.json b/pyt/vulnerability_definitions/flask_trigger_words.json new file mode 100644 index 00000000..e971ea62 --- /dev/null +++ b/pyt/vulnerability_definitions/flask_trigger_words.json @@ -0,0 +1,42 @@ +{ + "sources": [ + "request.args.get(", + ".data", + "form[", + "form(", + "Markup(", + "cookies[", + "files[", + "SQLAlchemy" + ], + "sinks": { + "Popen(": {}, + "execute(": { + "args_that_propogate_taint": [ + 1 + ] + }, + "filter(": {}, + "flash(": {}, + "jsonify(": {}, + "redirect(": {}, + "render(": {}, + "render_template(": {}, + "render_to_response(": {}, + "replace(": { + "sanitisers": [ + "escape" + ] + }, + "send_file(": { + "sanitisers": [ + "..", + "'..' in" + ] + }, + "set_cookie(": {}, + "subprocess.call(": {}, + "system(": {}, + "url_for(": {} + } +} diff --git a/pyt/vulnerability_definitions/flask_trigger_words.pyt b/pyt/vulnerability_definitions/flask_trigger_words.pyt deleted file mode 100644 index d7555a87..00000000 --- a/pyt/vulnerability_definitions/flask_trigger_words.pyt +++ /dev/null @@ -1,23 +0,0 @@ -sources: -request.args.get( -.data -form[ -form( -Markup( -cookies[ -files[ -SQLAlchemy - -sinks: -replace( -> escape -send_file( -> '..', '..' in -execute( -system( -filter( -subprocess.call( -render_template( -set_cookie( -redirect( -url_for( -flash( -jsonify( \ No newline at end of file diff --git a/pyt/vulnerability_definitions/test_triggers.json b/pyt/vulnerability_definitions/test_triggers.json new file mode 100644 index 00000000..50f9f60c --- /dev/null +++ b/pyt/vulnerability_definitions/test_triggers.json @@ -0,0 +1,16 @@ +{ + "sources": [ + "input" + ], + "sinks": { + "eval": [ + "sanitise" + ], + "horse": [ + "japan", + "host", + "kost" + ], + "valmue": [] + } +} diff --git a/pyt/vulnerability_definitions/test_triggers.pyt b/pyt/vulnerability_definitions/test_triggers.pyt deleted file mode 100644 index cfa83a37..00000000 --- a/pyt/vulnerability_definitions/test_triggers.pyt +++ /dev/null @@ -1,7 +0,0 @@ -sources: -input - -sinks: -eval -> sanitise -horse -> japan, host, kost -valmue diff --git a/tests/vulnerabilities_test.py b/tests/vulnerabilities_test.py index 65538734..003c2192 100644 --- a/tests/vulnerabilities_test.py +++ b/tests/vulnerabilities_test.py @@ -32,12 +32,12 @@ def get_lattice_elements(self, cfg_nodes): return cfg_nodes def test_parse(self): - definitions = vulnerabilities.parse( + definitions = trigger_definitions_parser.parse( trigger_word_file=os.path.join( os.getcwd(), 'pyt', 'vulnerability_definitions', - 'test_triggers.pyt' + 'test_triggers.json' ) ) @@ -525,7 +525,7 @@ def run_analysis(self, path): trigger_word_file = os.path.join( 'pyt', 'vulnerability_definitions', - 'django_trigger_words.pyt' + 'django_trigger_words.json' ) return vulnerabilities.find_vulnerabilities(