| |
@@ -1,1998 +0,0 @@
|
| |
- From 96632b53eb20ab6e572c2f147c114a1f626df738 Mon Sep 17 00:00:00 2001
|
| |
- From: Philip Jones <philip.graham.jones@gmail.com>
|
| |
- Date: Fri, 30 Dec 2016 19:21:03 +0000
|
| |
- Subject: [PATCH 01/43] Alter SQL Injection plugin SQL check
|
| |
-
|
| |
- The previous version assumed the SQL query would start with `select`,
|
| |
- `insert into`, `update` or `delete from` which rules out queries that
|
| |
- are not so simple, for example queries using `with` such as:
|
| |
-
|
| |
- WITH cte AS (query)
|
| |
- SELECT something FROM cte;
|
| |
-
|
| |
- This version losens the criteria and considers any string with simple
|
| |
- SQL grammar (e.g. `select` followed by `from` anywhere within) as SQL.
|
| |
-
|
| |
- Change-Id: I4c95842474e71aed61abc4bc878f3565a907f7c7
|
| |
- ---
|
| |
- bandit/plugins/injection_sql.py | 15 ++++++++++-----
|
| |
- examples/sql_statements.py | 2 ++
|
| |
- tests/functional/test_functional.py | 4 ++--
|
| |
- 3 files changed, 14 insertions(+), 7 deletions(-)
|
| |
-
|
| |
- diff --git a/bandit/plugins/injection_sql.py b/bandit/plugins/injection_sql.py
|
| |
- index 89b4e68..e284e4c 100644
|
| |
- --- a/bandit/plugins/injection_sql.py
|
| |
- +++ b/bandit/plugins/injection_sql.py
|
| |
- @@ -60,18 +60,23 @@ If so, a MEDIUM issue is reported. For example:
|
| |
- """
|
| |
-
|
| |
- import ast
|
| |
- +import re
|
| |
-
|
| |
- import bandit
|
| |
- from bandit.core import test_properties as test
|
| |
- from bandit.core import utils
|
| |
-
|
| |
- +SIMPLE_SQL_RE = re.compile(
|
| |
- + r'(select\s.*from\s|'
|
| |
- + r'delete\s+from\s|'
|
| |
- + r'insert\s+into\s.*values\s|'
|
| |
- + r'update\s.*set\s)',
|
| |
- + re.IGNORECASE | re.DOTALL,
|
| |
- +)
|
| |
- +
|
| |
-
|
| |
- def _check_string(data):
|
| |
- - val = data.lower()
|
| |
- - return ((val.startswith('select ') and ' from ' in val) or
|
| |
- - val.startswith('insert into') or
|
| |
- - (val.startswith('update ') and ' set ' in val) or
|
| |
- - val.startswith('delete from '))
|
| |
- + return SIMPLE_SQL_RE.search(data) is not None
|
| |
-
|
| |
-
|
| |
- def _evaluate_ast(node):
|
| |
- diff --git a/examples/sql_statements.py b/examples/sql_statements.py
|
| |
- index 66158df..51abcff 100644
|
| |
- --- a/examples/sql_statements.py
|
| |
- +++ b/examples/sql_statements.py
|
| |
- @@ -5,6 +5,8 @@ query = "SELECT * FROM foo WHERE id = '%s'" % identifier
|
| |
- query = "INSERT INTO foo VALUES ('a', 'b', '%s')" % value
|
| |
- query = "DELETE FROM foo WHERE id = '%s'" % identifier
|
| |
- query = "UPDATE foo SET value = 'b' WHERE id = '%s'" % identifier
|
| |
- +query = """WITH cte AS (SELECT x FROM foo)
|
| |
- +SELECT x FROM cte WHERE x = '%s'""" % identifier
|
| |
-
|
| |
- # bad
|
| |
- cur.execute("SELECT * FROM foo WHERE id = '%s'" % identifier)
|
| |
- diff --git a/tests/functional/test_functional.py b/tests/functional/test_functional.py
|
| |
- index 5578c6a..6d22ab6 100644
|
| |
- --- a/tests/functional/test_functional.py
|
| |
- +++ b/tests/functional/test_functional.py
|
| |
- @@ -292,8 +292,8 @@ class FunctionalTests(testtools.TestCase):
|
| |
- def test_sql_statements(self):
|
| |
- '''Test for SQL injection through string building.'''
|
| |
- expect = {
|
| |
- - 'SEVERITY': {'MEDIUM': 11},
|
| |
- - 'CONFIDENCE': {'LOW': 6, 'MEDIUM': 5}}
|
| |
- + 'SEVERITY': {'MEDIUM': 12},
|
| |
- + 'CONFIDENCE': {'LOW': 7, 'MEDIUM': 5}}
|
| |
- self.check_example('sql_statements.py', expect)
|
| |
-
|
| |
- def test_ssl_insecure_version(self):
|
| |
- --
|
| |
- 2.13.6
|
| |
-
|
| |
- From d4e213445aa4e5860936faf50f570fe00bdd0a44 Mon Sep 17 00:00:00 2001
|
| |
- From: Eric Brown <browne@vmware.com>
|
| |
- Date: Thu, 12 Jan 2017 23:53:24 -0800
|
| |
- Subject: [PATCH 02/43] Add Cryptodome to blacklist and weak ciphers/hash
|
| |
-
|
| |
- As stated in the bug, the PyCryptodomex package reintroduces
|
| |
- PyCrypto, but with a different namespace. Therefore Bandit should
|
| |
- also include Cryptodome in its checks.
|
| |
-
|
| |
- Change-Id: I6a02f97747420cedfb4523917ea0083ed5792d7a
|
| |
- Closes-Bug: #1655975
|
| |
- ---
|
| |
- bandit/blacklists/calls.py | 16 +++++++++++
|
| |
- bandit/plugins/weak_cryptographic_key.py | 2 ++
|
| |
- examples/ciphers.py | 48 ++++++++++++++++++++++----------
|
| |
- examples/crypto-md5.py | 7 +++++
|
| |
- examples/weak_cryptographic_key_sizes.py | 30 +++++++++++++-------
|
| |
- tests/functional/test_functional.py | 12 ++++----
|
| |
- 6 files changed, 84 insertions(+), 31 deletions(-)
|
| |
-
|
| |
- diff --git a/bandit/blacklists/calls.py b/bandit/blacklists/calls.py
|
| |
- index 5d9de7f..01f2ce5 100644
|
| |
- --- a/bandit/blacklists/calls.py
|
| |
- +++ b/bandit/blacklists/calls.py
|
| |
- @@ -64,6 +64,9 @@ Use of insecure MD2, MD4, or MD5 hash function.
|
| |
- | | | - Crypto.Hash.MD2.new | |
|
| |
- | | | - Crypto.Hash.MD4.new | |
|
| |
- | | | - Crypto.Hash.MD5.new | |
|
| |
- +| | | - Cryptodome.Hash.MD2.new | |
|
| |
- +| | | - Cryptodome.Hash.MD4.new | |
|
| |
- +| | | - Cryptodome.Hash.MD5.new | |
|
| |
- | | | - cryptography.hazmat.primitives | |
|
| |
- | | | .hashes.MD5 | |
|
| |
- +------+---------------------+------------------------------------+-----------+
|
| |
- @@ -82,6 +85,11 @@ as AES.
|
| |
- | | | - Crypto.Cipher.Blowfish.new | |
|
| |
- | | | - Crypto.Cipher.DES.new | |
|
| |
- | | | - Crypto.Cipher.XOR.new | |
|
| |
- +| | | - Cryptodome.Cipher.ARC2.new | |
|
| |
- +| | | - Cryptodome.Cipher.ARC4.new | |
|
| |
- +| | | - Cryptodome.Cipher.Blowfish.new | |
|
| |
- +| | | - Cryptodome.Cipher.DES.new | |
|
| |
- +| | | - Cryptodome.Cipher.XOR.new | |
|
| |
- | | | - cryptography.hazmat.primitives | |
|
| |
- | | | .ciphers.algorithms.ARC4 | |
|
| |
- | | | - cryptography.hazmat.primitives | |
|
| |
- @@ -313,6 +321,9 @@ def gen_blacklist():
|
| |
- 'Crypto.Hash.MD2.new',
|
| |
- 'Crypto.Hash.MD4.new',
|
| |
- 'Crypto.Hash.MD5.new',
|
| |
- + 'Cryptodome.Hash.MD2.new',
|
| |
- + 'Cryptodome.Hash.MD4.new',
|
| |
- + 'Cryptodome.Hash.MD5.new',
|
| |
- 'cryptography.hazmat.primitives.hashes.MD5'],
|
| |
- 'Use of insecure MD2, MD4, or MD5 hash function.'
|
| |
- ))
|
| |
- @@ -324,6 +335,11 @@ def gen_blacklist():
|
| |
- 'Crypto.Cipher.Blowfish.new',
|
| |
- 'Crypto.Cipher.DES.new',
|
| |
- 'Crypto.Cipher.XOR.new',
|
| |
- + 'Cryptodome.Cipher.ARC2.new',
|
| |
- + 'Cryptodome.Cipher.ARC4.new',
|
| |
- + 'Cryptodome.Cipher.Blowfish.new',
|
| |
- + 'Cryptodome.Cipher.DES.new',
|
| |
- + 'Cryptodome.Cipher.XOR.new',
|
| |
- 'cryptography.hazmat.primitives.ciphers.algorithms.ARC4',
|
| |
- 'cryptography.hazmat.primitives.ciphers.algorithms.Blowfish',
|
| |
- 'cryptography.hazmat.primitives.ciphers.algorithms.IDEA'],
|
| |
- diff --git a/bandit/plugins/weak_cryptographic_key.py b/bandit/plugins/weak_cryptographic_key.py
|
| |
- index 7411e2d..fba061b 100644
|
| |
- --- a/bandit/plugins/weak_cryptographic_key.py
|
| |
- +++ b/bandit/plugins/weak_cryptographic_key.py
|
| |
- @@ -106,6 +106,8 @@ def _weak_crypto_key_size_pycrypto(context):
|
| |
- func_key_type = {
|
| |
- 'Crypto.PublicKey.DSA.generate': 'DSA',
|
| |
- 'Crypto.PublicKey.RSA.generate': 'RSA',
|
| |
- + 'Cryptodome.PublicKey.DSA.generate': 'DSA',
|
| |
- + 'Cryptodome.PublicKey.RSA.generate': 'RSA',
|
| |
- }
|
| |
- key_type = func_key_type.get(context.call_function_name_qual)
|
| |
- if key_type:
|
| |
- diff --git a/examples/ciphers.py b/examples/ciphers.py
|
| |
- index ff334d4..7e0762d 100644
|
| |
- --- a/examples/ciphers.py
|
| |
- +++ b/examples/ciphers.py
|
| |
- @@ -1,8 +1,13 @@
|
| |
- -from Crypto.Cipher import ARC2
|
| |
- -from Crypto.Cipher import ARC4
|
| |
- -from Crypto.Cipher import Blowfish
|
| |
- -from Crypto.Cipher import DES
|
| |
- -from Crypto.Cipher import XOR
|
| |
- +from Crypto.Cipher import ARC2 as pycrypto_arc2
|
| |
- +from Crypto.Cipher import ARC4 as pycrypto_arc4
|
| |
- +from Crypto.Cipher import Blowfish as pycrypto_blowfish
|
| |
- +from Crypto.Cipher import DES as pycrypto_des
|
| |
- +from Crypto.Cipher import XOR as pycrypto_xor
|
| |
- +from Cryptodome.Cipher import ARC2 as pycryptodomex_arc2
|
| |
- +from Cryptodome.Cipher import ARC4 as pycryptodomex_arc4
|
| |
- +from Cryptodome.Cipher import Blowfish as pycryptodomex_blowfish
|
| |
- +from Cryptodome.Cipher import DES as pycryptodomex_des
|
| |
- +from Cryptodome.Cipher import XOR as pycryptodomex_xor
|
| |
- from Crypto.Hash import SHA
|
| |
- from Crypto import Random
|
| |
- from Crypto.Util import Counter
|
| |
- @@ -13,36 +18,49 @@ from cryptography.hazmat.backends import default_backend
|
| |
- from struct import pack
|
| |
-
|
| |
- key = b'Sixteen byte key'
|
| |
- -iv = Random.new().read(ARC2.block_size)
|
| |
- -cipher = ARC2.new(key, ARC2.MODE_CFB, iv)
|
| |
- +iv = Random.new().read(pycrypto_arc2.block_size)
|
| |
- +cipher = pycrypto_arc2.new(key, pycrypto_arc2.MODE_CFB, iv)
|
| |
- +msg = iv + cipher.encrypt(b'Attack at dawn')
|
| |
- +cipher = pycryptodomex_arc2.new(key, pycryptodomex_arc2.MODE_CFB, iv)
|
| |
- msg = iv + cipher.encrypt(b'Attack at dawn')
|
| |
-
|
| |
- key = b'Very long and confidential key'
|
| |
- nonce = Random.new().read(16)
|
| |
- tempkey = SHA.new(key+nonce).digest()
|
| |
- -cipher = ARC4.new(tempkey)
|
| |
- +cipher = pycrypto_arc4.new(tempkey)
|
| |
- +msg = nonce + cipher.encrypt(b'Open the pod bay doors, HAL')
|
| |
- +cipher = pycryptodomex_arc4.new(tempkey)
|
| |
- msg = nonce + cipher.encrypt(b'Open the pod bay doors, HAL')
|
| |
-
|
| |
- -bs = Blowfish.block_size
|
| |
- -key = b'An arbitrarily long key'
|
| |
- iv = Random.new().read(bs)
|
| |
- -cipher = Blowfish.new(key, Blowfish.MODE_CBC, iv)
|
| |
- +key = b'An arbitrarily long key'
|
| |
- plaintext = b'docendo discimus '
|
| |
- plen = bs - divmod(len(plaintext),bs)[1]
|
| |
- padding = [plen]*plen
|
| |
- padding = pack('b'*plen, *padding)
|
| |
- +bs = pycrypto_blowfish.block_size
|
| |
- +cipher = pycrypto_blowfish.new(key, pycrypto_blowfish.MODE_CBC, iv)
|
| |
- +msg = iv + cipher.encrypt(plaintext + padding)
|
| |
- +bs = pycryptodomex_blowfish.block_size
|
| |
- +cipher = pycryptodomex_blowfish.new(key, pycryptodomex_blowfish.MODE_CBC, iv)
|
| |
- msg = iv + cipher.encrypt(plaintext + padding)
|
| |
-
|
| |
- key = b'-8B key-'
|
| |
- -nonce = Random.new().read(DES.block_size/2)
|
| |
- -ctr = Counter.new(DES.block_size*8/2, prefix=nonce)
|
| |
- -cipher = DES.new(key, DES.MODE_CTR, counter=ctr)
|
| |
- plaintext = b'We are no longer the knights who say ni!'
|
| |
- +nonce = Random.new().read(pycrypto_des.block_size/2)
|
| |
- +ctr = Counter.new(pycrypto_des.block_size*8/2, prefix=nonce)
|
| |
- +cipher = pycrypto_des.new(key, pycrypto_des.MODE_CTR, counter=ctr)
|
| |
- +msg = nonce + cipher.encrypt(plaintext)
|
| |
- +nonce = Random.new().read(pycryptodomex_des.block_size/2)
|
| |
- +ctr = Counter.new(pycryptodomex_des.block_size*8/2, prefix=nonce)
|
| |
- +cipher = pycryptodomex_des.new(key, pycryptodomex_des.MODE_CTR, counter=ctr)
|
| |
- msg = nonce + cipher.encrypt(plaintext)
|
| |
-
|
| |
- key = b'Super secret key'
|
| |
- -cipher = XOR.new(key)
|
| |
- plaintext = b'Encrypt me'
|
| |
- +cipher = pycrypto_xor.new(key)
|
| |
- +msg = cipher.encrypt(plaintext)
|
| |
- +cipher = pycryptodomex_xor.new(key)
|
| |
- msg = cipher.encrypt(plaintext)
|
| |
-
|
| |
- cipher = Cipher(algorithms.ARC4(key), mode=None, backend=default_backend())
|
| |
- diff --git a/examples/crypto-md5.py b/examples/crypto-md5.py
|
| |
- index c9dc143..d5b85c2 100644
|
| |
- --- a/examples/crypto-md5.py
|
| |
- +++ b/examples/crypto-md5.py
|
| |
- @@ -2,6 +2,9 @@ from cryptography.hazmat.primitives import hashes
|
| |
- from Crypto.Hash import MD2 as pycrypto_md2
|
| |
- from Crypto.Hash import MD4 as pycrypto_md4
|
| |
- from Crypto.Hash import MD5 as pycrypto_md5
|
| |
- +from Cryptodome.Hash import MD2 as pycryptodomex_md2
|
| |
- +from Cryptodome.Hash import MD4 as pycryptodomex_md4
|
| |
- +from Cryptodome.Hash import MD5 as pycryptodomex_md5
|
| |
- import hashlib
|
| |
-
|
| |
- hashlib.md5(1)
|
| |
- @@ -15,4 +18,8 @@ pycrypto_md2.new()
|
| |
- pycrypto_md4.new()
|
| |
- pycrypto_md5.new()
|
| |
-
|
| |
- +pycryptodomex_md2.new()
|
| |
- +pycryptodomex_md4.new()
|
| |
- +pycryptodomex_md5.new()
|
| |
- +
|
| |
- hashes.MD5()
|
| |
- diff --git a/examples/weak_cryptographic_key_sizes.py b/examples/weak_cryptographic_key_sizes.py
|
| |
- index 42d8dd1..f2443b5 100644
|
| |
- --- a/examples/weak_cryptographic_key_sizes.py
|
| |
- +++ b/examples/weak_cryptographic_key_sizes.py
|
| |
- @@ -2,8 +2,10 @@ from cryptography.hazmat import backends
|
| |
- from cryptography.hazmat.primitives.asymmetric import dsa
|
| |
- from cryptography.hazmat.primitives.asymmetric import ec
|
| |
- from cryptography.hazmat.primitives.asymmetric import rsa
|
| |
- -from Crypto.PublicKey import DSA
|
| |
- -from Crypto.PublicKey import RSA
|
| |
- +from Crypto.PublicKey import DSA as pycrypto_dsa
|
| |
- +from Crypto.PublicKey import RSA as pycrypto_rsa
|
| |
- +from Cryptodome.PublicKey import DSA as pycryptodomex_dsa
|
| |
- +from Cryptodome.PublicKey import RSA as pycryptodomex_rsa
|
| |
-
|
| |
-
|
| |
- # Correct
|
| |
- @@ -14,8 +16,10 @@ ec.generate_private_key(curve=ec.SECP384R1,
|
| |
- rsa.generate_private_key(public_exponent=65537,
|
| |
- key_size=2048,
|
| |
- backend=backends.default_backend())
|
| |
- -DSA.generate(bits=2048)
|
| |
- -RSA.generate(bits=2048)
|
| |
- +pycrypto_dsa.generate(bits=2048)
|
| |
- +pycrypto_rsa.generate(bits=2048)
|
| |
- +pycryptodomex_dsa.generate(bits=2048)
|
| |
- +pycryptodomex_rsa.generate(bits=2048)
|
| |
-
|
| |
- # Also correct: without keyword args
|
| |
- dsa.generate_private_key(4096,
|
| |
- @@ -25,8 +29,10 @@ ec.generate_private_key(ec.SECP256K1,
|
| |
- rsa.generate_private_key(3,
|
| |
- 4096,
|
| |
- backends.default_backend())
|
| |
- -DSA.generate(4096)
|
| |
- -RSA.generate(4096)
|
| |
- +pycrypto_dsa.generate(4096)
|
| |
- +pycrypto_rsa.generate(4096)
|
| |
- +pycryptodomex_dsa.generate(4096)
|
| |
- +pycryptodomex_rsa.generate(4096)
|
| |
-
|
| |
- # Incorrect: weak key sizes
|
| |
- dsa.generate_private_key(key_size=1024,
|
| |
- @@ -36,8 +42,10 @@ ec.generate_private_key(curve=ec.SECT163R2,
|
| |
- rsa.generate_private_key(public_exponent=65537,
|
| |
- key_size=1024,
|
| |
- backend=backends.default_backend())
|
| |
- -DSA.generate(bits=1024)
|
| |
- -RSA.generate(bits=1024)
|
| |
- +pycrypto_dsa.generate(bits=1024)
|
| |
- +pycrypto_rsa.generate(bits=1024)
|
| |
- +pycryptodomex_dsa.generate(bits=1024)
|
| |
- +pycryptodomex_rsa.generate(bits=1024)
|
| |
-
|
| |
- # Also incorrect: without keyword args
|
| |
- dsa.generate_private_key(512,
|
| |
- @@ -47,8 +55,10 @@ ec.generate_private_key(ec.SECT163R2,
|
| |
- rsa.generate_private_key(3,
|
| |
- 512,
|
| |
- backends.default_backend())
|
| |
- -DSA.generate(512)
|
| |
- -RSA.generate(512)
|
| |
- +pycrypto_dsa.generate(512)
|
| |
- +pycrypto_rsa.generate(512)
|
| |
- +pycryptodomex_dsa.generate(512)
|
| |
- +pycryptodomex_rsa.generate(512)
|
| |
-
|
| |
- # Don't crash when the size is variable
|
| |
- rsa.generate_private_key(public_exponent=65537,
|
| |
- diff --git a/tests/functional/test_functional.py b/tests/functional/test_functional.py
|
| |
- index 5578c6a..b8b30c7 100644
|
| |
- --- a/tests/functional/test_functional.py
|
| |
- +++ b/tests/functional/test_functional.py
|
| |
- @@ -113,14 +113,14 @@ class FunctionalTests(testtools.TestCase):
|
| |
-
|
| |
- def test_crypto_md5(self):
|
| |
- '''Test the `hashlib.md5` example.'''
|
| |
- - expect = {'SEVERITY': {'MEDIUM': 8},
|
| |
- - 'CONFIDENCE': {'HIGH': 8}}
|
| |
- + expect = {'SEVERITY': {'MEDIUM': 11},
|
| |
- + 'CONFIDENCE': {'HIGH': 11}}
|
| |
- self.check_example('crypto-md5.py', expect)
|
| |
-
|
| |
- def test_ciphers(self):
|
| |
- '''Test the `Crypto.Cipher` example.'''
|
| |
- - expect = {'SEVERITY': {'HIGH': 8},
|
| |
- - 'CONFIDENCE': {'HIGH': 8}}
|
| |
- + expect = {'SEVERITY': {'HIGH': 13},
|
| |
- + 'CONFIDENCE': {'HIGH': 13}}
|
| |
- self.check_example('ciphers.py', expect)
|
| |
-
|
| |
- def test_cipher_modes(self):
|
| |
- @@ -465,8 +465,8 @@ class FunctionalTests(testtools.TestCase):
|
| |
- def test_weak_cryptographic_key(self):
|
| |
- '''Test for weak key sizes.'''
|
| |
- expect = {
|
| |
- - 'SEVERITY': {'MEDIUM': 6, 'HIGH': 4},
|
| |
- - 'CONFIDENCE': {'HIGH': 10}
|
| |
- + 'SEVERITY': {'MEDIUM': 8, 'HIGH': 6},
|
| |
- + 'CONFIDENCE': {'HIGH': 14}
|
| |
- }
|
| |
- self.check_example('weak_cryptographic_key_sizes.py', expect)
|
| |
-
|
| |
- --
|
| |
- 2.13.6
|
| |
-
|
| |
- From 6ce60806ca8a44d8a8b37050539e2b2f9a54b847 Mon Sep 17 00:00:00 2001
|
| |
- From: Philip Jones <philip.graham.jones@gmail.com>
|
| |
- Date: Fri, 30 Dec 2016 19:38:41 +0000
|
| |
- Subject: [PATCH 03/43] Alter SQL injection plugin to consider .format strings
|
| |
-
|
| |
- This considers `"{}".format()` style alongside `"%s" % ` string
|
| |
- formatting for possible SQL injection vulnerabilities.
|
| |
-
|
| |
- Change-Id: If7b09083bd2cc5e48e5d3fd3e8d5e6142fdb67ed
|
| |
- ---
|
| |
- bandit/plugins/injection_sql.py | 27 +++++++++++++++++++--------
|
| |
- examples/sql_statements.py | 13 ++++++-------
|
| |
- tests/functional/test_functional.py | 4 ++--
|
| |
- 3 files changed, 27 insertions(+), 17 deletions(-)
|
| |
-
|
| |
- diff --git a/bandit/plugins/injection_sql.py b/bandit/plugins/injection_sql.py
|
| |
- index e284e4c..afb63a5 100644
|
| |
- --- a/bandit/plugins/injection_sql.py
|
| |
- +++ b/bandit/plugins/injection_sql.py
|
| |
- @@ -27,6 +27,7 @@ some form of string building operation. For example:
|
| |
- - "SELECT %s FROM derp;" % var
|
| |
- - "SELECT thing FROM " + tab
|
| |
- - "SELECT " + val + " FROM " + tab + ...
|
| |
- + - "SELECT {} FROM derp;".format(var)
|
| |
-
|
| |
- Unless care is taken to sanitize and control the input data when building such
|
| |
- SQL statement strings, an injection attack becomes possible. If strings of this
|
| |
- @@ -80,15 +81,25 @@ def _check_string(data):
|
| |
-
|
| |
-
|
| |
- def _evaluate_ast(node):
|
| |
- - if not isinstance(node.parent, ast.BinOp):
|
| |
- - return (False, "")
|
| |
- -
|
| |
- - out = utils.concat_string(node, node.parent)
|
| |
- - if isinstance(out[0].parent, ast.Call): # wrapped in "execute" call?
|
| |
- + wrapper = None
|
| |
- + statement = ''
|
| |
- +
|
| |
- + if isinstance(node.parent, ast.BinOp):
|
| |
- + out = utils.concat_string(node, node.parent)
|
| |
- + wrapper = out[0].parent
|
| |
- + statement = out[1]
|
| |
- + elif (isinstance(node.parent, ast.Attribute)
|
| |
- + and node.parent.attr == 'format'):
|
| |
- + statement = node.s
|
| |
- + # Hierarchy for "".format() is Wrapper -> Call -> Attribute -> Str
|
| |
- + wrapper = node.parent.parent.parent
|
| |
- +
|
| |
- + if isinstance(wrapper, ast.Call): # wrapped in "execute" call?
|
| |
- names = ['execute', 'executemany']
|
| |
- - name = utils.get_called_name(out[0].parent)
|
| |
- - return (name in names, out[1])
|
| |
- - return (False, out[1])
|
| |
- + name = utils.get_called_name(wrapper)
|
| |
- + return (name in names, statement)
|
| |
- + else:
|
| |
- + return (False, statement)
|
| |
-
|
| |
-
|
| |
- @test.checks('Str')
|
| |
- diff --git a/examples/sql_statements.py b/examples/sql_statements.py
|
| |
- index 51abcff..1fabb70 100644
|
| |
- --- a/examples/sql_statements.py
|
| |
- +++ b/examples/sql_statements.py
|
| |
- @@ -7,12 +7,18 @@ query = "DELETE FROM foo WHERE id = '%s'" % identifier
|
| |
- query = "UPDATE foo SET value = 'b' WHERE id = '%s'" % identifier
|
| |
- query = """WITH cte AS (SELECT x FROM foo)
|
| |
- SELECT x FROM cte WHERE x = '%s'""" % identifier
|
| |
- +# bad alternate forms
|
| |
- +query = "SELECT * FROM foo WHERE id = '" + identifier + "'"
|
| |
- +query = "SELECT * FROM foo WHERE id = '{}'".format(identifier)
|
| |
-
|
| |
- # bad
|
| |
- cur.execute("SELECT * FROM foo WHERE id = '%s'" % identifier)
|
| |
- cur.execute("INSERT INTO foo VALUES ('a', 'b', '%s')" % value)
|
| |
- cur.execute("DELETE FROM foo WHERE id = '%s'" % identifier)
|
| |
- cur.execute("UPDATE foo SET value = 'b' WHERE id = '%s'" % identifier)
|
| |
- +# bad alternate forms
|
| |
- +cur.execute("SELECT * FROM foo WHERE id = '" + identifier + "'")
|
| |
- +cur.execute("SELECT * FROM foo WHERE id = '{}'".format(identifier))
|
| |
-
|
| |
- # good
|
| |
- cur.execute("SELECT * FROM foo WHERE id = '%s'", identifier)
|
| |
- @@ -20,13 +26,6 @@ cur.execute("INSERT INTO foo VALUES ('a', 'b', '%s')", value)
|
| |
- cur.execute("DELETE FROM foo WHERE id = '%s'", identifier)
|
| |
- cur.execute("UPDATE foo SET value = 'b' WHERE id = '%s'", identifier)
|
| |
-
|
| |
- -# bad
|
| |
- -query = "SELECT " + val + " FROM " + val +" WHERE id = " + val
|
| |
- -
|
| |
- -# bad
|
| |
- -cur.execute("SELECT " + val + " FROM " + val +" WHERE id = " + val)
|
| |
- -
|
| |
- -
|
| |
- # bug: https://bugs.launchpad.net/bandit/+bug/1479625
|
| |
- def a():
|
| |
- def b():
|
| |
- diff --git a/tests/functional/test_functional.py b/tests/functional/test_functional.py
|
| |
- index 1938583..afc206e 100644
|
| |
- --- a/tests/functional/test_functional.py
|
| |
- +++ b/tests/functional/test_functional.py
|
| |
- @@ -292,8 +292,8 @@ class FunctionalTests(testtools.TestCase):
|
| |
- def test_sql_statements(self):
|
| |
- '''Test for SQL injection through string building.'''
|
| |
- expect = {
|
| |
- - 'SEVERITY': {'MEDIUM': 12},
|
| |
- - 'CONFIDENCE': {'LOW': 7, 'MEDIUM': 5}}
|
| |
- + 'SEVERITY': {'MEDIUM': 14},
|
| |
- + 'CONFIDENCE': {'LOW': 8, 'MEDIUM': 6}}
|
| |
- self.check_example('sql_statements.py', expect)
|
| |
-
|
| |
- def test_ssl_insecure_version(self):
|
| |
- --
|
| |
- 2.13.6
|
| |
-
|
| |
- From 52c4b9be68258427d8b051c1bdd5dfed94c0a3ab Mon Sep 17 00:00:00 2001
|
| |
- From: Eric Brown <browne@vmware.com>
|
| |
- Date: Mon, 30 Jan 2017 14:08:03 -0800
|
| |
- Subject: [PATCH 04/43] Use https for references to openstack.org
|
| |
-
|
| |
- The openstack.org pages now support https and our references to
|
| |
- the site should by default be one signed by the organization.
|
| |
-
|
| |
- Change-Id: I83d2df500e2e30047494c201a2ab39820ffd1502
|
| |
- ---
|
| |
- bandit/core/docs_utils.py | 2 +-
|
| |
- bandit/formatters/html.py | 4 ++--
|
| |
- 2 files changed, 3 insertions(+), 3 deletions(-)
|
| |
-
|
| |
- diff --git a/bandit/core/docs_utils.py b/bandit/core/docs_utils.py
|
| |
- index 171eb27..6c2a640 100644
|
| |
- --- a/bandit/core/docs_utils.py
|
| |
- +++ b/bandit/core/docs_utils.py
|
| |
- @@ -15,7 +15,7 @@
|
| |
- # under the License.
|
| |
-
|
| |
- # where our docs are hosted
|
| |
- -BASE_URL = 'http://docs.openstack.org/developer/bandit/'
|
| |
- +BASE_URL = 'https://docs.openstack.org/developer/bandit/'
|
| |
-
|
| |
-
|
| |
- def get_url(bid):
|
| |
- diff --git a/bandit/formatters/html.py b/bandit/formatters/html.py
|
| |
- index 44917f2..04c289f 100644
|
| |
- --- a/bandit/formatters/html.py
|
| |
- +++ b/bandit/formatters/html.py
|
| |
- @@ -125,9 +125,9 @@ This formatter outputs the issues as HTML.
|
| |
- <b>Confidence: </b>HIGH<br>
|
| |
- <b>File: </b><a href="examples/yaml_load.py"
|
| |
- target="_blank">examples/yaml_load.py</a> <br>
|
| |
- - <b>More info: </b><a href="http://docs.openstack.org/developer/bandit/
|
| |
- + <b>More info: </b><a href="https://docs.openstack.org/developer/bandit/
|
| |
- plugins/yaml_load.html" target="_blank">
|
| |
- - http://docs.openstack.org/developer/bandit/plugins/yaml_load.html</a>
|
| |
- + https://docs.openstack.org/developer/bandit/plugins/yaml_load.html</a>
|
| |
- <br>
|
| |
-
|
| |
- <div class="code">
|
| |
- --
|
| |
- 2.13.6
|
| |
-
|
| |
- From e17af5cac241ef9ee72d47b90cb883d28274d5fd Mon Sep 17 00:00:00 2001
|
| |
- From: Anh Tran <anhtt@vn.fujitsu.com>
|
| |
- Date: Tue, 7 Feb 2017 10:12:47 +0700
|
| |
- Subject: [PATCH 05/43] Typo fix: targetting => targeting
|
| |
-
|
| |
- Change-Id: Iebfb2186e2824e47f57f53f9480776a9cbf67398
|
| |
- ---
|
| |
- bandit/core/test_set.py | 2 +-
|
| |
- 1 file changed, 1 insertion(+), 1 deletion(-)
|
| |
-
|
| |
- diff --git a/bandit/core/test_set.py b/bandit/core/test_set.py
|
| |
- index 826b5df..2429aeb 100644
|
| |
- --- a/bandit/core/test_set.py
|
| |
- +++ b/bandit/core/test_set.py
|
| |
- @@ -112,7 +112,7 @@ class BanditTestSet(object):
|
| |
- plugin.plugin._config = cfg
|
| |
- for check in plugin.plugin._checks:
|
| |
- self.tests.setdefault(check, []).append(plugin.plugin)
|
| |
- - LOG.debug('added function %s (%s) targetting %s',
|
| |
- + LOG.debug('added function %s (%s) targeting %s',
|
| |
- plugin.name, plugin.plugin._test_id, check)
|
| |
-
|
| |
- def get_tests(self, checktype):
|
| |
- --
|
| |
- 2.13.6
|
| |
-
|
| |
- From 32b4714562f3eb860d1a2afba90e0634e231fb09 Mon Sep 17 00:00:00 2001
|
| |
- From: OpenStack Proposal Bot <openstack-infra@lists.openstack.org>
|
| |
- Date: Fri, 10 Feb 2017 05:47:05 +0000
|
| |
- Subject: [PATCH 06/43] Updated from global requirements
|
| |
-
|
| |
- Change-Id: I5595e1b03dee7f2fdc7402a8e056ac84a8836040
|
| |
- ---
|
| |
- test-requirements.txt | 2 +-
|
| |
- 1 file changed, 1 insertion(+), 1 deletion(-)
|
| |
-
|
| |
- diff --git a/test-requirements.txt b/test-requirements.txt
|
| |
- index 11b6414..9e613c6 100644
|
| |
- --- a/test-requirements.txt
|
| |
- +++ b/test-requirements.txt
|
| |
- @@ -11,7 +11,7 @@ testscenarios>=0.4 # Apache-2.0/BSD
|
| |
- testtools>=1.4.0 # MIT
|
| |
- oslotest>=1.10.0 # Apache-2.0
|
| |
-
|
| |
- -sphinx!=1.3b1,<1.4,>=1.2.1 # BSD
|
| |
- +sphinx>=1.5.1 # BSD
|
| |
- oslosphinx>=4.7.0 # Apache-2.0
|
| |
- beautifulsoup4 # MIT
|
| |
- reno>=1.8.0 # Apache-2.0
|
| |
- --
|
| |
- 2.13.6
|
| |
-
|
| |
- From c924b2b12f1edf0e026e8e13a81005995e20d4fb Mon Sep 17 00:00:00 2001
|
| |
- From: Eric Brown <browne@vmware.com>
|
| |
- Date: Mon, 20 Feb 2017 11:20:18 -0800
|
| |
- Subject: [PATCH 07/43] HTTPSConnection is secure in newer Python
|
| |
-
|
| |
- In Python 2.7.9 [1] and 3.4.3 [2], the HTTPSConnection class has
|
| |
- been fixed to perform all the necessary certificate and hostname
|
| |
- checks by default.
|
| |
-
|
| |
- Therefore, Bandit's warning is only applicable if the module is
|
| |
- using older versions of Python. Even though Bandit could detect
|
| |
- the version of Python used for its scan, it cannot ensure that is
|
| |
- the same version used for running the said scanned module.
|
| |
-
|
| |
- This patch modifies the warning message to make this clearer.
|
| |
-
|
| |
- [1]: https://docs.python.org/2/library/httplib.html#httplib.HTTPSConnection
|
| |
- [2]: https://docs.python.org/3.4/library/http.client.html#http.client.HTTPSConnection
|
| |
-
|
| |
- Change-Id: I8105137d2cbbf0eb000729a18f43c3db443644d7
|
| |
- ---
|
| |
- bandit/blacklists/calls.py | 7 ++++---
|
| |
- 1 file changed, 4 insertions(+), 3 deletions(-)
|
| |
-
|
| |
- diff --git a/bandit/blacklists/calls.py b/bandit/blacklists/calls.py
|
| |
- index 01f2ce5..4f62961 100644
|
| |
- --- a/bandit/blacklists/calls.py
|
| |
- +++ b/bandit/blacklists/calls.py
|
| |
- @@ -138,8 +138,8 @@ be reviewed.
|
| |
- B309: httpsconnection
|
| |
- ---------------------
|
| |
-
|
| |
- -Use of HTTPSConnection does not provide security, see
|
| |
- -https://wiki.openstack.org/wiki/OSSN/OSSN-0033
|
| |
- +Use of HTTPSConnection on older versions of Python prior to 2.7.9 and 3.4.3 do
|
| |
- +not provide security, see https://wiki.openstack.org/wiki/OSSN/OSSN-0033
|
| |
-
|
| |
- +------+---------------------+------------------------------------+-----------+
|
| |
- | ID | Name | Calls | Severity |
|
| |
- @@ -376,7 +376,8 @@ def gen_blacklist():
|
| |
- ['httplib.HTTPSConnection',
|
| |
- 'http.client.HTTPSConnection',
|
| |
- 'six.moves.http_client.HTTPSConnection'],
|
| |
- - 'Use of HTTPSConnection does not provide security, see '
|
| |
- + 'Use of HTTPSConnection on older versions of Python prior to 2.7.9 '
|
| |
- + 'and 3.4.3 do not provide security, see '
|
| |
- 'https://wiki.openstack.org/wiki/OSSN/OSSN-0033'
|
| |
- ))
|
| |
-
|
| |
- --
|
| |
- 2.13.6
|
| |
-
|
| |
- From 4cf3af7d4cdaeace8992a6726e20ba63d3a22018 Mon Sep 17 00:00:00 2001
|
| |
- From: Eric Brown <browne@vmware.com>
|
| |
- Date: Mon, 20 Feb 2017 13:13:25 -0800
|
| |
- Subject: [PATCH 08/43] Allow config for high and medium severity key sizes
|
| |
-
|
| |
- The severity level of various key sizes of RSA, DSA, and EC are
|
| |
- currently hard-coded in the weak_cryptographic_key.py itself. This
|
| |
- patch allows the values to be overriden via the config file mechanism.
|
| |
-
|
| |
- Change-Id: I38ad5384e0e6012818bbac10f449840de6fb14ed
|
| |
- ---
|
| |
- bandit/plugins/weak_cryptographic_key.py | 40 ++++++++++++++++++++++----------
|
| |
- 1 file changed, 28 insertions(+), 12 deletions(-)
|
| |
-
|
| |
- diff --git a/bandit/plugins/weak_cryptographic_key.py b/bandit/plugins/weak_cryptographic_key.py
|
| |
- index fba061b..1c80e64 100644
|
| |
- --- a/bandit/plugins/weak_cryptographic_key.py
|
| |
- +++ b/bandit/plugins/weak_cryptographic_key.py
|
| |
- @@ -50,15 +50,30 @@ import bandit
|
| |
- from bandit.core import test_properties as test
|
| |
-
|
| |
-
|
| |
- -def _classify_key_size(key_type, key_size):
|
| |
- +def gen_config(name):
|
| |
- + if name == 'weak_cryptographic_key':
|
| |
- + return {
|
| |
- + 'weak_key_size_dsa_high': 1024,
|
| |
- + 'weak_key_size_dsa_medium': 2048,
|
| |
- + 'weak_key_size_rsa_high': 1024,
|
| |
- + 'weak_key_size_rsa_medium': 2048,
|
| |
- + 'weak_key_size_ec_high': 160,
|
| |
- + 'weak_key_size_ec_medium': 224,
|
| |
- + }
|
| |
- +
|
| |
- +
|
| |
- +def _classify_key_size(config, key_type, key_size):
|
| |
- if isinstance(key_size, str):
|
| |
- # size provided via a variable - can't process it at the moment
|
| |
- return
|
| |
-
|
| |
- key_sizes = {
|
| |
- - 'DSA': [(1024, bandit.HIGH), (2048, bandit.MEDIUM)],
|
| |
- - 'RSA': [(1024, bandit.HIGH), (2048, bandit.MEDIUM)],
|
| |
- - 'EC': [(160, bandit.HIGH), (224, bandit.MEDIUM)],
|
| |
- + 'DSA': [(config['weak_key_size_dsa_high'], bandit.HIGH),
|
| |
- + (config['weak_key_size_dsa_medium'], bandit.MEDIUM)],
|
| |
- + 'RSA': [(config['weak_key_size_rsa_high'], bandit.HIGH),
|
| |
- + (config['weak_key_size_rsa_medium'], bandit.MEDIUM)],
|
| |
- + 'EC': [(config['weak_key_size_ec_high'], bandit.HIGH),
|
| |
- + (config['weak_key_size_ec_medium'], bandit.MEDIUM)],
|
| |
- }
|
| |
-
|
| |
- for size, level in key_sizes[key_type]:
|
| |
- @@ -70,7 +85,7 @@ def _classify_key_size(key_type, key_size):
|
| |
- (key_type, size))
|
| |
-
|
| |
-
|
| |
- -def _weak_crypto_key_size_cryptography_io(context):
|
| |
- +def _weak_crypto_key_size_cryptography_io(context, config):
|
| |
- func_key_type = {
|
| |
- 'cryptography.hazmat.primitives.asymmetric.dsa.'
|
| |
- 'generate_private_key': 'DSA',
|
| |
- @@ -89,7 +104,7 @@ def _weak_crypto_key_size_cryptography_io(context):
|
| |
- key_size = (context.get_call_arg_value('key_size') or
|
| |
- context.get_call_arg_at_position(arg_position[key_type]) or
|
| |
- 2048)
|
| |
- - return _classify_key_size(key_type, key_size)
|
| |
- + return _classify_key_size(config, key_type, key_size)
|
| |
- elif key_type == 'EC':
|
| |
- curve_key_sizes = {
|
| |
- 'SECP192R1': 192,
|
| |
- @@ -99,10 +114,10 @@ def _weak_crypto_key_size_cryptography_io(context):
|
| |
- curve = (context.get_call_arg_value('curve') or
|
| |
- context.call_args[arg_position[key_type]])
|
| |
- key_size = curve_key_sizes[curve] if curve in curve_key_sizes else 224
|
| |
- - return _classify_key_size(key_type, key_size)
|
| |
- + return _classify_key_size(config, key_type, key_size)
|
| |
-
|
| |
-
|
| |
- -def _weak_crypto_key_size_pycrypto(context):
|
| |
- +def _weak_crypto_key_size_pycrypto(context, config):
|
| |
- func_key_type = {
|
| |
- 'Crypto.PublicKey.DSA.generate': 'DSA',
|
| |
- 'Crypto.PublicKey.RSA.generate': 'RSA',
|
| |
- @@ -114,11 +129,12 @@ def _weak_crypto_key_size_pycrypto(context):
|
| |
- key_size = (context.get_call_arg_value('bits') or
|
| |
- context.get_call_arg_at_position(0) or
|
| |
- 2048)
|
| |
- - return _classify_key_size(key_type, key_size)
|
| |
- + return _classify_key_size(config, key_type, key_size)
|
| |
-
|
| |
-
|
| |
- +@test.takes_config
|
| |
- @test.checks('Call')
|
| |
- @test.test_id('B505')
|
| |
- -def weak_cryptographic_key(context):
|
| |
- - return (_weak_crypto_key_size_cryptography_io(context) or
|
| |
- - _weak_crypto_key_size_pycrypto(context))
|
| |
- +def weak_cryptographic_key(context, config):
|
| |
- + return (_weak_crypto_key_size_cryptography_io(context, config) or
|
| |
- + _weak_crypto_key_size_pycrypto(context, config))
|
| |
- --
|
| |
- 2.13.6
|
| |
-
|
| |
- From a38056fafaead26e1b7198523b20f323130fa262 Mon Sep 17 00:00:00 2001
|
| |
- From: Eric Brown <browne@vmware.com>
|
| |
- Date: Mon, 20 Feb 2017 13:42:59 -0800
|
| |
- Subject: [PATCH 09/43] Dump bandit config file lists vertically
|
| |
-
|
| |
- Currently when using the bandit-config-generator to dump out a
|
| |
- config file, it looks rather messy because config option values
|
| |
- that are lists are dumped onto one long line.
|
| |
-
|
| |
- So rather than dumping on one line, use the vertical yaml list
|
| |
- format by specifying default_flow_style=False.
|
| |
-
|
| |
- Change-Id: Ic0dc97f19d067471b507421dcb98ac749874e49c
|
| |
- ---
|
| |
- bandit/cli/config_generator.py | 2 +-
|
| |
- tests/unit/cli/test_config_generator.py | 3 ++-
|
| |
- 2 files changed, 3 insertions(+), 2 deletions(-)
|
| |
-
|
| |
- diff --git a/bandit/cli/config_generator.py b/bandit/cli/config_generator.py
|
| |
- index e5d232c..a48c518 100644
|
| |
- --- a/bandit/cli/config_generator.py
|
| |
- +++ b/bandit/cli/config_generator.py
|
| |
- @@ -124,7 +124,7 @@ def get_config_settings():
|
| |
- if hasattr(fn_module, 'gen_config'):
|
| |
- config[fn_name] = fn_module.gen_config(function._takes_config)
|
| |
-
|
| |
- - return yaml.safe_dump(config)
|
| |
- + return yaml.safe_dump(config, default_flow_style=False)
|
| |
-
|
| |
-
|
| |
- def main():
|
| |
- diff --git a/tests/unit/cli/test_config_generator.py b/tests/unit/cli/test_config_generator.py
|
| |
- index f85c0a9..1431ab5 100644
|
| |
- --- a/tests/unit/cli/test_config_generator.py
|
| |
- +++ b/tests/unit/cli/test_config_generator.py
|
| |
- @@ -85,7 +85,8 @@ class BanditConfigGeneratorTests(testtools.TestCase):
|
| |
- config[plugin.name] = module.gen_config(
|
| |
- function._takes_config)
|
| |
- settings = config_generator.get_config_settings()
|
| |
- - self.assertEqual(yaml.safe_dump(config), settings)
|
| |
- + self.assertEqual(yaml.safe_dump(config, default_flow_style=False),
|
| |
- + settings)
|
| |
-
|
| |
- @mock.patch('sys.argv', ['bandit-config-generator', '--show-defaults'])
|
| |
- def test_main_show_defaults(self):
|
| |
- --
|
| |
- 2.13.6
|
| |
-
|
| |
- From 87c8b70e7bad5484e3ed9b17c9790108c151dee5 Mon Sep 17 00:00:00 2001
|
| |
- From: Eric Brown <browne@vmware.com>
|
| |
- Date: Thu, 23 Feb 2017 13:29:38 -0800
|
| |
- Subject: [PATCH 10/43] Refactor check_example to be clearer on error
|
| |
-
|
| |
- Currently the check_example in test_functional computes sums and
|
| |
- on error tells the developer the difference in sums, which is
|
| |
- confusing and error prone.
|
| |
-
|
| |
- It also leads to false positives where sums may be correct, but
|
| |
- the exact number of MEDIUM, HIGH, etc is different. This was the
|
| |
- case for two tests: test_xml and test_secret_config_option.
|
| |
-
|
| |
- The general_hardcoded_password test was also broken for py35
|
| |
- because it was assuming function args are ast.Name not ast.arg.
|
| |
- But surprisingly the tests passed because of a syntax error in
|
| |
- the example.
|
| |
-
|
| |
- Change-Id: Icd06fb7ca27a8a01d6442f199775d474d436371b
|
| |
- ---
|
| |
- bandit/plugins/general_hardcoded_password.py | 2 +-
|
| |
- examples/hardcoded-passwords.py | 10 +-
|
| |
- tests/functional/test_functional.py | 340 +++++++++++++++++++--------
|
| |
- 3 files changed, 247 insertions(+), 105 deletions(-)
|
| |
-
|
| |
- diff --git a/bandit/plugins/general_hardcoded_password.py b/bandit/plugins/general_hardcoded_password.py
|
| |
- index 97e3966..e9f7c3a 100644
|
| |
- --- a/bandit/plugins/general_hardcoded_password.py
|
| |
- +++ b/bandit/plugins/general_hardcoded_password.py
|
| |
- @@ -209,7 +209,7 @@ def hardcoded_password_default(context):
|
| |
-
|
| |
- # go through all (param, value)s and look for candidates
|
| |
- for key, val in zip(context.node.args.args, defs):
|
| |
- - if isinstance(key, ast.Name):
|
| |
- + if isinstance(key, ast.Name) or isinstance(key, ast.arg):
|
| |
- check = key.arg if sys.version_info.major > 2 else key.id # Py3
|
| |
- if isinstance(val, ast.Str) and check in CANDIDATES:
|
| |
- return _report(val.s)
|
| |
- diff --git a/examples/hardcoded-passwords.py b/examples/hardcoded-passwords.py
|
| |
- index b59794e..221c8f4 100644
|
| |
- --- a/examples/hardcoded-passwords.py
|
| |
- +++ b/examples/hardcoded-passwords.py
|
| |
- @@ -13,10 +13,12 @@ def NoMatch2(password):
|
| |
- if password == "ajklawejrkl42348swfgkg":
|
| |
- print("Nice password!")
|
| |
-
|
| |
- +def doLogin(password="blerg"):
|
| |
- + pass
|
| |
- +
|
| |
- +def NoMatch3(a, b):
|
| |
- + pass
|
| |
- +
|
| |
- doLogin(password="blerg")
|
| |
- password = "blerg"
|
| |
- d["password"] = "blerg"
|
| |
- -
|
| |
- -
|
| |
- -def NoMatch3((a, b)):
|
| |
- - pass
|
| |
- diff --git a/tests/functional/test_functional.py b/tests/functional/test_functional.py
|
| |
- index afc206e..5667001 100644
|
| |
- --- a/tests/functional/test_functional.py
|
| |
- +++ b/tests/functional/test_functional.py
|
| |
- @@ -69,17 +69,20 @@ class FunctionalTests(testtools.TestCase):
|
| |
- # reset scores for subsequent calls to check_example
|
| |
- self.b_mgr.scores = []
|
| |
- self.run_example(example_script, ignore_nosec=ignore_nosec)
|
| |
- - expected = 0
|
| |
- - result = 0
|
| |
- +
|
| |
- + result = {
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 0},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 0}
|
| |
- + }
|
| |
- +
|
| |
- for test_scores in self.b_mgr.scores:
|
| |
- for score_type in test_scores:
|
| |
- self.assertIn(score_type, expect)
|
| |
- - for rating in expect[score_type]:
|
| |
- - expected += (
|
| |
- - expect[score_type][rating] * C.RANKING_VALUES[rating]
|
| |
- - )
|
| |
- - result += sum(test_scores[score_type])
|
| |
- - self.assertEqual(expected, result)
|
| |
- + for idx, rank in enumerate(C.RANKING):
|
| |
- + result[score_type][rank] = (test_scores[score_type][idx] /
|
| |
- + C.RANKING_VALUES[rank])
|
| |
- +
|
| |
- + self.assertDictEqual(expect, result)
|
| |
-
|
| |
- def check_metrics(self, example_script, expect):
|
| |
- '''A helper method to test the metrics being returned.
|
| |
- @@ -108,34 +111,50 @@ class FunctionalTests(testtools.TestCase):
|
| |
-
|
| |
- def test_binding(self):
|
| |
- '''Test the bind-to-0.0.0.0 example.'''
|
| |
- - expect = {'SEVERITY': {'MEDIUM': 1}, 'CONFIDENCE': {'MEDIUM': 1}}
|
| |
- + expect = {
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 1, 'HIGH': 0},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 1, 'HIGH': 0}
|
| |
- + }
|
| |
- self.check_example('binding.py', expect)
|
| |
-
|
| |
- def test_crypto_md5(self):
|
| |
- '''Test the `hashlib.md5` example.'''
|
| |
- - expect = {'SEVERITY': {'MEDIUM': 11},
|
| |
- - 'CONFIDENCE': {'HIGH': 11}}
|
| |
- + expect = {
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 11, 'HIGH': 0},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 11}
|
| |
- + }
|
| |
- self.check_example('crypto-md5.py', expect)
|
| |
-
|
| |
- def test_ciphers(self):
|
| |
- '''Test the `Crypto.Cipher` example.'''
|
| |
- - expect = {'SEVERITY': {'HIGH': 13},
|
| |
- - 'CONFIDENCE': {'HIGH': 13}}
|
| |
- + expect = {
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 13},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 13}
|
| |
- + }
|
| |
- self.check_example('ciphers.py', expect)
|
| |
-
|
| |
- def test_cipher_modes(self):
|
| |
- '''Test for insecure cipher modes.'''
|
| |
- - expect = {'SEVERITY': {'MEDIUM': 1}, 'CONFIDENCE': {'HIGH': 1}}
|
| |
- + expect = {
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 1, 'HIGH': 0},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 1}
|
| |
- + }
|
| |
- self.check_example('cipher-modes.py', expect)
|
| |
-
|
| |
- def test_eval(self):
|
| |
- '''Test the `eval` example.'''
|
| |
- - expect = {'SEVERITY': {'MEDIUM': 3}, 'CONFIDENCE': {'HIGH': 3}}
|
| |
- + expect = {
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 3, 'HIGH': 0},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 3}
|
| |
- + }
|
| |
- self.check_example('eval.py', expect)
|
| |
-
|
| |
- def test_mark_safe(self):
|
| |
- '''Test the `mark_safe` example.'''
|
| |
- - expect = {'SEVERITY': {'MEDIUM': 1}, 'CONFIDENCE': {'HIGH': 1}}
|
| |
- + expect = {
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 1, 'HIGH': 0},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 1}
|
| |
- + }
|
| |
- self.check_example('mark_safe.py', expect)
|
| |
-
|
| |
- def test_exec(self):
|
| |
- @@ -143,68 +162,106 @@ class FunctionalTests(testtools.TestCase):
|
| |
- filename = 'exec-{}.py'
|
| |
- if six.PY2:
|
| |
- filename = filename.format('py2')
|
| |
- - expect = {'SEVERITY': {'MEDIUM': 2}, 'CONFIDENCE': {'HIGH': 2}}
|
| |
- + expect = {
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 2, 'HIGH': 0},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0,
|
| |
- + 'HIGH': 2}
|
| |
- + }
|
| |
- else:
|
| |
- filename = filename.format('py3')
|
| |
- - expect = {'SEVERITY': {'MEDIUM': 1}, 'CONFIDENCE': {'HIGH': 1}}
|
| |
- + expect = {
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 1, 'HIGH': 0},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0,
|
| |
- + 'HIGH': 1}
|
| |
- + }
|
| |
- self.check_example(filename, expect)
|
| |
-
|
| |
- def test_exec_as_root(self):
|
| |
- '''Test for the `run_as_root=True` keyword argument.'''
|
| |
- - expect = {'SEVERITY': {'LOW': 5}, 'CONFIDENCE': {'MEDIUM': 5}}
|
| |
- + expect = {
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 5, 'MEDIUM': 0, 'HIGH': 0},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 5, 'HIGH': 0}
|
| |
- + }
|
| |
- self.check_example('exec-as-root.py', expect)
|
| |
-
|
| |
- def test_hardcoded_passwords(self):
|
| |
- '''Test for hard-coded passwords.'''
|
| |
- - expect = {'SEVERITY': {'LOW': 7}, 'CONFIDENCE': {'MEDIUM': 7}}
|
| |
- + expect = {
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 8, 'MEDIUM': 0, 'HIGH': 0},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 8, 'HIGH': 0}
|
| |
- + }
|
| |
- self.check_example('hardcoded-passwords.py', expect)
|
| |
-
|
| |
- def test_hardcoded_tmp(self):
|
| |
- '''Test for hard-coded /tmp, /var/tmp, /dev/shm.'''
|
| |
- - expect = {'SEVERITY': {'MEDIUM': 3}, 'CONFIDENCE': {'MEDIUM': 3}}
|
| |
- + expect = {
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 3, 'HIGH': 0},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 3, 'HIGH': 0}
|
| |
- + }
|
| |
- self.check_example('hardcoded-tmp.py', expect)
|
| |
-
|
| |
- def test_httplib_https(self):
|
| |
- '''Test for `httplib.HTTPSConnection`.'''
|
| |
- - expect = {'SEVERITY': {'MEDIUM': 3}, 'CONFIDENCE': {'HIGH': 3}}
|
| |
- + expect = {
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 3, 'HIGH': 0},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 3}
|
| |
- + }
|
| |
- self.check_example('httplib_https.py', expect)
|
| |
-
|
| |
- def test_imports_aliases(self):
|
| |
- '''Test the `import X as Y` syntax.'''
|
| |
- expect = {
|
| |
- - 'SEVERITY': {'LOW': 4, 'MEDIUM': 5, 'HIGH': 0},
|
| |
- - 'CONFIDENCE': {'HIGH': 9}
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 4, 'MEDIUM': 5, 'HIGH': 0},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 9}
|
| |
- }
|
| |
- self.check_example('imports-aliases.py', expect)
|
| |
-
|
| |
- def test_imports_from(self):
|
| |
- '''Test the `from X import Y` syntax.'''
|
| |
- - expect = {'SEVERITY': {'LOW': 3}, 'CONFIDENCE': {'HIGH': 3}}
|
| |
- + expect = {
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 3, 'MEDIUM': 0, 'HIGH': 0},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 3}
|
| |
- + }
|
| |
- self.check_example('imports-from.py', expect)
|
| |
-
|
| |
- def test_imports_function(self):
|
| |
- '''Test the `__import__` function.'''
|
| |
- - expect = {'SEVERITY': {'LOW': 2}, 'CONFIDENCE': {'HIGH': 2}}
|
| |
- + expect = {
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 2, 'MEDIUM': 0, 'HIGH': 0},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 2}
|
| |
- + }
|
| |
- self.check_example('imports-function.py', expect)
|
| |
-
|
| |
- def test_telnet_usage(self):
|
| |
- '''Test for `import telnetlib` and Telnet.* calls.'''
|
| |
- - expect = {'SEVERITY': {'HIGH': 2}, 'CONFIDENCE': {'HIGH': 2}}
|
| |
- + expect = {
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 2},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 2}
|
| |
- + }
|
| |
- self.check_example('telnetlib.py', expect)
|
| |
-
|
| |
- def test_ftp_usage(self):
|
| |
- '''Test for `import ftplib` and FTP.* calls.'''
|
| |
- - expect = {'SEVERITY': {'HIGH': 2}, 'CONFIDENCE': {'HIGH': 2}}
|
| |
- + expect = {
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 2},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 2}
|
| |
- + }
|
| |
- self.check_example('ftplib.py', expect)
|
| |
-
|
| |
- def test_imports(self):
|
| |
- '''Test for dangerous imports.'''
|
| |
- - expect = {'SEVERITY': {'LOW': 2}, 'CONFIDENCE': {'HIGH': 2}}
|
| |
- + expect = {
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 2, 'MEDIUM': 0, 'HIGH': 0},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 2}
|
| |
- + }
|
| |
- self.check_example('imports.py', expect)
|
| |
-
|
| |
- def test_mktemp(self):
|
| |
- '''Test for `tempfile.mktemp`.'''
|
| |
- - expect = {'SEVERITY': {'MEDIUM': 4}, 'CONFIDENCE': {'HIGH': 4}}
|
| |
- + expect = {
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 4, 'HIGH': 0},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 4}
|
| |
- + }
|
| |
- self.check_example('mktemp.py', expect)
|
| |
-
|
| |
- def test_nonsense(self):
|
| |
- @@ -214,7 +271,10 @@ class FunctionalTests(testtools.TestCase):
|
| |
-
|
| |
- def test_okay(self):
|
| |
- '''Test a vulnerability-free file.'''
|
| |
- - expect = {'SEVERITY': {}, 'CONFIDENCE': {}}
|
| |
- + expect = {
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 0},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 0}
|
| |
- + }
|
| |
- self.check_example('okay.py', expect)
|
| |
-
|
| |
- def test_os_chmod(self):
|
| |
- @@ -225,75 +285,105 @@ class FunctionalTests(testtools.TestCase):
|
| |
- else:
|
| |
- filename = filename.format('py3')
|
| |
- expect = {
|
| |
- - 'SEVERITY': {'MEDIUM': 2, 'HIGH': 8},
|
| |
- - 'CONFIDENCE': {'MEDIUM': 1, 'HIGH': 9}
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 2, 'HIGH': 8},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 1, 'HIGH': 9}
|
| |
- }
|
| |
- self.check_example(filename, expect)
|
| |
-
|
| |
- def test_os_exec(self):
|
| |
- '''Test for `os.exec*`.'''
|
| |
- - expect = {'SEVERITY': {'LOW': 8}, 'CONFIDENCE': {'MEDIUM': 8}}
|
| |
- + expect = {
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 8, 'MEDIUM': 0, 'HIGH': 0},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 8, 'HIGH': 0}
|
| |
- + }
|
| |
- self.check_example('os-exec.py', expect)
|
| |
-
|
| |
- def test_os_popen(self):
|
| |
- '''Test for `os.popen`.'''
|
| |
- - expect = {'SEVERITY': {'LOW': 8, 'MEDIUM': 0, 'HIGH': 1},
|
| |
- - 'CONFIDENCE': {'HIGH': 9}}
|
| |
- + expect = {
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 8, 'MEDIUM': 0, 'HIGH': 1},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 9}
|
| |
- + }
|
| |
- self.check_example('os-popen.py', expect)
|
| |
-
|
| |
- def test_os_spawn(self):
|
| |
- '''Test for `os.spawn*`.'''
|
| |
- - expect = {'SEVERITY': {'LOW': 8}, 'CONFIDENCE': {'MEDIUM': 8}}
|
| |
- + expect = {
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 8, 'MEDIUM': 0, 'HIGH': 0},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 8, 'HIGH': 0}
|
| |
- + }
|
| |
- self.check_example('os-spawn.py', expect)
|
| |
-
|
| |
- def test_os_startfile(self):
|
| |
- '''Test for `os.startfile`.'''
|
| |
- - expect = {'SEVERITY': {'LOW': 3}, 'CONFIDENCE': {'MEDIUM': 3}}
|
| |
- + expect = {
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 3, 'MEDIUM': 0, 'HIGH': 0},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 3, 'HIGH': 0}
|
| |
- + }
|
| |
- self.check_example('os-startfile.py', expect)
|
| |
-
|
| |
- def test_os_system(self):
|
| |
- '''Test for `os.system`.'''
|
| |
- - expect = {'SEVERITY': {'LOW': 1}, 'CONFIDENCE': {'HIGH': 1}}
|
| |
- + expect = {
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 1, 'MEDIUM': 0, 'HIGH': 0},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 1}
|
| |
- + }
|
| |
- self.check_example('os_system.py', expect)
|
| |
-
|
| |
- def test_pickle(self):
|
| |
- '''Test for the `pickle` module.'''
|
| |
- expect = {
|
| |
- - 'SEVERITY': {'LOW': 2, 'MEDIUM': 6},
|
| |
- - 'CONFIDENCE': {'HIGH': 8}
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 2, 'MEDIUM': 6, 'HIGH': 0},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 8}
|
| |
- }
|
| |
- self.check_example('pickle_deserialize.py', expect)
|
| |
-
|
| |
- def test_popen_wrappers(self):
|
| |
- '''Test the `popen2` and `commands` modules.'''
|
| |
- - expect = {'SEVERITY': {'LOW': 7}, 'CONFIDENCE': {'HIGH': 7}}
|
| |
- + expect = {
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 7, 'MEDIUM': 0, 'HIGH': 0},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 7}
|
| |
- + }
|
| |
- self.check_example('popen_wrappers.py', expect)
|
| |
-
|
| |
- def test_random_module(self):
|
| |
- '''Test for the `random` module.'''
|
| |
- - expect = {'SEVERITY': {'LOW': 6}, 'CONFIDENCE': {'HIGH': 6}}
|
| |
- + expect = {
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 6, 'MEDIUM': 0, 'HIGH': 0},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 6}
|
| |
- + }
|
| |
- self.check_example('random_module.py', expect)
|
| |
-
|
| |
- def test_requests_ssl_verify_disabled(self):
|
| |
- '''Test for the `requests` library skipping verification.'''
|
| |
- - expect = {'SEVERITY': {'HIGH': 7}, 'CONFIDENCE': {'HIGH': 7}}
|
| |
- + expect = {
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 7},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 7}
|
| |
- + }
|
| |
- self.check_example('requests-ssl-verify-disabled.py', expect)
|
| |
-
|
| |
- def test_skip(self):
|
| |
- '''Test `#nosec` and `#noqa` comments.'''
|
| |
- - expect = {'SEVERITY': {'LOW': 5}, 'CONFIDENCE': {'HIGH': 5}}
|
| |
- + expect = {
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 5, 'MEDIUM': 0, 'HIGH': 0},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 5}
|
| |
- + }
|
| |
- self.check_example('skip.py', expect)
|
| |
-
|
| |
- def test_ignore_skip(self):
|
| |
- '''Test --ignore-nosec flag.'''
|
| |
- - expect = {'SEVERITY': {'LOW': 7}, 'CONFIDENCE': {'HIGH': 7}}
|
| |
- + expect = {
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 7, 'MEDIUM': 0, 'HIGH': 0},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 7}
|
| |
- + }
|
| |
- self.check_example('skip.py', expect, ignore_nosec=True)
|
| |
-
|
| |
- def test_sql_statements(self):
|
| |
- '''Test for SQL injection through string building.'''
|
| |
- expect = {
|
| |
- - 'SEVERITY': {'MEDIUM': 14},
|
| |
- - 'CONFIDENCE': {'LOW': 8, 'MEDIUM': 6}}
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 14, 'HIGH': 0},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 8, 'MEDIUM': 6, 'HIGH': 0}
|
| |
- + }
|
| |
- self.check_example('sql_statements.py', expect)
|
| |
-
|
| |
- def test_ssl_insecure_version(self):
|
| |
- @@ -302,126 +392,164 @@ class FunctionalTests(testtools.TestCase):
|
| |
- 'SEVERITY': {'LOW': 1, 'MEDIUM': 10, 'HIGH': 7},
|
| |
- 'CONFIDENCE': {'LOW': 0, 'MEDIUM': 11, 'HIGH': 7}
|
| |
- }
|
| |
- + expect = {
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 1, 'MEDIUM': 10, 'HIGH': 7},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 11, 'HIGH': 7}
|
| |
- + }
|
| |
- self.check_example('ssl-insecure-version.py', expect)
|
| |
-
|
| |
- def test_subprocess_shell(self):
|
| |
- '''Test for `subprocess.Popen` with `shell=True`.'''
|
| |
- expect = {
|
| |
- - 'SEVERITY': {'HIGH': 3, 'MEDIUM': 1, 'LOW': 14},
|
| |
- - 'CONFIDENCE': {'HIGH': 17, 'LOW': 1}
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 14, 'MEDIUM': 1, 'HIGH': 3},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 1, 'MEDIUM': 0, 'HIGH': 17}
|
| |
- }
|
| |
- self.check_example('subprocess_shell.py', expect)
|
| |
-
|
| |
- def test_urlopen(self):
|
| |
- '''Test for dangerous URL opening.'''
|
| |
- - expect = {'SEVERITY': {'MEDIUM': 14}, 'CONFIDENCE': {'HIGH': 14}}
|
| |
- + expect = {
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 14, 'HIGH': 0},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 14}
|
| |
- + }
|
| |
- self.check_example('urlopen.py', expect)
|
| |
-
|
| |
- def test_utils_shell(self):
|
| |
- '''Test for `utils.execute*` with `shell=True`.'''
|
| |
- expect = {
|
| |
- - 'SEVERITY': {'LOW': 5},
|
| |
- - 'CONFIDENCE': {'HIGH': 5}
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 5, 'MEDIUM': 0, 'HIGH': 0},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 5}
|
| |
- }
|
| |
- self.check_example('utils-shell.py', expect)
|
| |
-
|
| |
- def test_wildcard_injection(self):
|
| |
- '''Test for wildcard injection in shell commands.'''
|
| |
- expect = {
|
| |
- - 'SEVERITY': {'HIGH': 4, 'MEDIUM': 0, 'LOW': 10},
|
| |
- - 'CONFIDENCE': {'MEDIUM': 5, 'HIGH': 9}
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 10, 'MEDIUM': 0, 'HIGH': 4},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 5, 'HIGH': 9}
|
| |
- }
|
| |
- self.check_example('wildcard-injection.py', expect)
|
| |
-
|
| |
- def test_yaml(self):
|
| |
- '''Test for `yaml.load`.'''
|
| |
- - expect = {'SEVERITY': {'MEDIUM': 1}, 'CONFIDENCE': {'HIGH': 1}}
|
| |
- + expect = {
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 1, 'HIGH': 0},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 1}
|
| |
- + }
|
| |
- self.check_example('yaml_load.py', expect)
|
| |
-
|
| |
- def test_jinja2_templating(self):
|
| |
- '''Test jinja templating for potential XSS bugs.'''
|
| |
- expect = {
|
| |
- - 'SEVERITY': {'HIGH': 4},
|
| |
- - 'CONFIDENCE': {'HIGH': 3, 'MEDIUM': 1}
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 4},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 1, 'HIGH': 3}
|
| |
- }
|
| |
- self.check_example('jinja2_templating.py', expect)
|
| |
-
|
| |
- def test_secret_config_option(self):
|
| |
- '''Test for `secret=True` in Oslo's config.'''
|
| |
- expect = {
|
| |
- - 'SEVERITY': {'LOW': 1, 'MEDIUM': 2},
|
| |
- - 'CONFIDENCE': {'MEDIUM': 3}
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 3, 'HIGH': 0},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 1, 'MEDIUM': 2, 'HIGH': 0}
|
| |
- }
|
| |
- self.check_example('secret-config-option.py', expect)
|
| |
-
|
| |
- def test_mako_templating(self):
|
| |
- '''Test Mako templates for XSS.'''
|
| |
- - expect = {'SEVERITY': {'MEDIUM': 3}, 'CONFIDENCE': {'HIGH': 3}}
|
| |
- + expect = {
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 3, 'HIGH': 0},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 3}
|
| |
- + }
|
| |
- self.check_example('mako_templating.py', expect)
|
| |
-
|
| |
- def test_xml(self):
|
| |
- '''Test xml vulnerabilities.'''
|
| |
- - expect = {'SEVERITY': {'LOW': 1, 'HIGH': 4},
|
| |
- - 'CONFIDENCE': {'HIGH': 1, 'MEDIUM': 4}}
|
| |
- + expect = {
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 1, 'MEDIUM': 4, 'HIGH': 0},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 5}
|
| |
- + }
|
| |
- self.check_example('xml_etree_celementtree.py', expect)
|
| |
-
|
| |
- - expect = {'SEVERITY': {'LOW': 1, 'HIGH': 2},
|
| |
- - 'CONFIDENCE': {'HIGH': 1, 'MEDIUM': 2}}
|
| |
- + expect = {
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 1, 'MEDIUM': 2, 'HIGH': 0},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 3}
|
| |
- + }
|
| |
- self.check_example('xml_expatbuilder.py', expect)
|
| |
-
|
| |
- - expect = {'SEVERITY': {'LOW': 3, 'HIGH': 1},
|
| |
- - 'CONFIDENCE': {'HIGH': 3, 'MEDIUM': 1}}
|
| |
- + expect = {
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 3, 'MEDIUM': 1, 'HIGH': 0},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 4}
|
| |
- + }
|
| |
- self.check_example('xml_lxml.py', expect)
|
| |
-
|
| |
- - expect = {'SEVERITY': {'LOW': 2, 'HIGH': 2},
|
| |
- - 'CONFIDENCE': {'HIGH': 2, 'MEDIUM': 2}}
|
| |
- + expect = {
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 2, 'MEDIUM': 2, 'HIGH': 0},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 4}
|
| |
- + }
|
| |
- self.check_example('xml_pulldom.py', expect)
|
| |
-
|
| |
- - expect = {'SEVERITY': {'HIGH': 1},
|
| |
- - 'CONFIDENCE': {'HIGH': 1}}
|
| |
- + expect = {
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 1},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 1}
|
| |
- + }
|
| |
- self.check_example('xml_xmlrpc.py', expect)
|
| |
-
|
| |
- - expect = {'SEVERITY': {'LOW': 1, 'HIGH': 4},
|
| |
- - 'CONFIDENCE': {'HIGH': 1, 'MEDIUM': 4}}
|
| |
- + expect = {
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 1, 'MEDIUM': 4, 'HIGH': 0},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 5}
|
| |
- + }
|
| |
- self.check_example('xml_etree_elementtree.py', expect)
|
| |
-
|
| |
- - expect = {'SEVERITY': {'LOW': 1, 'HIGH': 1},
|
| |
- - 'CONFIDENCE': {'HIGH': 1, 'MEDIUM': 1}}
|
| |
- + expect = {
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 1, 'MEDIUM': 1, 'HIGH': 0},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 2}
|
| |
- + }
|
| |
- self.check_example('xml_expatreader.py', expect)
|
| |
-
|
| |
- - expect = {'SEVERITY': {'LOW': 2, 'HIGH': 2},
|
| |
- - 'CONFIDENCE': {'HIGH': 2, 'MEDIUM': 2}}
|
| |
- + expect = {
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 2, 'MEDIUM': 2, 'HIGH': 0},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 4}
|
| |
- + }
|
| |
- self.check_example('xml_minidom.py', expect)
|
| |
-
|
| |
- - expect = {'SEVERITY': {'LOW': 2, 'HIGH': 6},
|
| |
- - 'CONFIDENCE': {'HIGH': 2, 'MEDIUM': 6}}
|
| |
- + expect = {
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 2, 'MEDIUM': 6, 'HIGH': 0},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 8}
|
| |
- + }
|
| |
- self.check_example('xml_sax.py', expect)
|
| |
-
|
| |
- def test_httpoxy(self):
|
| |
- '''Test httpoxy vulnerability.'''
|
| |
- - expect = {'SEVERITY': {'HIGH': 1},
|
| |
- - 'CONFIDENCE': {'HIGH': 1}}
|
| |
- + expect = {
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 1},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 1}
|
| |
- + }
|
| |
- self.check_example('httpoxy_cgihandler.py', expect)
|
| |
- self.check_example('httpoxy_twisted_script.py', expect)
|
| |
- self.check_example('httpoxy_twisted_directory.py', expect)
|
| |
-
|
| |
- def test_asserts(self):
|
| |
- '''Test catching the use of assert.'''
|
| |
- - expect = {'SEVERITY': {'LOW': 1},
|
| |
- - 'CONFIDENCE': {'HIGH': 1}}
|
| |
- + expect = {
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 1, 'MEDIUM': 0, 'HIGH': 0},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 1}
|
| |
- + }
|
| |
- self.check_example('assert.py', expect)
|
| |
-
|
| |
- def test_paramiko_injection(self):
|
| |
- '''Test paramiko command execution.'''
|
| |
- - expect = {'SEVERITY': {'MEDIUM': 2},
|
| |
- - 'CONFIDENCE': {'MEDIUM': 2}}
|
| |
- + expect = {
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 2, 'HIGH': 0},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 2, 'HIGH': 0}
|
| |
- + }
|
| |
- self.check_example('paramiko_injection.py', expect)
|
| |
-
|
| |
- def test_partial_path(self):
|
| |
- '''Test process spawning with partial file paths.'''
|
| |
- - expect = {'SEVERITY': {'LOW': 11},
|
| |
- - 'CONFIDENCE': {'HIGH': 11}}
|
| |
- -
|
| |
- + expect = {
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 11, 'MEDIUM': 0, 'HIGH': 0},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 11}
|
| |
- + }
|
| |
- self.check_example('partial_path_process.py', expect)
|
| |
-
|
| |
- def test_try_except_continue(self):
|
| |
- @@ -430,11 +558,17 @@ class FunctionalTests(testtools.TestCase):
|
| |
- if x.__name__ == 'try_except_continue'))
|
| |
-
|
| |
- test._config = {'check_typed_exception': True}
|
| |
- - expect = {'SEVERITY': {'LOW': 3}, 'CONFIDENCE': {'HIGH': 3}}
|
| |
- + expect = {
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 3, 'MEDIUM': 0, 'HIGH': 0},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 3}
|
| |
- + }
|
| |
- self.check_example('try_except_continue.py', expect)
|
| |
-
|
| |
- test._config = {'check_typed_exception': False}
|
| |
- - expect = {'SEVERITY': {'LOW': 2}, 'CONFIDENCE': {'HIGH': 2}}
|
| |
- + expect = {
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 2, 'MEDIUM': 0, 'HIGH': 0},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 2}
|
| |
- + }
|
| |
- self.check_example('try_except_continue.py', expect)
|
| |
-
|
| |
- def test_try_except_pass(self):
|
| |
- @@ -443,11 +577,17 @@ class FunctionalTests(testtools.TestCase):
|
| |
- if x.__name__ == 'try_except_pass'))
|
| |
-
|
| |
- test._config = {'check_typed_exception': True}
|
| |
- - expect = {'SEVERITY': {'LOW': 3}, 'CONFIDENCE': {'HIGH': 3}}
|
| |
- + expect = {
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 3, 'MEDIUM': 0, 'HIGH': 0},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 3}
|
| |
- + }
|
| |
- self.check_example('try_except_pass.py', expect)
|
| |
-
|
| |
- test._config = {'check_typed_exception': False}
|
| |
- - expect = {'SEVERITY': {'LOW': 2}, 'CONFIDENCE': {'HIGH': 2}}
|
| |
- + expect = {
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 2, 'MEDIUM': 0, 'HIGH': 0},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 2}
|
| |
- + }
|
| |
- self.check_example('try_except_pass.py', expect)
|
| |
-
|
| |
- def test_metric_gathering(self):
|
| |
- @@ -465,8 +605,8 @@ class FunctionalTests(testtools.TestCase):
|
| |
- def test_weak_cryptographic_key(self):
|
| |
- '''Test for weak key sizes.'''
|
| |
- expect = {
|
| |
- - 'SEVERITY': {'MEDIUM': 8, 'HIGH': 6},
|
| |
- - 'CONFIDENCE': {'HIGH': 14}
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 8, 'HIGH': 6},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 14}
|
| |
- }
|
| |
- self.check_example('weak_cryptographic_key_sizes.py', expect)
|
| |
-
|
| |
- @@ -503,15 +643,15 @@ class FunctionalTests(testtools.TestCase):
|
| |
-
|
| |
- def test_flask_debug_true(self):
|
| |
- expect = {
|
| |
- - 'SEVERITY': {'HIGH': 1},
|
| |
- - 'CONFIDENCE': {'MEDIUM': 1}
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 1},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 1, 'HIGH': 0}
|
| |
- }
|
| |
- self.check_example('flask_debug.py', expect)
|
| |
-
|
| |
- def test_nosec(self):
|
| |
- expect = {
|
| |
- - 'SEVERITY': {},
|
| |
- - 'CONFIDENCE': {}
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 0},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 0}
|
| |
- }
|
| |
- self.check_example('nosec.py', expect)
|
| |
-
|
| |
- @@ -545,7 +685,7 @@ class FunctionalTests(testtools.TestCase):
|
| |
-
|
| |
- def test_blacklist_input(self):
|
| |
- expect = {
|
| |
- - 'SEVERITY': {'HIGH': 1},
|
| |
- - 'CONFIDENCE': {'HIGH': 1}
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 1},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 1}
|
| |
- }
|
| |
- self.check_example('input.py', expect)
|
| |
- --
|
| |
- 2.13.6
|
| |
-
|
| |
- From 35e35446b00bceea5bebf028567e4f99bd29dc98 Mon Sep 17 00:00:00 2001
|
| |
- From: Eric Brown <browne@vmware.com>
|
| |
- Date: Thu, 23 Feb 2017 11:05:04 -0800
|
| |
- Subject: [PATCH 11/43] Add sha-1 to list of insecure hashes
|
| |
-
|
| |
- With the news of a first collison implemented [1], bandit should
|
| |
- now start blacklisting the use of sha-1.
|
| |
-
|
| |
- The sha-1 hash was added to the existing blacklist check B303 which
|
| |
- currently checks for MD5 and variants.
|
| |
-
|
| |
- [1]: https://security.googleblog.com/2017/02/announcing-first-sha1-collision.html
|
| |
-
|
| |
- Change-Id: I411d8d4aeb4d740635c60b559ecda72ab951b629
|
| |
- ---
|
| |
- bandit/blacklists/calls.py | 15 ++++++++++++---
|
| |
- examples/crypto-md5.py | 7 +++++++
|
| |
- tests/functional/test_functional.py | 8 ++++----
|
| |
- 3 files changed, 23 insertions(+), 7 deletions(-)
|
| |
-
|
| |
- diff --git a/bandit/blacklists/calls.py b/bandit/blacklists/calls.py
|
| |
- index 01f2ce5..78b029c 100644
|
| |
- --- a/bandit/blacklists/calls.py
|
| |
- +++ b/bandit/blacklists/calls.py
|
| |
- @@ -55,20 +55,25 @@ Deserialization with the marshal module is possibly dangerous.
|
| |
- B303: md5
|
| |
- ---------
|
| |
-
|
| |
- -Use of insecure MD2, MD4, or MD5 hash function.
|
| |
- +Use of insecure MD2, MD4, MD5, or SHA1 hash function.
|
| |
-
|
| |
- +------+---------------------+------------------------------------+-----------+
|
| |
- | ID | Name | Calls | Severity |
|
| |
- +======+=====================+====================================+===========+
|
| |
- | B303 | md5 | - hashlib.md5 | Medium |
|
| |
- +| | | - hashlib.sha1 | |
|
| |
- | | | - Crypto.Hash.MD2.new | |
|
| |
- | | | - Crypto.Hash.MD4.new | |
|
| |
- | | | - Crypto.Hash.MD5.new | |
|
| |
- +| | | - Crypto.Hash.SHA.new | |
|
| |
- | | | - Cryptodome.Hash.MD2.new | |
|
| |
- | | | - Cryptodome.Hash.MD4.new | |
|
| |
- | | | - Cryptodome.Hash.MD5.new | |
|
| |
- +| | | - Cryptodome.Hash.SHA.new | |
|
| |
- | | | - cryptography.hazmat.primitives | |
|
| |
- | | | .hashes.MD5 | |
|
| |
- +| | | - cryptography.hazmat.primitives | |
|
| |
- +| | | .hashes.SHA1 | |
|
| |
- +------+---------------------+------------------------------------+-----------+
|
| |
-
|
| |
- B304 - B305: ciphers and modes
|
| |
- @@ -318,14 +323,18 @@ def gen_blacklist():
|
| |
- sets.append(utils.build_conf_dict(
|
| |
- 'md5', 'B303',
|
| |
- ['hashlib.md5',
|
| |
- + 'hashlib.sha1',
|
| |
- 'Crypto.Hash.MD2.new',
|
| |
- 'Crypto.Hash.MD4.new',
|
| |
- 'Crypto.Hash.MD5.new',
|
| |
- + 'Crypto.Hash.SHA.new',
|
| |
- 'Cryptodome.Hash.MD2.new',
|
| |
- 'Cryptodome.Hash.MD4.new',
|
| |
- 'Cryptodome.Hash.MD5.new',
|
| |
- - 'cryptography.hazmat.primitives.hashes.MD5'],
|
| |
- - 'Use of insecure MD2, MD4, or MD5 hash function.'
|
| |
- + 'Cryptodome.Hash.SHA.new',
|
| |
- + 'cryptography.hazmat.primitives.hashes.MD5',
|
| |
- + 'cryptography.hazmat.primitives.hashes.SHA1'],
|
| |
- + 'Use of insecure MD2, MD4, MD5, or SHA1 hash function.'
|
| |
- ))
|
| |
-
|
| |
- sets.append(utils.build_conf_dict(
|
| |
- diff --git a/examples/crypto-md5.py b/examples/crypto-md5.py
|
| |
- index d5b85c2..045740c 100644
|
| |
- --- a/examples/crypto-md5.py
|
| |
- +++ b/examples/crypto-md5.py
|
| |
- @@ -2,9 +2,11 @@ from cryptography.hazmat.primitives import hashes
|
| |
- from Crypto.Hash import MD2 as pycrypto_md2
|
| |
- from Crypto.Hash import MD4 as pycrypto_md4
|
| |
- from Crypto.Hash import MD5 as pycrypto_md5
|
| |
- +from Crypto.Hash import SHA as pycrypto_sha
|
| |
- from Cryptodome.Hash import MD2 as pycryptodomex_md2
|
| |
- from Cryptodome.Hash import MD4 as pycryptodomex_md4
|
| |
- from Cryptodome.Hash import MD5 as pycryptodomex_md5
|
| |
- +from Cryptodome.Hash import SHA as pycryptodomex_sha
|
| |
- import hashlib
|
| |
-
|
| |
- hashlib.md5(1)
|
| |
- @@ -14,12 +16,17 @@ abc = str.replace(hashlib.md5("1"), "###")
|
| |
-
|
| |
- print(hashlib.md5("1"))
|
| |
-
|
| |
- +hashlib.sha1(1)
|
| |
- +
|
| |
- pycrypto_md2.new()
|
| |
- pycrypto_md4.new()
|
| |
- pycrypto_md5.new()
|
| |
- +pycrypto_sha.new()
|
| |
-
|
| |
- pycryptodomex_md2.new()
|
| |
- pycryptodomex_md4.new()
|
| |
- pycryptodomex_md5.new()
|
| |
- +pycryptodomex_sha.new()
|
| |
-
|
| |
- hashes.MD5()
|
| |
- +hashes.SHA1()
|
| |
- diff --git a/tests/functional/test_functional.py b/tests/functional/test_functional.py
|
| |
- index 5667001..e94c3bf 100644
|
| |
- --- a/tests/functional/test_functional.py
|
| |
- +++ b/tests/functional/test_functional.py
|
| |
- @@ -120,16 +120,16 @@ class FunctionalTests(testtools.TestCase):
|
| |
- def test_crypto_md5(self):
|
| |
- '''Test the `hashlib.md5` example.'''
|
| |
- expect = {
|
| |
- - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 11, 'HIGH': 0},
|
| |
- - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 11}
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 15, 'HIGH': 0},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 15}
|
| |
- }
|
| |
- self.check_example('crypto-md5.py', expect)
|
| |
-
|
| |
- def test_ciphers(self):
|
| |
- '''Test the `Crypto.Cipher` example.'''
|
| |
- expect = {
|
| |
- - 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 13},
|
| |
- - 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 13}
|
| |
- + 'SEVERITY': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 1, 'HIGH': 13},
|
| |
- + 'CONFIDENCE': {'UNDEFINED': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 14}
|
| |
- }
|
| |
- self.check_example('ciphers.py', expect)
|
| |
-
|
| |
- --
|
| |
- 2.13.6
|
| |
-
|
| |
- From 6a1a4b120d005cee2ed0c8be723dd367b549b3f0 Mon Sep 17 00:00:00 2001
|
| |
- From: Eric Brown <browne@vmware.com>
|
| |
- Date: Mon, 27 Feb 2017 15:48:44 -0800
|
| |
- Subject: [PATCH 12/43] Docs for B319 listed twice
|
| |
-
|
| |
- The blacklist calls doc lists B319 twice. This patch removes the
|
| |
- duplicate.
|
| |
-
|
| |
- [1]: https://docs.openstack.org/developer/bandit/blacklists/blacklist_calls.html#b313-b320-xml
|
| |
-
|
| |
- Change-Id: I94ca7cb1201f6d74ce8672294d2ba421ea5a608c
|
| |
- ---
|
| |
- bandit/blacklists/calls.py | 3 ---
|
| |
- 1 file changed, 3 deletions(-)
|
| |
-
|
| |
- diff --git a/bandit/blacklists/calls.py b/bandit/blacklists/calls.py
|
| |
- index 01f2ce5..9928f59 100644
|
| |
- --- a/bandit/blacklists/calls.py
|
| |
- +++ b/bandit/blacklists/calls.py
|
| |
- @@ -244,9 +244,6 @@ to XML attacks. Methods should be replaced with their defusedxml equivalents.
|
| |
- | B319 | xml_bad_pulldom | - xml.dom.pulldom.parse | Medium |
|
| |
- | | | - xml.dom.pulldom.parseString | |
|
| |
- +------+---------------------+------------------------------------+-----------+
|
| |
- -| B319 | xml_bad_pulldom | - xml.dom.pulldom.parse | Medium |
|
| |
- -| | | - xml.dom.pulldom.parseString | |
|
| |
- -+------+---------------------+------------------------------------+-----------+
|
| |
- | B320 | xml_bad_etree | - lxml.etree.parse | Medium |
|
| |
- | | | - lxml.etree.fromstring | |
|
| |
- | | | - lxml.etree.RestrictedElement | |
|
| |
- --
|
| |
- 2.13.6
|
| |
-
|
| |
- From be0483a603407ba8f8110da415161cee26f142b8 Mon Sep 17 00:00:00 2001
|
| |
- From: Eric Brown <browne@vmware.com>
|
| |
- Date: Mon, 27 Feb 2017 15:44:02 -0800
|
| |
- Subject: [PATCH 13/43] Repair the more info links for two blacklist calls
|
| |
-
|
| |
- The blacklist calls has some of documentation anchors combined [1].
|
| |
- As a result, the links don't correct point to the proper anchor in
|
| |
- the html. Therefore we need some exception cases for checks that
|
| |
- have doc combined. Namely B304-B305 and B313-B320.
|
| |
-
|
| |
- This patch also fixes links where there is an underscore in the
|
| |
- plugin name and replaces it with a dash. Apparently sphinx will
|
| |
- substitute _ for - when building the doc anchors.
|
| |
-
|
| |
- [1]: https://docs.openstack.org/developer/bandit/blacklists/blacklist_calls.html#b304-b305-ciphers-and-modes
|
| |
-
|
| |
- Change-Id: I4dfa905425f2631fa488a9a066c427d4145f4aac
|
| |
- ---
|
| |
- bandit/core/docs_utils.py | 9 +++++++++
|
| |
- 1 file changed, 9 insertions(+)
|
| |
-
|
| |
- diff --git a/bandit/core/docs_utils.py b/bandit/core/docs_utils.py
|
| |
- index 6c2a640..8f268e0 100644
|
| |
- --- a/bandit/core/docs_utils.py
|
| |
- +++ b/bandit/core/docs_utils.py
|
| |
- @@ -31,7 +31,16 @@ def get_url(bid):
|
| |
- info = extension_loader.MANAGER.blacklist_by_id.get(bid, None)
|
| |
- if info is not None:
|
| |
- template = 'blacklists/blacklist_{kind}.html#{id}-{name}'
|
| |
- + info['name'] = info['name'].replace('_', '-')
|
| |
- +
|
| |
- if info['id'].startswith('B3'): # B3XX
|
| |
- + # Some of the links are combined, so we have exception cases
|
| |
- + if info['id'] in ['B304', 'B305']:
|
| |
- + info['id'] = 'b304-b305'
|
| |
- + info['name'] = 'ciphers-and-modes'
|
| |
- + elif info['id'] in ['B313', 'B314', 'B315', 'B316', 'B317',
|
| |
- + 'B318', 'B319', 'B320']:
|
| |
- + info['id'] = 'b313-b320'
|
| |
- ext = template.format(
|
| |
- kind='calls', id=info['id'], name=info['name'])
|
| |
- else:
|
| |
- --
|
| |
- 2.13.6
|
| |
-
|
| |
- From fbd4e83efe15987f4f8a4608456211fba37fc80b Mon Sep 17 00:00:00 2001
|
| |
- From: Eric Brown <browne@vmware.com>
|
| |
- Date: Wed, 22 Feb 2017 20:28:19 -0800
|
| |
- Subject: [PATCH 14/43] Yet Another Formatter (yaml)
|
| |
-
|
| |
- This patch adds a yaml formatter to the output options of bandit.
|
| |
-
|
| |
- Change-Id: Ibbe0cff062ce2c11138b746f95109f31de10f5b1
|
| |
- ---
|
| |
- README.rst | 6 +--
|
| |
- bandit/formatters/yaml.py | 92 ++++++++++++++++++++++++++++++++++++
|
| |
- doc/source/formatters/yaml.rst | 5 ++
|
| |
- doc/source/man/bandit.rst | 6 +--
|
| |
- setup.cfg | 1 +
|
| |
- tests/unit/formatters/test_yaml.py | 96 ++++++++++++++++++++++++++++++++++++++
|
| |
- 6 files changed, 200 insertions(+), 6 deletions(-)
|
| |
- create mode 100644 bandit/formatters/yaml.py
|
| |
- create mode 100644 doc/source/formatters/yaml.rst
|
| |
- create mode 100644 tests/unit/formatters/test_yaml.py
|
| |
-
|
| |
- diff --git a/README.rst b/README.rst
|
| |
- index 61a5e7b..b7f4ba9 100644
|
| |
- --- a/README.rst
|
| |
- +++ b/README.rst
|
| |
- @@ -87,8 +87,8 @@ Usage::
|
| |
- $ bandit -h
|
| |
- usage: bandit [-h] [-r] [-a {file,vuln}] [-n CONTEXT_LINES] [-c CONFIG_FILE]
|
| |
- [-p PROFILE] [-t TESTS] [-s SKIPS] [-l] [-i]
|
| |
- - [-f {csv,html,json,screen,txt,xml}] [-o [OUTPUT_FILE]] [-v] [-d]
|
| |
- - [--ignore-nosec] [-x EXCLUDED_PATHS] [-b BASELINE]
|
| |
- + [-f {csv,html,json,screen,txt,xml,yaml}] [-o [OUTPUT_FILE]] [-v]
|
| |
- + [-d] [--ignore-nosec] [-x EXCLUDED_PATHS] [-b BASELINE]
|
| |
- [--ini INI_PATH] [--version]
|
| |
- targets [targets ...]
|
| |
-
|
| |
- @@ -118,7 +118,7 @@ Usage::
|
| |
- (-l for LOW, -ll for MEDIUM, -lll for HIGH)
|
| |
- -i, --confidence report only issues of a given confidence level or
|
| |
- higher (-i for LOW, -ii for MEDIUM, -iii for HIGH)
|
| |
- - -f {csv,html,json,screen,txt,xml}, --format {csv,html,json,screen,txt,xml}
|
| |
- + -f {csv,html,json,screen,txt,xml,yaml}, --format {csv,html,json,screen,txt,xml,yaml}
|
| |
- specify output format
|
| |
- -o [OUTPUT_FILE], --output [OUTPUT_FILE]
|
| |
- write report to filename
|
| |
- diff --git a/bandit/formatters/yaml.py b/bandit/formatters/yaml.py
|
| |
- new file mode 100644
|
| |
- index 0000000..69aeedb
|
| |
- --- /dev/null
|
| |
- +++ b/bandit/formatters/yaml.py
|
| |
- @@ -0,0 +1,92 @@
|
| |
- +# Copyright (c) 2017 VMware, Inc.
|
| |
- +#
|
| |
- +# 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.
|
| |
- +
|
| |
- +r"""
|
| |
- +==============
|
| |
- +YAML Formatter
|
| |
- +==============
|
| |
- +
|
| |
- +This formatter outputs the issues in a yaml format.
|
| |
- +
|
| |
- +:Example:
|
| |
- +
|
| |
- +.. code-block:: none
|
| |
- +
|
| |
- + filename,test_name,test_id,issue_severity,issue_confidence,issue_text,
|
| |
- + line_number,line_range
|
| |
- + examples/yaml_load.py,blacklist_calls,B301,MEDIUM,HIGH,"Use of unsafe yaml
|
| |
- + load. Allows instantiation of arbitrary objects. Consider yaml.safe_load().
|
| |
- + ",5,[5]
|
| |
- +
|
| |
- +.. versionadded:: 1.4.1
|
| |
- +
|
| |
- +"""
|
| |
- +# Necessary for this formatter to work when imported on Python 2. Importing
|
| |
- +# the standard library's yaml module conflicts with the name of this module.
|
| |
- +from __future__ import absolute_import
|
| |
- +
|
| |
- +import datetime
|
| |
- +import logging
|
| |
- +import operator
|
| |
- +import sys
|
| |
- +
|
| |
- +import yaml
|
| |
- +
|
| |
- +LOG = logging.getLogger(__name__)
|
| |
- +
|
| |
- +
|
| |
- +def report(manager, fileobj, sev_level, conf_level, lines=-1):
|
| |
- + '''Prints issues in YAML format
|
| |
- +
|
| |
- + :param manager: the bandit manager object
|
| |
- + :param fileobj: The output file object, which may be sys.stdout
|
| |
- + :param sev_level: Filtering severity level
|
| |
- + :param conf_level: Filtering confidence level
|
| |
- + :param lines: Number of lines to report, -1 for all
|
| |
- + '''
|
| |
- +
|
| |
- + machine_output = {'results': [], 'errors': []}
|
| |
- + for (fname, reason) in manager.get_skipped():
|
| |
- + machine_output['errors'].append({'filename': fname, 'reason': reason})
|
| |
- +
|
| |
- + results = manager.get_issue_list(sev_level=sev_level,
|
| |
- + conf_level=conf_level)
|
| |
- +
|
| |
- + collector = [r.as_dict() for r in results]
|
| |
- +
|
| |
- + itemgetter = operator.itemgetter
|
| |
- + if manager.agg_type == 'vuln':
|
| |
- + machine_output['results'] = sorted(collector,
|
| |
- + key=itemgetter('test_name'))
|
| |
- + else:
|
| |
- + machine_output['results'] = sorted(collector,
|
| |
- + key=itemgetter('filename'))
|
| |
- +
|
| |
- + machine_output['metrics'] = manager.metrics.data
|
| |
- +
|
| |
- + for result in machine_output['results']:
|
| |
- + if 'code' in result:
|
| |
- + code = result['code'].replace('\n', '\\n')
|
| |
- + result['code'] = code
|
| |
- +
|
| |
- + # timezone agnostic format
|
| |
- + TS_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
|
| |
- +
|
| |
- + time_string = datetime.datetime.utcnow().strftime(TS_FORMAT)
|
| |
- + machine_output['generated_at'] = time_string
|
| |
- +
|
| |
- + yaml.safe_dump(machine_output, fileobj, default_flow_style=False)
|
| |
- +
|
| |
- + if fileobj.name != sys.stdout.name:
|
| |
- + LOG.info("YAML output written to file: %s", fileobj.name)
|
| |
- diff --git a/doc/source/formatters/yaml.rst b/doc/source/formatters/yaml.rst
|
| |
- new file mode 100644
|
| |
- index 0000000..020feae
|
| |
- --- /dev/null
|
| |
- +++ b/doc/source/formatters/yaml.rst
|
| |
- @@ -0,0 +1,5 @@
|
| |
- +----
|
| |
- +yaml
|
| |
- +----
|
| |
- +
|
| |
- +.. automodule:: bandit.formatters.yaml
|
| |
- diff --git a/doc/source/man/bandit.rst b/doc/source/man/bandit.rst
|
| |
- index be0b993..04a3a43 100644
|
| |
- --- a/doc/source/man/bandit.rst
|
| |
- +++ b/doc/source/man/bandit.rst
|
| |
- @@ -7,8 +7,8 @@ SYNOPSIS
|
| |
-
|
| |
- bandit [-h] [-r] [-a {file,vuln}] [-n CONTEXT_LINES] [-c CONFIG_FILE]
|
| |
- [-p PROFILE] [-t TESTS] [-s SKIPS] [-l] [-i]
|
| |
- - [-f {csv,html,json,screen,txt,xml}] [-o OUTPUT_FILE] [-v] [-d]
|
| |
- - [--ignore-nosec] [-x EXCLUDED_PATHS] [-b BASELINE]
|
| |
- + [-f {csv,html,json,screen,txt,xml,yaml}] [-o OUTPUT_FILE] [-v]
|
| |
- + [-d] [--ignore-nosec] [-x EXCLUDED_PATHS] [-b BASELINE]
|
| |
- [--ini INI_PATH] [--version]
|
| |
- targets [targets ...]
|
| |
-
|
| |
- @@ -43,7 +43,7 @@ OPTIONS
|
| |
- (-l for LOW, -ll for MEDIUM, -lll for HIGH)
|
| |
- -i, --confidence report only issues of a given confidence level or
|
| |
- higher (-i for LOW, -ii for MEDIUM, -iii for HIGH)
|
| |
- - -f {csv,html,json,screen,txt,xml}, --format {csv,html,json,screen,txt,xml}
|
| |
- + -f {csv,html,json,screen,txt,xml,yaml}, --format {csv,html,json,screen,txt,xml,yaml}
|
| |
- specify output format
|
| |
- -o OUTPUT_FILE, --output OUTPUT_FILE
|
| |
- write report to filename
|
| |
- diff --git a/setup.cfg b/setup.cfg
|
| |
- index ac21b7e..cb3aad6 100644
|
| |
- --- a/setup.cfg
|
| |
- +++ b/setup.cfg
|
| |
- @@ -36,6 +36,7 @@ bandit.formatters =
|
| |
- xml = bandit.formatters.xml:report
|
| |
- html = bandit.formatters.html:report
|
| |
- screen = bandit.formatters.screen:report
|
| |
- + yaml = bandit.formatters.yaml:report
|
| |
- bandit.plugins =
|
| |
- # bandit/plugins/app_debug.py
|
| |
- flask_debug_true = bandit.plugins.app_debug:flask_debug_true
|
| |
- diff --git a/tests/unit/formatters/test_yaml.py b/tests/unit/formatters/test_yaml.py
|
| |
- new file mode 100644
|
| |
- index 0000000..348066d
|
| |
- --- /dev/null
|
| |
- +++ b/tests/unit/formatters/test_yaml.py
|
| |
- @@ -0,0 +1,96 @@
|
| |
- +# Copyright (c) 2017 VMware, Inc.
|
| |
- +#
|
| |
- +# 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 collections
|
| |
- +import tempfile
|
| |
- +
|
| |
- +import mock
|
| |
- +import testtools
|
| |
- +import yaml
|
| |
- +
|
| |
- +import bandit
|
| |
- +from bandit.core import config
|
| |
- +from bandit.core import constants
|
| |
- +from bandit.core import issue
|
| |
- +from bandit.core import manager
|
| |
- +from bandit.core import metrics
|
| |
- +from bandit.formatters import json as b_json
|
| |
- +
|
| |
- +
|
| |
- +class JsonFormatterTests(testtools.TestCase):
|
| |
- +
|
| |
- + def setUp(self):
|
| |
- + super(JsonFormatterTests, self).setUp()
|
| |
- + conf = config.BanditConfig()
|
| |
- + self.manager = manager.BanditManager(conf, 'file')
|
| |
- + (tmp_fd, self.tmp_fname) = tempfile.mkstemp()
|
| |
- + self.context = {'filename': self.tmp_fname,
|
| |
- + 'lineno': 4,
|
| |
- + 'linerange': [4]}
|
| |
- + self.check_name = 'hardcoded_bind_all_interfaces'
|
| |
- + self.issue = issue.Issue(bandit.MEDIUM, bandit.MEDIUM,
|
| |
- + 'Possible binding to all interfaces.')
|
| |
- +
|
| |
- + self.candidates = [issue.Issue(bandit.LOW, bandit.LOW, 'Candidate A',
|
| |
- + lineno=1),
|
| |
- + issue.Issue(bandit.HIGH, bandit.HIGH, 'Candiate B',
|
| |
- + lineno=2)]
|
| |
- +
|
| |
- + self.manager.out_file = self.tmp_fname
|
| |
- +
|
| |
- + self.issue.fname = self.context['filename']
|
| |
- + self.issue.lineno = self.context['lineno']
|
| |
- + self.issue.linerange = self.context['linerange']
|
| |
- + self.issue.test = self.check_name
|
| |
- +
|
| |
- + self.manager.results.append(self.issue)
|
| |
- + self.manager.metrics = metrics.Metrics()
|
| |
- +
|
| |
- + # mock up the metrics
|
| |
- + for key in ['_totals', 'binding.py']:
|
| |
- + self.manager.metrics.data[key] = {'loc': 4, 'nosec': 2}
|
| |
- + for (criteria, default) in constants.CRITERIA:
|
| |
- + for rank in constants.RANKING:
|
| |
- + self.manager.metrics.data[key]['{0}.{1}'.format(
|
| |
- + criteria, rank
|
| |
- + )] = 0
|
| |
- +
|
| |
- + @mock.patch('bandit.core.manager.BanditManager.get_issue_list')
|
| |
- + def test_report(self, get_issue_list):
|
| |
- + self.manager.files_list = ['binding.py']
|
| |
- + self.manager.scores = [{'SEVERITY': [0] * len(constants.RANKING),
|
| |
- + 'CONFIDENCE': [0] * len(constants.RANKING)}]
|
| |
- +
|
| |
- + get_issue_list.return_value = collections.OrderedDict(
|
| |
- + [(self.issue, self.candidates)])
|
| |
- +
|
| |
- + tmp_file = open(self.tmp_fname, 'w')
|
| |
- + b_json.report(self.manager, tmp_file, self.issue.severity,
|
| |
- + self.issue.confidence)
|
| |
- +
|
| |
- + with open(self.tmp_fname) as f:
|
| |
- + data = yaml.load(f.read())
|
| |
- + self.assertIsNotNone(data['generated_at'])
|
| |
- + self.assertEqual(self.tmp_fname, data['results'][0]['filename'])
|
| |
- + self.assertEqual(self.issue.severity,
|
| |
- + data['results'][0]['issue_severity'])
|
| |
- + self.assertEqual(self.issue.confidence,
|
| |
- + data['results'][0]['issue_confidence'])
|
| |
- + self.assertEqual(self.issue.text, data['results'][0]['issue_text'])
|
| |
- + self.assertEqual(self.context['lineno'],
|
| |
- + data['results'][0]['line_number'])
|
| |
- + self.assertEqual(self.context['linerange'],
|
| |
- + data['results'][0]['line_range'])
|
| |
- + self.assertEqual(self.check_name, data['results'][0]['test_name'])
|
| |
- + self.assertIn('candidates', data['results'][0])
|
| |
- --
|
| |
- 2.13.6
|
| |
-
|
| |
1.6.2 fixes issues with Python-3.8