python-taint
Advanced tools
+3
-2
| Metadata-Version: 1.1 | ||
| Name: python-taint | ||
| Version: 0.38 | ||
| Version: 0.39 | ||
| Summary: Find security vulnerabilities in Python web applications using static analysis. | ||
@@ -9,3 +9,4 @@ Home-page: https://github.com/python-security/pyt | ||
| License: GPLv2 | ||
| Download-URL: https://github.com/python-security/pyt/archive/0.38.tar.gz | ||
| Download-URL: https://github.com/python-security/pyt/archive/0.39.tar.gz | ||
| Description-Content-Type: UNKNOWN | ||
| Description: Check out PyT on `GitHub <https://github.com/python-security/pyt>`_! | ||
@@ -12,0 +13,0 @@ Keywords: security,vulnerability,web,flask,django,static-analysis,program-analysis |
+10
-8
@@ -68,7 +68,5 @@ """The comand line module of PyT.""" | ||
| ui_mode = UImode.NORMAL | ||
| ui_mode = UImode.TRIM | ||
| if args.interactive: | ||
| ui_mode = UImode.INTERACTIVE | ||
| elif args.trim_reassigned_in: | ||
| ui_mode = UImode.TRIM | ||
@@ -83,11 +81,15 @@ files = discover_files( | ||
| for path in files: | ||
| if args.project_root: | ||
| directory = os.path.normpath(args.project_root) | ||
| project_modules = get_modules(directory, prepend_module_root=args.prepend_module_root) | ||
| cfg_list = list() | ||
| for path in sorted(files): | ||
| if not args.ignore_nosec: | ||
| nosec_lines[path] = retrieve_nosec_lines(path) | ||
| if args.project_root: | ||
| directory = os.path.normpath(args.project_root) | ||
| else: | ||
| if not args.project_root: | ||
| directory = os.path.dirname(path) | ||
| project_modules = get_modules(directory, prepend_module_root=args.prepend_module_root) | ||
| project_modules = get_modules(directory, prepend_module_root=args.prepend_module_root) | ||
| local_modules = get_directory_modules(directory) | ||
@@ -94,0 +96,0 @@ tree = generate_ast(path) |
+32
-27
@@ -14,2 +14,3 @@ import ast | ||
| generate_ast, | ||
| get_call_names, | ||
| get_call_names_as_string | ||
@@ -330,7 +331,7 @@ ) | ||
| def assign_tuple_target(self, node, right_hand_side_variables): | ||
| def assign_tuple_target(self, target_nodes, value_nodes, right_hand_side_variables): | ||
| new_assignment_nodes = [] | ||
| remaining_variables = list(right_hand_side_variables) | ||
| remaining_targets = list(node.targets[0].elts) | ||
| remaining_values = list(node.value.elts) # May contain duplicates | ||
| remaining_targets = list(target_nodes) | ||
| remaining_values = list(value_nodes) # May contain duplicates | ||
@@ -344,3 +345,3 @@ def visit(target, value): | ||
| new_ast_node = ast.Assign(target, value) | ||
| ast.copy_location(new_ast_node, node) | ||
| ast.copy_location(new_ast_node, target) | ||
| new_assignment_nodes.append(self.assignment_call_node(label.result, new_ast_node)) | ||
@@ -355,3 +356,3 @@ else: | ||
| rhs_visitor.result, | ||
| line_number=node.lineno, | ||
| line_number=target.lineno, | ||
| path=self.filenames[-1] | ||
@@ -365,3 +366,3 @@ ))) | ||
| # Pair targets and values until a Starred node is reached | ||
| for target, value in zip(node.targets[0].elts, node.value.elts): | ||
| for target, value in zip(target_nodes, value_nodes): | ||
| if isinstance(target, ast.Starred) or isinstance(value, ast.Starred): | ||
@@ -388,3 +389,3 @@ break | ||
| remaining_variables, | ||
| line_number=node.lineno, | ||
| line_number=target.lineno, | ||
| path=self.filenames[-1] | ||
@@ -422,3 +423,3 @@ ))) | ||
| if isinstance(node.value, (ast.Tuple, ast.List)): | ||
| return self.assign_tuple_target(node, rhs_visitor.result) | ||
| return self.assign_tuple_target(node.targets[0].elts, node.value.elts, rhs_visitor.result) | ||
| elif isinstance(node.value, ast.Call): | ||
@@ -431,2 +432,6 @@ call = None | ||
| return call | ||
| elif isinstance(node.value, ast.Name): # Treat `x, y = z` like `x, y = (*z,)` | ||
| value_node = ast.Starred(node.value, ast.Load()) | ||
| ast.copy_location(value_node, node) | ||
| return self.assign_tuple_target(node.targets[0].elts, [value_node], rhs_visitor.result) | ||
| else: | ||
@@ -479,10 +484,2 @@ label = LabelVisitor() | ||
| if isinstance(call, BBorBInode): | ||
| # Necessary to know e.g. | ||
| # `image_name = image_name.replace('..', '')` | ||
| # is a reassignment. | ||
| vars_visitor = VarsVisitor() | ||
| vars_visitor.visit(ast_node.value) | ||
| call.right_hand_side_variables.extend(vars_visitor.result) | ||
| call_assignment = AssignmentCallNode( | ||
@@ -580,3 +577,3 @@ left_hand_label + ' = ' + call_label, | ||
| def add_blackbox_or_builtin_call(self, node, blackbox): | ||
| def add_blackbox_or_builtin_call(self, node, blackbox): # noqa: C901 | ||
| """Processes a blackbox or builtin function when it is called. | ||
@@ -606,10 +603,10 @@ Nothing gets assigned to ret_func_foo in the builtin/blackbox case. | ||
| call_label = LabelVisitor() | ||
| call_label.visit(node) | ||
| call_label_visitor = LabelVisitor() | ||
| call_label_visitor.visit(node) | ||
| index = call_label.result.find('(') | ||
| call_function_label = call_label_visitor.result[:call_label_visitor.result.find('(')] | ||
| # Create e.g. ~call_1 = ret_func_foo | ||
| LHS = CALL_IDENTIFIER + 'call_' + str(saved_function_call_index) | ||
| RHS = 'ret_' + call_label.result[:index] + '(' | ||
| RHS = 'ret_' + call_function_label + '(' | ||
@@ -623,3 +620,3 @@ call_node = BBorBInode( | ||
| path=self.filenames[-1], | ||
| func_name=call_label.result[:index] | ||
| func_name=call_function_label | ||
| ) | ||
@@ -630,3 +627,4 @@ visual_args = list() | ||
| for arg in itertools.chain(node.args, node.keywords): | ||
| for arg_node in itertools.chain(node.args, node.keywords): | ||
| arg = arg_node.value if isinstance(arg_node, ast.keyword) else arg_node | ||
| if isinstance(arg, ast.Call): | ||
@@ -650,11 +648,14 @@ return_value_of_nested_call = self.visit(arg) | ||
| visual_args.append(return_value_of_nested_call.left_hand_side) | ||
| if isinstance(arg_node, ast.keyword) and arg_node.arg is not None: | ||
| visual_args.append(arg_node.arg + '=' + return_value_of_nested_call.left_hand_side) | ||
| else: | ||
| visual_args.append(return_value_of_nested_call.left_hand_side) | ||
| rhs_vars.append(return_value_of_nested_call.left_hand_side) | ||
| else: | ||
| label = LabelVisitor() | ||
| label.visit(arg) | ||
| label.visit(arg_node) | ||
| visual_args.append(label.result) | ||
| vv = VarsVisitor() | ||
| vv.visit(arg) | ||
| vv.visit(arg_node) | ||
| rhs_vars.extend(vv.result) | ||
@@ -666,2 +667,7 @@ if last_return_value_of_nested_call: | ||
| call_names = list(get_call_names(node.func)) | ||
| if len(call_names) > 1: | ||
| # taint is a RHS variable (self) of taint.lower() | ||
| rhs_vars.append(call_names[0]) | ||
| if len(visual_args) > 0: | ||
@@ -677,3 +683,2 @@ for arg in visual_args: | ||
| call_node.right_hand_side_variables = rhs_vars | ||
| # Used in get_sink_args, not using right_hand_side_variables because it is extended in assignment_call_node | ||
| rhs_visitor = RHSVisitor() | ||
@@ -680,0 +685,0 @@ rhs_visitor.visit(node) |
@@ -87,3 +87,4 @@ import ast | ||
| self.visit(node.func) | ||
| for arg in itertools.chain(node.args, node.keywords): | ||
| for arg_node in itertools.chain(node.args, node.keywords): | ||
| arg = arg_node.value if isinstance(arg_node, ast.keyword) else arg_node | ||
| if isinstance(arg, ast.Call): | ||
@@ -99,8 +100,28 @@ if isinstance(arg.func, ast.Name): | ||
| self.result.append('ret_' + arg.func.attr) | ||
| elif isinstance(arg.func, ast.Call): | ||
| self.visit_curried_call_inside_call_args(arg) | ||
| else: | ||
| # Deal with it when we have code that triggers it. | ||
| raise | ||
| raise Exception('Cannot visit vars of ' + ast.dump(arg)) | ||
| else: | ||
| self.visit(arg) | ||
| def visit_curried_call_inside_call_args(self, inner_call): | ||
| # Curried functions aren't supported really, but we now at least have a defined behaviour. | ||
| # In f(g(a)(b)(c)), inner_call is the Call node with argument c | ||
| # Try to get the name of curried function g | ||
| curried_func = inner_call.func.func | ||
| while isinstance(curried_func, ast.Call): | ||
| curried_func = curried_func.func | ||
| if isinstance(curried_func, ast.Name): | ||
| self.result.append('ret_' + curried_func.id) | ||
| elif isinstance(curried_func, ast.Attribute): | ||
| self.result.append('ret_' + curried_func.attr) | ||
| # Visit all arguments except a (ignore the curried function g) | ||
| not_curried = inner_call | ||
| while not_curried.func is not curried_func: | ||
| for arg in itertools.chain(not_curried.args, not_curried.keywords): | ||
| self.visit(arg.value if isinstance(arg, ast.keyword) else arg) | ||
| not_curried = not_curried.func | ||
| def visit_Attribute(self, node): | ||
@@ -107,0 +128,0 @@ if not isinstance(node.value, ast.Name): |
@@ -6,2 +6,3 @@ """This module contains vulnerability types, Enums, nodes and helpers.""" | ||
| from collections import namedtuple | ||
| from itertools import takewhile | ||
@@ -60,12 +61,9 @@ from ..core.node_types import YieldNode | ||
| self.reassignment_nodes = reassignment_nodes | ||
| self._remove_sink_from_secondary_nodes() | ||
| # 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._remove_non_propagating_yields() | ||
| def _remove_sink_from_secondary_nodes(self): | ||
| try: | ||
| self.reassignment_nodes.remove(self.sink) | ||
| except ValueError: # pragma: no cover | ||
| pass | ||
| def _remove_non_propagating_yields(self): | ||
@@ -72,0 +70,0 @@ """Remove yield with no variables e.g. `yield 123` and plain `yield` from vulnerability.""" |
| Metadata-Version: 1.1 | ||
| Name: python-taint | ||
| Version: 0.38 | ||
| Version: 0.39 | ||
| Summary: Find security vulnerabilities in Python web applications using static analysis. | ||
@@ -9,3 +9,4 @@ Home-page: https://github.com/python-security/pyt | ||
| License: GPLv2 | ||
| Download-URL: https://github.com/python-security/pyt/archive/0.38.tar.gz | ||
| Download-URL: https://github.com/python-security/pyt/archive/0.39.tar.gz | ||
| Description-Content-Type: UNKNOWN | ||
| Description: Check out PyT on `GitHub <https://github.com/python-security/pyt>`_! | ||
@@ -12,0 +13,0 @@ Keywords: security,vulnerability,web,flask,django,static-analysis,program-analysis |
+46
-13
@@ -54,9 +54,31 @@ .. image:: https://travis-ci.org/python-security/pyt.svg?branch=master | ||
| How It Works | ||
| How it Works | ||
| ============ | ||
| Soon you will find a README.rst in every directory in the pyt folder, `start here`_. | ||
| Soon you will find a `README.rst`_ in every directory in the ``pyt/`` folder, `start here`_. | ||
| .. _README.rst: https://github.com/python-security/pyt/tree/master/pyt | ||
| .. _start here: https://github.com/python-security/pyt/tree/master/pyt | ||
| How to Use | ||
| ============ | ||
| 1. Choose a web framework | ||
| `The -a option determines which functions will have their arguments tainted`_, by default it is Flask. | ||
| 2. (optional) Customize source and sink information | ||
| Use the ``-t`` option to specify sources and sinks, by default `this file is used`_. | ||
| 3. (optional) Customize which library functions propagate taint | ||
| For functions from builtins or libraries, e.g. ``url_for`` or ``os.path.join``, use the ``-m`` option to specify whether or not they return tainted values given tainted inputs, by `default this file is used`_. | ||
| .. _The -a option determines which functions will have their arguments tainted: https://github.com/python-security/pyt/tree/master/pyt/web_frameworks#web-frameworks | ||
| .. _this file is used: https://github.com/python-security/pyt/blob/master/pyt/vulnerability_definitions/all_trigger_words.pyt | ||
| .. _default this file is used: https://github.com/python-security/pyt/blob/master/pyt/vulnerability_definitions/blackbox_mapping.json | ||
| Usage | ||
@@ -76,24 +98,35 @@ ===== | ||
| optional arguments: | ||
| important optional arguments: | ||
| -a ADAPTOR, --adaptor ADAPTOR | ||
| 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 | ||
| -m BLACKBOX_MAPPING_FILE, --blackbox-mapping-file 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. | ||
| 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) | ||
| -j, --json Prints JSON instead of report. | ||
| -m BLACKBOX_MAPPING_FILE, --blackbox-mapping-file BLACKBOX_MAPPING_FILE | ||
| Input blackbox mapping file. | ||
| -t TRIGGER_WORD_FILE, --trigger-word-file TRIGGER_WORD_FILE | ||
| Input file with a list of sources and sinks | ||
| -j, --json Prints JSON instead of report | ||
| -o OUTPUT_FILE, --output OUTPUT_FILE | ||
| write report to filename | ||
| --ignore-nosec do not skip lines with # nosec comments | ||
| -r, --recursive find and process files in subdirectories | ||
| Write report to filename | ||
| --ignore-nosec Do not skip lines with # nosec comments | ||
| -r, --recursive Find and process files in subdirectories | ||
| -x EXCLUDED_PATHS, --exclude EXCLUDED_PATHS | ||
| Separate files with commas | ||
| print arguments: | ||
@@ -103,3 +136,3 @@ -trim, --trim-reassigned-in | ||
| chain. | ||
| -i, --interactive Will ask you about each blackbox function call in | ||
| -i, --interactive Will ask you about each blackbox function call in | ||
| vulnerability chains. | ||
@@ -106,0 +139,0 @@ |
+1
-2
@@ -5,5 +5,4 @@ [metadata] | ||
| [egg_info] | ||
| tag_svn_revision = 0 | ||
| tag_build = | ||
| tag_date = 0 | ||
| tag_build = | ||
+1
-1
@@ -5,3 +5,3 @@ from setuptools import find_packages | ||
| VERSION = '0.38' | ||
| VERSION = '0.39' | ||
@@ -8,0 +8,0 @@ |
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
168450
1.75%4000
0.58%