python-taint
Advanced tools
| """This formatter outputs the issues as color-coded text.""" | ||
| from ..vulnerabilities.vulnerability_helper import SanitisedVulnerability, UnknownVulnerability | ||
| RESET = '\033[0m' | ||
| BOLD = '\033[1m' | ||
| UNDERLINE = '\033[4m' | ||
| DANGER = '\033[31m' | ||
| GOOD = '\033[32m' | ||
| HIGHLIGHT = '\033[45;1m' | ||
| RED_ON_WHITE = '\033[31m\033[107m' | ||
| def color(string, color_string): | ||
| return color_string + str(string) + RESET | ||
| def report( | ||
| vulnerabilities, | ||
| fileobj, | ||
| print_sanitised, | ||
| ): | ||
| """ | ||
| Prints issues in color-coded text format. | ||
| Args: | ||
| vulnerabilities: list of vulnerabilities to report | ||
| fileobj: The output file object, which may be sys.stdout | ||
| """ | ||
| n_vulnerabilities = len(vulnerabilities) | ||
| unsanitised_vulnerabilities = [v for v in vulnerabilities if not isinstance(v, SanitisedVulnerability)] | ||
| n_unsanitised = len(unsanitised_vulnerabilities) | ||
| n_sanitised = n_vulnerabilities - n_unsanitised | ||
| heading = "{} vulnerabilit{} found{}.\n".format( | ||
| 'No' if n_unsanitised == 0 else n_unsanitised, | ||
| 'y' if n_unsanitised == 1 else 'ies', | ||
| " (plus {} sanitised)".format(n_sanitised) if n_sanitised else "", | ||
| ) | ||
| vulnerabilities_to_print = vulnerabilities if print_sanitised else unsanitised_vulnerabilities | ||
| with fileobj: | ||
| for i, vulnerability in enumerate(vulnerabilities_to_print, start=1): | ||
| fileobj.write(vulnerability_to_str(i, vulnerability)) | ||
| if n_unsanitised == 0: | ||
| fileobj.write(color(heading, GOOD)) | ||
| else: | ||
| fileobj.write(color(heading, DANGER)) | ||
| def vulnerability_to_str(i, vulnerability): | ||
| lines = [] | ||
| lines.append(color('Vulnerability {}'.format(i), UNDERLINE)) | ||
| lines.append('File: {}'.format(color(vulnerability.source.path, BOLD))) | ||
| lines.append( | ||
| 'User input at line {}, source "{}":'.format( | ||
| vulnerability.source.line_number, | ||
| color(vulnerability.source_trigger_word, HIGHLIGHT), | ||
| ) | ||
| ) | ||
| lines.append('\t{}'.format(color(vulnerability.source.label, RED_ON_WHITE))) | ||
| if vulnerability.reassignment_nodes: | ||
| previous_path = None | ||
| lines.append('Reassigned in:') | ||
| for node in vulnerability.reassignment_nodes: | ||
| if node.path != previous_path: | ||
| lines.append('\tFile: {}'.format(node.path)) | ||
| previous_path = node.path | ||
| label = node.label | ||
| if ( | ||
| isinstance(vulnerability, SanitisedVulnerability) and | ||
| node.label == vulnerability.sanitiser.label | ||
| ): | ||
| label = color(label, GOOD) | ||
| lines.append( | ||
| '\t Line {}:\t{}'.format( | ||
| node.line_number, | ||
| label, | ||
| ) | ||
| ) | ||
| if vulnerability.source.path != vulnerability.sink.path: | ||
| lines.append('File: {}'.format(color(vulnerability.sink.path, BOLD))) | ||
| lines.append( | ||
| 'Reaches line {}, sink "{}"'.format( | ||
| vulnerability.sink.line_number, | ||
| color(vulnerability.sink_trigger_word, HIGHLIGHT), | ||
| ) | ||
| ) | ||
| lines.append('\t{}'.format( | ||
| color(vulnerability.sink.label, RED_ON_WHITE) | ||
| )) | ||
| if isinstance(vulnerability, SanitisedVulnerability): | ||
| lines.append( | ||
| 'This vulnerability is {}{} by {}'.format( | ||
| color('potentially ', BOLD) if not vulnerability.confident else '', | ||
| color('sanitised', GOOD), | ||
| color(vulnerability.sanitiser.label, BOLD), | ||
| ) | ||
| ) | ||
| elif isinstance(vulnerability, UnknownVulnerability): | ||
| lines.append( | ||
| 'This vulnerability is unknown due to "{}"'.format( | ||
| color(vulnerability.unknown_assignment.label, BOLD), | ||
| ) | ||
| ) | ||
| return '\n'.join(lines) + '\n\n' |
+2
-3
| Metadata-Version: 1.1 | ||
| Name: python-taint | ||
| Version: 0.39 | ||
| Version: 0.40 | ||
| Summary: Find security vulnerabilities in Python web applications using static analysis. | ||
@@ -9,4 +9,3 @@ Home-page: https://github.com/python-security/pyt | ||
| License: GPLv2 | ||
| Download-URL: https://github.com/python-security/pyt/archive/0.39.tar.gz | ||
| Description-Content-Type: UNKNOWN | ||
| Download-URL: https://github.com/python-security/pyt/archive/0.40.tar.gz | ||
| Description: Check out PyT on `GitHub <https://github.com/python-security/pyt>`_! | ||
@@ -13,0 +12,0 @@ Keywords: security,vulnerability,web,flask,django,static-analysis,program-analysis |
+23
-16
| """The comand line module of PyT.""" | ||
| import logging | ||
| import os | ||
@@ -15,11 +16,6 @@ import sys | ||
| ) | ||
| from .formatters import ( | ||
| json, | ||
| text | ||
| ) | ||
| from .usage import parse_args | ||
| from .vulnerabilities import ( | ||
| find_vulnerabilities, | ||
| get_vulnerabilities_not_in_baseline, | ||
| UImode | ||
| get_vulnerabilities_not_in_baseline | ||
| ) | ||
@@ -35,3 +31,5 @@ from .vulnerabilities.vulnerability_helper import SanitisedVulnerability | ||
| log = logging.getLogger(__name__) | ||
| def discover_files(targets, excluded_files, recursive=False): | ||
@@ -47,2 +45,3 @@ included_files = list() | ||
| included_files.append(fullpath) | ||
| log.debug('Discovered file: %s', fullpath) | ||
| if not recursive: | ||
@@ -53,2 +52,3 @@ break | ||
| included_files.append(target) | ||
| log.debug('Discovered file: %s', target) | ||
| return included_files | ||
@@ -72,5 +72,9 @@ | ||
| ui_mode = UImode.TRIM | ||
| if args.interactive: | ||
| ui_mode = UImode.INTERACTIVE | ||
| logging_level = ( | ||
| logging.ERROR if not args.verbose else | ||
| logging.WARN if args.verbose == 1 else | ||
| logging.INFO if args.verbose == 2 else | ||
| logging.DEBUG | ||
| ) | ||
| logging.basicConfig(level=logging_level, format='[%(levelname)s] %(name)s: %(message)s') | ||
@@ -91,2 +95,3 @@ files = discover_files( | ||
| for path in sorted(files): | ||
| log.info("Processing %s", path) | ||
| if not args.ignore_nosec: | ||
@@ -129,8 +134,10 @@ nosec_lines[path] = retrieve_nosec_lines(path) | ||
| initialize_constraint_table(cfg_list) | ||
| log.info("Analysing") | ||
| analyse(cfg_list) | ||
| log.info("Finding vulnerabilities") | ||
| vulnerabilities = find_vulnerabilities( | ||
| cfg_list, | ||
| ui_mode, | ||
| args.blackbox_mapping_file, | ||
| args.trigger_word_file, | ||
| args.interactive, | ||
| nosec_lines | ||
@@ -145,9 +152,9 @@ ) | ||
| if args.json: | ||
| json.report(vulnerabilities, args.output_file) | ||
| else: | ||
| text.report(vulnerabilities, args.output_file) | ||
| args.formatter.report(vulnerabilities, args.output_file, not args.only_unsanitised) | ||
| has_unsanitized_vulnerabilities = any(not isinstance(v, SanitisedVulnerability) for v in vulnerabilities) | ||
| if has_unsanitized_vulnerabilities: | ||
| has_unsanitised_vulnerabilities = any( | ||
| not isinstance(v, SanitisedVulnerability) | ||
| for v in vulnerabilities | ||
| ) | ||
| if has_unsanitised_vulnerabilities: | ||
| sys.exit(1) | ||
@@ -154,0 +161,0 @@ |
| import ast | ||
| import logging | ||
@@ -33,3 +34,5 @@ from .alias_helper import handle_aliases_in_calls | ||
| log = logging.getLogger(__name__) | ||
| class ExprVisitor(StmtVisitor): | ||
@@ -56,2 +59,3 @@ def __init__( | ||
| self.function_return_stack = list() | ||
| self.function_definition_stack = list() # used to avoid recursion | ||
| self.module_definitions_stack = list() | ||
@@ -548,2 +552,3 @@ self.prev_nodes_to_avoid = list() | ||
| self.function_return_stack.pop() | ||
| self.function_definition_stack.pop() | ||
@@ -566,2 +571,5 @@ return self.nodes[-1] | ||
| if definition: | ||
| if definition in self.function_definition_stack: | ||
| log.debug("Recursion encountered in function %s", _id) | ||
| return self.add_blackbox_or_builtin_call(node, blackbox=True) | ||
| if isinstance(definition.node, ast.ClassDef): | ||
@@ -572,2 +580,3 @@ self.add_blackbox_or_builtin_call(node, blackbox=False) | ||
| self.function_return_stack.append(_id) | ||
| self.function_definition_stack.append(definition) | ||
| return self.process_function(node, definition) | ||
@@ -574,0 +583,0 @@ else: |
| import ast | ||
| import itertools | ||
| import logging | ||
| import os.path | ||
| from pkgutil import iter_modules | ||
@@ -55,3 +57,6 @@ from .alias_helper import ( | ||
| log = logging.getLogger(__name__) | ||
| uninspectable_modules = {module.name for module in iter_modules()} # Don't warn about failing to import these | ||
| class StmtVisitor(ast.NodeVisitor): | ||
@@ -433,5 +438,8 @@ def __init__(self, allow_local_directory_imports=True): | ||
| label.visit(node) | ||
| print('Assignment not properly handled.', | ||
| 'Could result in not finding a vulnerability.', | ||
| 'Assignment:', label.result) | ||
| log.warn( | ||
| 'Assignment not properly handled in %s. Could result in not finding a vulnerability.' | ||
| 'Assignment: %s', | ||
| getattr(self, 'filenames', ['?'])[0], | ||
| self.label.result, | ||
| ) | ||
| return self.append_node(AssignmentNode( | ||
@@ -670,2 +678,3 @@ label.result, | ||
| call_node.right_hand_side_variables = rhs_vars | ||
| # Used in get_sink_args | ||
| rhs_visitor = RHSVisitor() | ||
@@ -1027,2 +1036,6 @@ rhs_visitor.visit(node) | ||
| ) | ||
| for alias in node.names: | ||
| if alias.name not in uninspectable_modules: | ||
| log.warn("Cannot inspect module %s", alias.name) | ||
| uninspectable_modules.add(alias.name) # Don't repeatedly warn about this | ||
| return IgnoredNode() | ||
@@ -1067,2 +1080,5 @@ | ||
| ) | ||
| if node.module not in uninspectable_modules: | ||
| log.warn("Cannot inspect module %s", node.module) | ||
| uninspectable_modules.add(node.module) | ||
| return IgnoredNode() |
@@ -5,2 +5,3 @@ """This module contains helper function. | ||
| import ast | ||
| import logging | ||
| import os | ||
@@ -10,5 +11,5 @@ import subprocess | ||
| from .transformer import AsyncTransformer | ||
| from .transformer import PytTransformer | ||
| log = logging.getLogger(__name__) | ||
| BLACK_LISTED_CALL_NAMES = ['self'] | ||
@@ -21,7 +22,6 @@ recursive = False | ||
| try: | ||
| print('##### Trying to convert file to Python 3. #####') | ||
| log.warn('##### Trying to convert %s to Python 3. #####', path) | ||
| subprocess.call(['2to3', '-w', path]) | ||
| except subprocess.SubprocessError: | ||
| print('Check if 2to3 is installed. ' | ||
| 'https://docs.python.org/2/library/2to3.html') | ||
| log.exception('Check if 2to3 is installed. https://docs.python.org/2/library/2to3.html') | ||
| exit(1) | ||
@@ -41,3 +41,3 @@ | ||
| tree = ast.parse(f.read()) | ||
| return AsyncTransformer().visit(tree) | ||
| return PytTransformer().visit(tree) | ||
| except SyntaxError: # pragma: no cover | ||
@@ -44,0 +44,0 @@ global recursive |
| import ast | ||
| class AsyncTransformer(ast.NodeTransformer): | ||
| class AsyncTransformer(): | ||
| """Converts all async nodes into their synchronous counterparts.""" | ||
@@ -19,1 +19,51 @@ | ||
| return self.visit(ast.With(**node.__dict__)) | ||
| class ChainedFunctionTransformer(): | ||
| def visit_chain(self, node, depth=1): | ||
| if ( | ||
| isinstance(node.value, ast.Call) and | ||
| isinstance(node.value.func, ast.Attribute) and | ||
| isinstance(node.value.func.value, ast.Call) | ||
| ): | ||
| # Node is assignment or return with value like `b.c().d()` | ||
| call_node = node.value | ||
| # If we want to handle nested functions in future, depth needs fixing | ||
| temp_var_id = '__chain_tmp_{}'.format(depth) | ||
| # AST tree is from right to left, so d() is the outer Call and b.c() is the inner Call | ||
| unvisited_inner_call = ast.Assign( | ||
| targets=[ast.Name(id=temp_var_id, ctx=ast.Store())], | ||
| value=call_node.func.value, | ||
| ) | ||
| ast.copy_location(unvisited_inner_call, node) | ||
| inner_calls = self.visit_chain(unvisited_inner_call, depth + 1) | ||
| for inner_call_node in inner_calls: | ||
| ast.copy_location(inner_call_node, node) | ||
| outer_call = self.generic_visit(type(node)( | ||
| value=ast.Call( | ||
| func=ast.Attribute( | ||
| value=ast.Name(id=temp_var_id, ctx=ast.Load()), | ||
| attr=call_node.func.attr, | ||
| ctx=ast.Load(), | ||
| ), | ||
| args=call_node.args, | ||
| keywords=call_node.keywords, | ||
| ), | ||
| **{field: value for field, value in ast.iter_fields(node) if field != 'value'} # e.g. targets | ||
| )) | ||
| ast.copy_location(outer_call, node) | ||
| ast.copy_location(outer_call.value, node) | ||
| ast.copy_location(outer_call.value.func, node) | ||
| return [*inner_calls, outer_call] | ||
| else: | ||
| return [self.generic_visit(node)] | ||
| def visit_Assign(self, node): | ||
| return self.visit_chain(node) | ||
| def visit_Return(self, node): | ||
| return self.visit_chain(node) | ||
| class PytTransformer(AsyncTransformer, ChainedFunctionTransformer, ast.NodeTransformer): | ||
| pass |
| """This formatter outputs the issues in JSON.""" | ||
| import json | ||
| from datetime import datetime | ||
| from ..vulnerabilities.vulnerability_helper import SanitisedVulnerability | ||
| def report( | ||
| vulnerabilities, | ||
| fileobj | ||
| fileobj, | ||
| print_sanitised, | ||
| ): | ||
@@ -22,3 +24,6 @@ """ | ||
| 'generated_at': time_string, | ||
| 'vulnerabilities': [vuln.as_dict() for vuln in vulnerabilities] | ||
| 'vulnerabilities': [ | ||
| vuln.as_dict() for vuln in vulnerabilities | ||
| if print_sanitised or not isinstance(vuln, SanitisedVulnerability) | ||
| ] | ||
| } | ||
@@ -25,0 +30,0 @@ |
| """This formatter outputs the issues as plain text.""" | ||
| from ..vulnerabilities.vulnerability_helper import SanitisedVulnerability | ||
@@ -6,3 +7,4 @@ | ||
| vulnerabilities, | ||
| fileobj | ||
| fileobj, | ||
| print_sanitised, | ||
| ): | ||
@@ -15,13 +17,19 @@ """ | ||
| fileobj: The output file object, which may be sys.stdout | ||
| print_sanitised: Print just unsanitised vulnerabilities or sanitised vulnerabilities as well | ||
| """ | ||
| number_of_vulnerabilities = len(vulnerabilities) | ||
| n_vulnerabilities = len(vulnerabilities) | ||
| unsanitised_vulnerabilities = [v for v in vulnerabilities if not isinstance(v, SanitisedVulnerability)] | ||
| n_unsanitised = len(unsanitised_vulnerabilities) | ||
| n_sanitised = n_vulnerabilities - n_unsanitised | ||
| heading = "{} vulnerabilit{} found{}{}\n".format( | ||
| 'No' if n_unsanitised == 0 else n_unsanitised, | ||
| 'y' if n_unsanitised == 1 else 'ies', | ||
| " (plus {} sanitised)".format(n_sanitised) if n_sanitised else "", | ||
| ':' if n_vulnerabilities else '.', | ||
| ) | ||
| vulnerabilities_to_print = vulnerabilities if print_sanitised else unsanitised_vulnerabilities | ||
| with fileobj: | ||
| if number_of_vulnerabilities == 0: | ||
| fileobj.write('No vulnerabilities found.\n') | ||
| elif number_of_vulnerabilities == 1: | ||
| fileobj.write('%s vulnerability found:\n' % number_of_vulnerabilities) | ||
| else: | ||
| fileobj.write('%s vulnerabilities found:\n' % number_of_vulnerabilities) | ||
| fileobj.write(heading) | ||
| for i, vulnerability in enumerate(vulnerabilities, start=1): | ||
| for i, vulnerability in enumerate(vulnerabilities_to_print, start=1): | ||
| fileobj.write('Vulnerability {}:\n{}\n\n'.format(i, vulnerability)) |
+48
-38
@@ -5,3 +5,5 @@ import argparse | ||
| from .formatters import json, screen, text | ||
| default_blackbox_mapping_file = os.path.join( | ||
@@ -24,4 +26,5 @@ os.path.dirname(__file__), | ||
| required_group.add_argument( | ||
| 'targets', metavar='targets', type=str, nargs='+', | ||
| help='source file(s) or directory(s) to be tested' | ||
| 'targets', metavar='targets', nargs='+', | ||
| help='source file(s) or directory(s) to be scanned', | ||
| type=str | ||
| ) | ||
@@ -32,4 +35,8 @@ | ||
| optional_group = parser.add_argument_group('optional arguments') | ||
| optional_group.add_argument( | ||
| '-v', '--verbose', | ||
| action='count', | ||
| help='Increase logging verbosity. Can repeated e.g. -vvv', | ||
| ) | ||
| optional_group.add_argument( | ||
| '-a', '--adaptor', | ||
@@ -55,6 +62,6 @@ help='Choose a web framework adaptor: ' | ||
| optional_group.add_argument( | ||
| '-j', '--json', | ||
| help='Prints JSON instead of report.', | ||
| action='store_true', | ||
| default=False | ||
| '-t', '--trigger-word-file', | ||
| help='Input file with a list of sources and sinks', | ||
| type=str, | ||
| default=default_trigger_word_file | ||
| ) | ||
@@ -68,10 +75,10 @@ optional_group.add_argument( | ||
| optional_group.add_argument( | ||
| '-t', '--trigger-word-file', | ||
| help='Input file with a list of sources and sinks', | ||
| type=str, | ||
| default=default_trigger_word_file | ||
| '-i', '--interactive', | ||
| help='Will ask you about each blackbox function call in vulnerability chains.', | ||
| action='store_true', | ||
| default=False | ||
| ) | ||
| optional_group.add_argument( | ||
| '-o', '--output', | ||
| help='write report to filename', | ||
| help='Write report to filename', | ||
| dest='output_file', | ||
@@ -86,7 +93,9 @@ action='store', | ||
| action='store_true', | ||
| help='do not skip lines with # nosec comments' | ||
| help='Do not skip lines with # nosec comments' | ||
| ) | ||
| optional_group.add_argument( | ||
| '-r', '--recursive', dest='recursive', | ||
| action='store_true', help='find and process files in subdirectories' | ||
| '-r', '--recursive', | ||
| dest='recursive', | ||
| action='store_true', | ||
| help='Find and process files in subdirectories' | ||
| ) | ||
@@ -115,25 +124,26 @@ optional_group.add_argument( | ||
| ) | ||
| def _add_print_group(parser): | ||
| print_group = parser.add_argument_group('print arguments') | ||
| print_group.add_argument( | ||
| '-trim', '--trim-reassigned-in', | ||
| help='Trims the reassigned list to just the vulnerability chain.', | ||
| optional_group.add_argument( | ||
| '-u', '--only-unsanitised', | ||
| help="Don't print sanitised vulnerabilities.", | ||
| action='store_true', | ||
| default=True | ||
| default=False, | ||
| ) | ||
| print_group.add_argument( | ||
| '-i', '--interactive', | ||
| help='Will ask you about each blackbox function call in vulnerability chains.', | ||
| action='store_true', | ||
| default=False | ||
| parser.set_defaults(formatter=text) | ||
| formatter_group = optional_group.add_mutually_exclusive_group() | ||
| formatter_group.add_argument( | ||
| '-j', '--json', | ||
| help='Prints JSON instead of report.', | ||
| action='store_const', | ||
| const=json, | ||
| dest='formatter', | ||
| ) | ||
| formatter_group.add_argument( | ||
| '-s', '--screen', | ||
| help='Prints colorful report.', | ||
| action='store_const', | ||
| const=screen, | ||
| dest='formatter', | ||
| ) | ||
| def _check_required_and_mutually_exclusive_args(parser, args): | ||
| if args.targets is None: | ||
| parser.error('The targets argument is required') | ||
| def parse_args(args): | ||
@@ -143,12 +153,12 @@ if len(args) == 0: | ||
| parser = argparse.ArgumentParser(prog='python -m pyt') | ||
| # Hack to in order to list required args above optional | ||
| parser._action_groups.pop() | ||
| _add_required_group(parser) | ||
| _add_optional_group(parser) | ||
| _add_print_group(parser) | ||
| args = parser.parse_args(args) | ||
| _check_required_and_mutually_exclusive_args( | ||
| parser, | ||
| args | ||
| ) | ||
| if args.targets is None: | ||
| parser.error('The targets argument is required') | ||
| return args |
| from .vulnerabilities import find_vulnerabilities | ||
| from .vulnerability_helper import ( | ||
| get_vulnerabilities_not_in_baseline, | ||
| UImode | ||
| ) | ||
| from .vulnerability_helper import get_vulnerabilities_not_in_baseline | ||
@@ -10,4 +7,3 @@ | ||
| 'find_vulnerabilities', | ||
| 'get_vulnerabilities_not_in_baseline', | ||
| 'UImode' | ||
| 'get_vulnerabilities_not_in_baseline' | ||
| ] |
@@ -26,4 +26,3 @@ """Module for finding vulnerabilities based on a definitions file.""" | ||
| vuln_factory, | ||
| VulnerabilityType, | ||
| UImode | ||
| VulnerabilityType | ||
| ) | ||
@@ -310,3 +309,3 @@ | ||
| blackbox_assignments, | ||
| ui_mode, | ||
| interactive, | ||
| vuln_deets | ||
@@ -325,3 +324,3 @@ ): | ||
| blackbox_assignments(set[AssignmentNode]): set of blackbox assignments, includes the ReturnNode's of BBorBInode's. | ||
| ui_mode(UImode): determines if we interact with the user when we don't already have a blackbox mapping available. | ||
| interactive(bool): determines if we ask the user about blackbox functions not in the mapping file. | ||
| vuln_deets(dict): vulnerability details. | ||
@@ -343,3 +342,3 @@ | ||
| return VulnerabilityType.FALSE | ||
| elif ui_mode == UImode.INTERACTIVE: | ||
| elif interactive: | ||
| user_says = input( | ||
@@ -385,3 +384,3 @@ 'Is the return value of {} with tainted argument "{}" vulnerable? (Y/n)'.format( | ||
| cfg, | ||
| ui_mode, | ||
| interactive, | ||
| blackbox_mapping | ||
@@ -403,3 +402,3 @@ ): | ||
| cfg(CFG): .blackbox_assignments used in is_unknown, .nodes used in build_def_use_chain | ||
| ui_mode(UImode): determines if we interact with the user or trim the nodes in the output, if at all. | ||
| interactive(bool): determines if we ask the user about blackbox functions not in the mapping file. | ||
| blackbox_mapping(dict): A map of blackbox functions containing whether or not they propagate taint. | ||
@@ -430,4 +429,3 @@ | ||
| 'sink': sink.cfg_node, | ||
| 'sink_trigger_word': sink.trigger_word, | ||
| 'reassignment_nodes': source.secondary_nodes | ||
| 'sink_trigger_word': sink.trigger_word | ||
| } | ||
@@ -460,3 +458,3 @@ | ||
| cfg.blackbox_assignments, | ||
| ui_mode, | ||
| interactive, | ||
| vuln_deets | ||
@@ -467,4 +465,3 @@ ) | ||
| if ui_mode != UImode.NORMAL: | ||
| vuln_deets['reassignment_nodes'] = chain | ||
| vuln_deets['reassignment_nodes'] = chain | ||
@@ -480,5 +477,5 @@ return vuln_factory(vulnerability_type)(**vuln_deets) | ||
| lattice, | ||
| ui_mode, | ||
| blackbox_mapping, | ||
| vulnerabilities_list, | ||
| interactive, | ||
| nosec_lines | ||
@@ -492,6 +489,5 @@ ): | ||
| lattice(Lattice): the lattice we're analysing. | ||
| ui_mode(UImode): determines if we interact with the user or trim the nodes in the output, if at all. | ||
| blackbox_mapping(dict): A map of blackbox functions containing whether or not they propagate taint. | ||
| vulnerabilities_list(list): That we append to when we find vulnerabilities. | ||
| nosec_lines(dict): filenames mapped to their nosec lines | ||
| interactive(bool): determines if we ask the user about blackbox functions not in the mapping file. | ||
| """ | ||
@@ -513,3 +509,3 @@ triggers = identify_triggers( | ||
| cfg, | ||
| ui_mode, | ||
| interactive, | ||
| blackbox_mapping | ||
@@ -523,5 +519,5 @@ ) | ||
| cfg_list, | ||
| ui_mode, | ||
| blackbox_mapping_file, | ||
| sources_and_sinks_file, | ||
| interactive=False, | ||
| nosec_lines=defaultdict(set) | ||
@@ -533,7 +529,5 @@ ): | ||
| cfg_list(list[CFG]): the list of CFGs to scan. | ||
| ui_mode(UImode): determines if we interact with the user or trim the nodes in the output, if at all. | ||
| blackbox_mapping_file(str) | ||
| sources_and_sinks_file(str) | ||
| nosec_lines(dict): filenames mapped to their nosec lines | ||
| interactive(bool): determines if we ask the user about blackbox functions not in the mapping file. | ||
| Returns: | ||
@@ -552,9 +546,9 @@ A list of vulnerabilities. | ||
| Lattice(cfg.nodes), | ||
| ui_mode, | ||
| blackbox_mapping, | ||
| vulnerabilities, | ||
| interactive, | ||
| nosec_lines | ||
| ) | ||
| if ui_mode == UImode.INTERACTIVE: | ||
| if interactive: | ||
| with open(blackbox_mapping_file, 'w') as outfile: | ||
@@ -561,0 +555,0 @@ json.dump(blackbox_mapping, outfile, indent=4) |
@@ -6,3 +6,2 @@ """This module contains vulnerability types, Enums, nodes and helpers.""" | ||
| from collections import namedtuple | ||
| from itertools import takewhile | ||
@@ -19,8 +18,2 @@ from ..core.node_types import YieldNode | ||
| class UImode(Enum): | ||
| INTERACTIVE = 0 | ||
| NORMAL = 1 | ||
| TRIM = 2 | ||
| def vuln_factory(vulnerability_type): | ||
@@ -62,7 +55,3 @@ if vulnerability_type == VulnerabilityType.UNKNOWN: | ||
| # Remove the sink node and all nodes after the sink from the list of reassignments. | ||
| self.reassignment_nodes = list(takewhile( | ||
| lambda node: node is not sink, | ||
| reassignment_nodes | ||
| )) | ||
| self.reassignment_nodes = reassignment_nodes | ||
| self._remove_non_propagating_yields() | ||
@@ -69,0 +58,0 @@ |
| """A generic framework adaptor that leaves route criteria to the caller.""" | ||
| import ast | ||
| import logging | ||
@@ -13,3 +14,5 @@ from ..cfg import make_cfg | ||
| log = logging.getLogger(__name__) | ||
| class FrameworkAdaptor(): | ||
@@ -35,2 +38,3 @@ """An engine that uses the template pattern to find all | ||
| """Build a function cfg and return it, with all arguments tainted.""" | ||
| log.debug("Getting CFG for %s", definition.name) | ||
| func_cfg = make_cfg( | ||
@@ -37,0 +41,0 @@ definition.node, |
| Metadata-Version: 1.1 | ||
| Name: python-taint | ||
| Version: 0.39 | ||
| Version: 0.40 | ||
| Summary: Find security vulnerabilities in Python web applications using static analysis. | ||
@@ -9,4 +9,3 @@ Home-page: https://github.com/python-security/pyt | ||
| License: GPLv2 | ||
| Download-URL: https://github.com/python-security/pyt/archive/0.39.tar.gz | ||
| Description-Content-Type: UNKNOWN | ||
| Download-URL: https://github.com/python-security/pyt/archive/0.40.tar.gz | ||
| Description: Check out PyT on `GitHub <https://github.com/python-security/pyt>`_! | ||
@@ -13,0 +12,0 @@ Keywords: security,vulnerability,web,flask,django,static-analysis,program-analysis |
@@ -29,2 +29,3 @@ MANIFEST.in | ||
| pyt/formatters/json.py | ||
| pyt/formatters/screen.py | ||
| pyt/formatters/text.py | ||
@@ -31,0 +32,0 @@ pyt/helper_visitors/__init__.py |
+29
-26
@@ -89,49 +89,52 @@ .. image:: https://travis-ci.org/python-security/pyt.svg?branch=master | ||
| usage: python -m pyt [-h] [-a ADAPTOR] [-pr PROJECT_ROOT] | ||
| [-b BASELINE_JSON_FILE] [-j] [-m BLACKBOX_MAPPING_FILE] | ||
| [-t TRIGGER_WORD_FILE] [-o OUTPUT_FILE] [--ignore-nosec] | ||
| [-r] [-x EXCLUDED_PATHS] [-trim] [-i] | ||
| targets [targets ...] | ||
| [-b BASELINE_JSON_FILE] [-j] [-t TRIGGER_WORD_FILE] | ||
| [-m BLACKBOX_MAPPING_FILE] [-i] [-o OUTPUT_FILE] | ||
| [--ignore-nosec] [-r] [-x EXCLUDED_PATHS] | ||
| [--dont-prepend-root] [--no-local-imports] | ||
| targets [targets ...] | ||
| required arguments: | ||
| targets source file(s) or directory(s) to be tested | ||
| targets source file(s) or directory(s) to be scanned | ||
| important optional arguments: | ||
| -a ADAPTOR, --adaptor ADAPTOR | ||
| Choose a web framework adaptor: Flask(Default), | ||
| Django, Every or Pylons | ||
| Choose a web framework adaptor: Flask(Default), | ||
| Django, Every or Pylons | ||
| -t TRIGGER_WORD_FILE, --trigger-word-file TRIGGER_WORD_FILE | ||
| Input file with a list of sources and sinks | ||
| Input file with a list of sources and sinks | ||
| -m BLACKBOX_MAPPING_FILE, --blackbox-mapping-file BLACKBOX_MAPPING_FILE | ||
| Input blackbox mapping file | ||
| Input blackbox mapping file | ||
| optional arguments: | ||
| -pr PROJECT_ROOT, --project-root PROJECT_ROOT | ||
| Add project root, only important when the entry file | ||
| is not at the root of the project | ||
| Add project root, only important when the entry file | ||
| is not at the root of the project. | ||
| -b BASELINE_JSON_FILE, --baseline BASELINE_JSON_FILE | ||
| Path of a baseline report to compare against (only | ||
| JSON-formatted files are accepted) | ||
| Path of a baseline report to compare against (only | ||
| JSON-formatted files are accepted) | ||
| -j, --json Prints JSON instead of report | ||
| -j, --json Prints JSON instead of report. | ||
| -i, --interactive Will ask you about each blackbox function call in | ||
| vulnerability chains. | ||
| -o OUTPUT_FILE, --output OUTPUT_FILE | ||
| Write report to filename | ||
| Write report to filename | ||
| --ignore-nosec Do not skip lines with # nosec comments | ||
| --ignore-nosec Do not skip lines with # nosec comments | ||
| -r, --recursive Find and process files in subdirectories | ||
| -r, --recursive Find and process files in subdirectories | ||
| -x EXCLUDED_PATHS, --exclude EXCLUDED_PATHS | ||
| Separate files with commas | ||
| Separate files with commas | ||
| --dont-prepend-root In project root e.g. /app, imports are not prepended | ||
| with app.* | ||
| print arguments: | ||
| -trim, --trim-reassigned-in | ||
| Trims the reassigned list to just the vulnerability | ||
| chain. | ||
| -i, --interactive Will ask you about each blackbox function call in | ||
| vulnerability chains. | ||
| --no-local-imports If set, absolute imports must be relative to the | ||
| project root. If not set, modules in the same | ||
| directory can be imported just by their names. | ||
@@ -138,0 +141,0 @@ Usage from Source |
+2
-1
@@ -5,4 +5,5 @@ [metadata] | ||
| [egg_info] | ||
| tag_svn_revision = 0 | ||
| tag_date = 0 | ||
| tag_build = | ||
| tag_date = 0 | ||
+1
-1
@@ -5,3 +5,3 @@ from setuptools import find_packages | ||
| VERSION = '0.39' | ||
| VERSION = '0.40' | ||
@@ -8,0 +8,0 @@ |
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
176729
4.91%55
1.85%4179
4.47%