From afd5cee6a1fec441a4788d1fe1aac052e68c6007 Mon Sep 17 00:00:00 2001 From: Naglis Jonaitis Date: Fri, 9 Nov 2018 03:22:33 +0200 Subject: [PATCH 1/4] Add functions susceptible to AST stack overflow to calls blacklist --- README.rst | 1 + bandit/blacklists/calls.py | 35 +++++++++++++++++++++++++++++ examples/ast_overflow.py | 11 +++++++++ tests/functional/test_functional.py | 8 +++++++ 4 files changed, 55 insertions(+) create mode 100644 examples/ast_overflow.py diff --git a/README.rst b/README.rst index 777695ee4..10f9709ba 100644 --- a/README.rst +++ b/README.rst @@ -212,6 +212,7 @@ Usage:: B323 unverified_context B324 hashlib_new_insecure_functions B325 tempnam + B326 ast_overflow B401 import_telnetlib B402 import_ftplib B403 import_pickle diff --git a/bandit/blacklists/calls.py b/bandit/blacklists/calls.py index 85fb67894..71eaa01d1 100644 --- a/bandit/blacklists/calls.py +++ b/bandit/blacklists/calls.py @@ -318,6 +318,27 @@ | | | - os.tmpnam | | +------+---------------------+------------------------------------+-----------+ +B326: ast_overflow +------------------- + +It is possible to crash the Python interpreter by passing sufficiently +large/complex strings to ast.literal_eval(), ast.parse(), compile(), +dbm.dumb.open(), eval() or exec() due to stack depth limitations in Python’s +AST compiler. Ensure these functions are not used on untrusted data. + +For further information: + https://docs.python.org/3.7/library/ast.html#ast.parse + https://bugs.python.org/issue32758 + ++------+---------------------+------------------------------------+-----------+ +| ID | Name | Calls | Severity | ++======+=====================+====================================+===========+ +| B326 | ast_overflow | - ast.literal_eval | Medium | +| | | - ast.parse | | +| | | - compile | | +| | | - dbm.dumb.open | | ++------+---------------------+------------------------------------+-----------+ + """ from bandit.blacklists import utils @@ -574,4 +595,18 @@ def gen_blacklist(): 'attacks. Consider using tmpfile() instead.' )) + # omitted eval() and exec() as they are already covered by B307 and B102 + sets.append(utils.build_conf_dict( + 'ast_overflow', 'B326', + ['ast.literal_eval', + 'ast.parse', + 'compile', + 'dbm.dumb.open'], + 'It is possible to crash the Python interpreter by passing ' + 'sufficiently large/complex strings to ast.literal_eval(), ' + 'ast.parse(), compile(), dbm.dumb.open(), eval() or exec() due to ' + 'stack depth limitations in Python’s AST compiler. Ensure these ' + 'functions are not used on untrusted data.' + )) + return {'Call': sets} diff --git a/examples/ast_overflow.py b/examples/ast_overflow.py new file mode 100644 index 000000000..32f33e9d1 --- /dev/null +++ b/examples/ast_overflow.py @@ -0,0 +1,11 @@ +import ast +import dbm.dumb + + +ast.literal_eval('x = 2 + 2') + +ast.parse('x = 2 + 2') + +compile('2 + 2', '?', 'eval') + +dbm.dumb.open('test.db') diff --git a/tests/functional/test_functional.py b/tests/functional/test_functional.py index 0a7af9619..2d9cca00a 100644 --- a/tests/functional/test_functional.py +++ b/tests/functional/test_functional.py @@ -272,6 +272,14 @@ def test_tempnam(self): } self.check_example('tempnam.py', expect) + def test_ast_overflow(self): + '''Test for functions susceptible to AST stack overflow.''' + expect = { + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 4, 'HIGH': 0}, + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 4} + } + self.check_example('ast_overflow.py', expect) + def test_nonsense(self): '''Test that a syntactically invalid module is skipped.''' self.run_example('nonsense.py') From 527e647eccd7550df068ed8c817e86b96a7cad70 Mon Sep 17 00:00:00 2001 From: Naglis Jonaitis Date: Wed, 21 Nov 2018 23:12:28 +0200 Subject: [PATCH 2/4] Reduces severity of `ast_overflow` to low --- bandit/blacklists/calls.py | 5 +++-- tests/functional/test_functional.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/bandit/blacklists/calls.py b/bandit/blacklists/calls.py index 71eaa01d1..ee3ad3af7 100644 --- a/bandit/blacklists/calls.py +++ b/bandit/blacklists/calls.py @@ -333,7 +333,7 @@ +------+---------------------+------------------------------------+-----------+ | ID | Name | Calls | Severity | +======+=====================+====================================+===========+ -| B326 | ast_overflow | - ast.literal_eval | Medium | +| B326 | ast_overflow | - ast.literal_eval | Low | | | | - ast.parse | | | | | - compile | | | | | - dbm.dumb.open | | @@ -606,7 +606,8 @@ def gen_blacklist(): 'sufficiently large/complex strings to ast.literal_eval(), ' 'ast.parse(), compile(), dbm.dumb.open(), eval() or exec() due to ' 'stack depth limitations in Python’s AST compiler. Ensure these ' - 'functions are not used on untrusted data.' + 'functions are not used on untrusted data.', + 'LOW' )) return {'Call': sets} diff --git a/tests/functional/test_functional.py b/tests/functional/test_functional.py index 2d9cca00a..394a1790a 100644 --- a/tests/functional/test_functional.py +++ b/tests/functional/test_functional.py @@ -275,7 +275,7 @@ def test_tempnam(self): def test_ast_overflow(self): '''Test for functions susceptible to AST stack overflow.''' expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 4, 'HIGH': 0}, + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 4, 'MEDIUM': 0, 'HIGH': 0}, 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 4} } self.check_example('ast_overflow.py', expect) From c64829f6350e115d84c776ed577f2514b6d8e353 Mon Sep 17 00:00:00 2001 From: Naglis Jonaitis Date: Fri, 30 Nov 2018 01:05:30 +0200 Subject: [PATCH 3/4] Includes both eval() and exec() to the rule --- bandit/blacklists/calls.py | 6 ++- bandit/plugins/ast_overflow_exec.py | 45 +++++++++++++++++++ examples/ast_overflow-py2.py | 16 +++++++ .../{ast_overflow.py => ast_overflow-py3.py} | 4 ++ setup.cfg | 3 ++ tests/functional/test_functional.py | 29 +++++++++--- 6 files changed, 96 insertions(+), 7 deletions(-) create mode 100644 bandit/plugins/ast_overflow_exec.py create mode 100644 examples/ast_overflow-py2.py rename examples/{ast_overflow.py => ast_overflow-py3.py} (82%) diff --git a/bandit/blacklists/calls.py b/bandit/blacklists/calls.py index ee3ad3af7..8d13015c0 100644 --- a/bandit/blacklists/calls.py +++ b/bandit/blacklists/calls.py @@ -337,6 +337,8 @@ | | | - ast.parse | | | | | - compile | | | | | - dbm.dumb.open | | +| | | - eval | | +| | | - exec | | +------+---------------------+------------------------------------+-----------+ """ @@ -595,13 +597,13 @@ def gen_blacklist(): 'attacks. Consider using tmpfile() instead.' )) - # omitted eval() and exec() as they are already covered by B307 and B102 sets.append(utils.build_conf_dict( 'ast_overflow', 'B326', ['ast.literal_eval', 'ast.parse', 'compile', - 'dbm.dumb.open'], + 'dbm.dumb.open', + 'eval'], 'It is possible to crash the Python interpreter by passing ' 'sufficiently large/complex strings to ast.literal_eval(), ' 'ast.parse(), compile(), dbm.dumb.open(), eval() or exec() due to ' diff --git a/bandit/plugins/ast_overflow_exec.py b/bandit/plugins/ast_overflow_exec.py new file mode 100644 index 000000000..d31016d4a --- /dev/null +++ b/bandit/plugins/ast_overflow_exec.py @@ -0,0 +1,45 @@ +# -*- coding:utf-8 -*- +# +# Copyright 2018 Hewlett-Packard Development Company, L.P. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import six + +import bandit +from bandit.core import test_properties as test + + +def ast_overflow_exec_issue(): + return bandit.Issue( + severity=bandit.LOW, + confidence=bandit.HIGH, + text=('It is possible to crash the Python interpreter by passing ' + 'sufficiently large/complex strings to ast.literal_eval(), ' + 'ast.parse(), compile(), dbm.dumb.open(), eval() or exec() due ' + 'to stack depth limitations in Python’s AST compiler. Ensure ' + 'these functions are not used on untrusted data.') + ) + + +if six.PY2: + @test.checks('Exec') + @test.test_id('B326') + def ast_overflow(context): + return ast_overflow_exec_issue() +else: + @test.checks('Call') + @test.test_id('B326') + def ast_overflow(context): + if context.call_function_name_qual == 'exec': + return ast_overflow_exec_issue() diff --git a/examples/ast_overflow-py2.py b/examples/ast_overflow-py2.py new file mode 100644 index 000000000..e67821204 --- /dev/null +++ b/examples/ast_overflow-py2.py @@ -0,0 +1,16 @@ +import ast +import dbm.dumb + + +ast.literal_eval('x = 2 + 2') + +ast.parse('x = 2 + 2') + +compile('2 + 2', '?', 'eval') + +dbm.dumb.open('test.db') + +eval('2 + 2') + +exec('2 + 2') +exec '2 + 2' diff --git a/examples/ast_overflow.py b/examples/ast_overflow-py3.py similarity index 82% rename from examples/ast_overflow.py rename to examples/ast_overflow-py3.py index 32f33e9d1..feef65ea6 100644 --- a/examples/ast_overflow.py +++ b/examples/ast_overflow-py3.py @@ -9,3 +9,7 @@ compile('2 + 2', '?', 'eval') dbm.dumb.open('test.db') + +eval('2 + 2') + +exec('2 + 2') diff --git a/setup.cfg b/setup.cfg index 4dbd036f0..ab4d334ea 100644 --- a/setup.cfg +++ b/setup.cfg @@ -121,6 +121,9 @@ bandit.plugins = # bandit/plugins/ssh_no_host_key_verification.py ssh_no_host_key_verification = bandit.plugins.ssh_no_host_key_verification:ssh_no_host_key_verification + # bandit/plugins/ast_overflow_exec.py + ast_overflow = bandit.plugins.ast_overflow_exec:ast_overflow + [build_sphinx] all_files = 1 build-dir = doc/build diff --git a/tests/functional/test_functional.py b/tests/functional/test_functional.py index 394a1790a..48832e2d9 100644 --- a/tests/functional/test_functional.py +++ b/tests/functional/test_functional.py @@ -174,6 +174,10 @@ def test_exec(self): 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 1} } + self.b_mgr.b_ts = b_test_set.BanditTestSet( + config=self.b_mgr.b_conf, + profile={'exclude': ['B326']} + ) self.check_example(filename, expect) def test_hardcoded_passwords(self): @@ -274,11 +278,26 @@ def test_tempnam(self): def test_ast_overflow(self): '''Test for functions susceptible to AST stack overflow.''' - expect = { - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 4, 'MEDIUM': 0, 'HIGH': 0}, - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 4} - } - self.check_example('ast_overflow.py', expect) + filename = 'ast_overflow-{}.py' + if six.PY2: + filename = filename.format('py2') + expect = { + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 7, 'MEDIUM': 0, 'HIGH': 0}, + 'CONFIDENCE': { + 'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 7} + } + else: + filename = filename.format('py3') + expect = { + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 6, 'MEDIUM': 0, 'HIGH': 0}, + 'CONFIDENCE': { + 'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 6} + } + self.b_mgr.b_ts = b_test_set.BanditTestSet( + config=self.b_mgr.b_conf, + profile={'exclude': ['B102', 'B307']} + ) + self.check_example(filename, expect) def test_nonsense(self): '''Test that a syntactically invalid module is skipped.''' From 55d01611c27d422d709d0654b1eb2f7bcaa80a46 Mon Sep 17 00:00:00 2001 From: Naglis Jonaitis Date: Mon, 14 Jan 2019 02:13:40 +0200 Subject: [PATCH 4/4] Removes redundant copyright --- bandit/plugins/ast_overflow_exec.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/bandit/plugins/ast_overflow_exec.py b/bandit/plugins/ast_overflow_exec.py index d31016d4a..b534bbe9f 100644 --- a/bandit/plugins/ast_overflow_exec.py +++ b/bandit/plugins/ast_overflow_exec.py @@ -1,7 +1,5 @@ # -*- coding:utf-8 -*- # -# Copyright 2018 Hewlett-Packard Development Company, L.P. -# # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at