Repository: PyCQA/bandit (python)

Issue:
When using -n and -f (json) flags, -n flag is ignored
**Describe the bug**
When using Bandit and adding -n (context lines number flags) and -f (output format) json flag, -n flag is ignored and the returned result always contain 3 lines of code (the default).

**To Reproduce**
Steps to reproduce the behavior:
Take a python file with few lines of code and run bandit against it. I ran test B605. 
My python file cotent:
```
import os
for s in ["1","2","3","4","5"]:
    command = "echo " + s
    print "Calling command: [", command, "]"
    b = []
    os.system(command)
    print 'test1'
    print 'test2'
```

The command I ran with -f flag: 
`python -m bandit test.py -t B605 -n 1 -f json`
Output: `"code": "5     b = []\n6     os.system(command)\n7     print 'test1'\n"`
**3** lines of code are returned although -n 1 flag was added.


**Expected behavior**
A clear and concise description of what you expected to happen.
`python -m bandit test.py -t B605 -n 1` (without -f flag)
As expected you'll get **1 line of code for each issue**:
`6	                os.system(command)`


**Bandit version**
<!--- Paste the output from "bandit --version" between quotes below -->
```
__main__.py 1.6.2
  python version = 2.7.17 (default, Nov  7 2019, 10:07:09) [GCC 7.4.0]

```

**Additional context**
Two photos are attached:
With -f flag:
![image](https://user-images.githubusercontent.com/50482935/79974264-aa4d1180-84a1-11ea-8f91-ad981d4d50cd.png)

Without -f flag:
![image](https://user-images.githubusercontent.com/50482935/79974300-bdf87800-84a1-11ea-817d-50a672c7d2e1.png)



Pull request:
Fix json and yaml formatters to respect num lines
The json and yaml formatters were not outputting the lines of
content according to the user's selection.

This is a result of issue:as_dict() function that does not pass
through the max_lines argument to get_code. As a result, it
would always return the default of 3.

This change now optionally passes through desired lines of content.

Fixes #604

Signed-off-by: Eric Brown <eric_wade_brown@yahoo.com>

Patch:
diff --git a/bandit/blacklists/calls.py b/bandit/blacklists/calls.py
index 977dbcd..37ad101 100644
--- a/bandit/blacklists/calls.py
+++ b/bandit/blacklists/calls.py
@@ -24,9 +24,6 @@ deserialize untrusted data, possible security issue.
 | B301 | pickle              | - pickle.loads                     | Medium    |
 |      |                     | - pickle.load                      |           |
 |      |                     | - pickle.Unpickler                 |           |
-|      |                     | - cPickle.loads                    |           |
-|      |                     | - cPickle.load                     |           |
-|      |                     | - cPickle.Unpickler                |           |
 |      |                     | - dill.loads                       |           |
 |      |                     | - dill.load                        |           |
 |      |                     | - dill.Unpickler                   |           |
@@ -348,9 +345,6 @@ def gen_blacklist():
                 "pickle.loads",
                 "pickle.load",
                 "pickle.Unpickler",
-                "cPickle.loads",
-                "cPickle.load",
-                "cPickle.Unpickler",
                 "dill.loads",
                 "dill.load",
                 "dill.Unpickler",
diff --git a/bandit/core/issue.py b/bandit/core/issue.py
index 553cda6..875e5e4 100644
--- a/bandit/core/issue.py
+++ b/bandit/core/issue.py
@@ -196,7 +196,7 @@ class Issue:
             lines.append(tmplt % (line, text))
         return "".join(lines)
 
-    def as_dict(self, with_code=True):
+    def as_dict(self, with_code=True, max_lines=3):
         """Convert the issue to a dict of values for outputting."""
         out = {
             "filename": self.fname,
@@ -213,7 +213,7 @@ class Issue:
         }
 
         if with_code:
-            out["code"] = self.get_code()
+            out["code"] = self.get_code(max_lines=max_lines)
         return out
 
     def from_dict(self, data, with_code=True):
diff --git a/bandit/core/manager.py b/bandit/core/manager.py
index fe20a2d..e82f671 100644
--- a/bandit/core/manager.py
+++ b/bandit/core/manager.py
@@ -331,12 +331,12 @@ class BanditManager:
             new_files_list.remove(fname)
         except Exception as e:
             LOG.error(
-                "Exception occurred when executing tests against %s.",
-                fname,
-                fname,
+                "Exception occurred when executing tests against %s.", fname
             )
             if not LOG.isEnabledFor(logging.DEBUG):
-                LOG.error('Run "bandit --debug %s" to see the full traceback.')
+                LOG.error(
+                    'Run "bandit --debug %s" to see the full traceback.', fname
+                )
 
             self.skipped.append((fname, "exception while scanning file"))
             new_files_list.remove(fname)
diff --git a/bandit/formatters/json.py b/bandit/formatters/json.py
index 9926382..eeb7f92 100644
--- a/bandit/formatters/json.py
+++ b/bandit/formatters/json.py
@@ -111,14 +111,16 @@ def report(manager, fileobj, sev_level, conf_level, lines=-1):
     if baseline:
         collector = []
         for r in results:
-            d = r.as_dict()
+            d = r.as_dict(max_lines=lines)
             d["more_info"] = docs_utils.get_url(d["test_id"])
             if len(results[r]) > 1:
-                d["candidates"] = [c.as_dict() for c in results[r]]
+                d["candidates"] = [
+                    c.as_dict(max_lines=lines) for c in results[r]
+                ]
             collector.append(d)
 
     else:
-        collector = [r.as_dict() for r in results]
+        collector = [r.as_dict(max_lines=lines) for r in results]
         for elem in collector:
             elem["more_info"] = docs_utils.get_url(elem["test_id"])
 
diff --git a/bandit/formatters/yaml.py b/bandit/formatters/yaml.py
index b9b0a44..e8a848e 100644
--- a/bandit/formatters/yaml.py
+++ b/bandit/formatters/yaml.py
@@ -91,7 +91,7 @@ def report(manager, fileobj, sev_level, conf_level, lines=-1):
         sev_level=sev_level, conf_level=conf_level
     )
 
-    collector = [r.as_dict() for r in results]
+    collector = [r.as_dict(max_lines=lines) for r in results]
     for elem in collector:
         elem["more_info"] = docs_utils.get_url(elem["test_id"])
 
diff --git a/bandit/plugins/exec.py b/bandit/plugins/exec.py
index 3c309d5..42354db 100644
--- a/bandit/plugins/exec.py
+++ b/bandit/plugins/exec.py
@@ -23,7 +23,6 @@ Python docs succinctly describe why the use of `exec` is risky.
 
 .. seealso::
 
- - https://docs.python.org/2/reference/simple_stmts.html#exec
  - https://docs.python.org/3/library/functions.html#exec
  - https://www.python.org/dev/peps/pep-0551/#background
  - https://www.python.org/dev/peps/pep-0578/#suggested-audit-hook-locations
diff --git a/bandit/plugins/insecure_ssl_tls.py b/bandit/plugins/insecure_ssl_tls.py
index 80f2e5c..319abcf 100644
--- a/bandit/plugins/insecure_ssl_tls.py
+++ b/bandit/plugins/insecure_ssl_tls.py
@@ -22,6 +22,8 @@ def gen_config(name):
                 "PROTOCOL_TLSv1",  # strict option
                 "SSLv3_METHOD",  # strict option
                 "TLSv1_METHOD",
+                "PROTOCOL_TLSv1_1",
+                "TLSv1_1_METHOD",
             ]
         }  # strict option
 
@@ -106,6 +108,9 @@ def ssl_with_bad_version(context, config):
     .. versionchanged:: 1.7.3
         CWE information added
 
+    .. versionchanged:: 1.7.5
+        Added TLS 1.1
+
     """
     bad_ssl_versions = get_bad_proto_versions(config)
     if context.call_function_name_qual == "ssl.wrap_socket":
@@ -196,6 +201,9 @@ def ssl_with_bad_defaults(context, config):
     .. versionchanged:: 1.7.3
         CWE information added
 
+    .. versionchanged:: 1.7.5
+        Added TLS 1.1
+
     """
 
     bad_ssl_versions = get_bad_proto_versions(config)
diff --git a/bandit/plugins/jinja2_templates.py b/bandit/plugins/jinja2_templates.py
index f0b23e0..667b79d 100644
--- a/bandit/plugins/jinja2_templates.py
+++ b/bandit/plugins/jinja2_templates.py
@@ -104,9 +104,10 @@ def jinja2_autoescape_false(context):
                         ):
                             return
                         # Check if select_autoescape function is used.
-                        elif (
-                            isinstance(value, ast.Call)
-                            and getattr(value.func, "id", None)
+                        elif isinstance(value, ast.Call) and (
+                            getattr(value.func, "attr", None)
+                            == "select_autoescape"
+                            or getattr(value.func, "id", None)
                             == "select_autoescape"
                         ):
                             return
diff --git a/bandit/plugins/tarfile_unsafe_members.py b/bandit/plugins/tarfile_unsafe_members.py
new file mode 100644
index 0000000..32c1e61
--- /dev/null
+++ b/bandit/plugins/tarfile_unsafe_members.py
@@ -0,0 +1,109 @@
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+r"""
+=================================
+B202: Test for tarfile.extractall
+=================================
+
+This plugin will look for usage of ``tarfile.extractall()``
+
+Severity are set as follows:
+
+* ``tarfile.extractalll(members=function(tarfile))`` - LOW
+* ``tarfile.extractalll(members=?)`` - member is not a function - MEDIUM
+* ``tarfile.extractall()`` - members from the archive is trusted - HIGH
+
+Use ``tarfile.extractall(members=function_name)`` and define a function
+that will inspect each member. Discard files that contain a directory
+traversal sequences such as ``../`` or ``\..`` along with all special filetypes
+unless you explicitly need them.
+
+:Example:
+
+.. code-block:: none
+
+    >> Issue: [B202:tarfile_unsafe_members] tarfile.extractall used without
+    any validation. You should check members and discard dangerous ones
+    Severity: High   Confidence: High
+    CWE: CWE-22 (https://cwe.mitre.org/data/definitions/22.html)
+    Location: examples/tarfile_extractall.py:8
+    More Info:
+    https://bandit.readthedocs.io/en/latest/plugins/b202_tarfile_unsafe_members.html
+    7	    tar = tarfile.open(filename)
+    8	    tar.extractall(path=tempfile.mkdtemp())
+    9	    tar.close()
+
+
+.. seealso::
+
+ - https://docs.python.org/3/library/tarfile.html#tarfile.TarFile.extractall
+ - https://docs.python.org/3/library/tarfile.html#tarfile.TarInfo
+
+.. versionadded:: 1.7.5
+
+"""
+import ast
+
+import bandit
+from bandit.core import issue
+from bandit.core import test_properties as test
+
+
+def exec_issue(level, members=""):
+    if level == bandit.LOW:
+        return bandit.Issue(
+            severity=bandit.LOW,
+            confidence=bandit.LOW,
+            cwe=issue.Cwe.PATH_TRAVERSAL,
+            text="Usage of tarfile.extractall(members=function(tarfile)). "
+            "Make sure your function properly discards dangerous members "
+            "{members}).".format(members=members),
+        )
+    elif level == bandit.MEDIUM:
+        return bandit.Issue(
+            severity=bandit.MEDIUM,
+            confidence=bandit.MEDIUM,
+            cwe=issue.Cwe.PATH_TRAVERSAL,
+            text="Found tarfile.extractall(members=?) but couldn't "
+            "identify the type of members. "
+            "Check if the members were properly validated "
+            "{members}).".format(members=members),
+        )
+    else:
+        return bandit.Issue(
+            severity=bandit.HIGH,
+            confidence=bandit.HIGH,
+            cwe=issue.Cwe.PATH_TRAVERSAL,
+            text="tarfile.extractall used without any validation. "
+            "Please check and discard dangerous members.",
+        )
+
+
+def get_members_value(context):
+    for keyword in context.node.keywords:
+        if keyword.arg == "members":
+            arg = keyword.value
+            if isinstance(arg, ast.Call):
+                return {"Function": arg.func.id}
+            else:
+                value = arg.id if isinstance(arg, ast.Name) else arg
+                return {"Other": value}
+
+
+@test.test_id("B202")
+@test.checks("Call")
+def tarfile_unsafe_members(context):
+    if all(
+        [
+            context.is_module_imported_exact("tarfile"),
+            "extractall" in context.call_function_name,
+        ]
+    ):
+        if "members" in context.call_keywords:
+            members = get_members_value(context)
+            if "Function" in members:
+                return exec_issue(bandit.LOW, members)
+            else:
+                return exec_issue(bandit.MEDIUM, members)
+        return exec_issue(bandit.HIGH)
diff --git a/bandit/plugins/weak_cryptographic_key.py b/bandit/plugins/weak_cryptographic_key.py
index db91cb2..da73ced 100644
--- a/bandit/plugins/weak_cryptographic_key.py
+++ b/bandit/plugins/weak_cryptographic_key.py
@@ -131,9 +131,9 @@ def _weak_crypto_key_size_cryptography_io(context, config):
             "SECT163K1": 163,
             "SECT163R2": 163,
         }
-        curve = (
-            context.get_call_arg_value("curve")
-            or context.call_args[arg_position[key_type]]
+        curve = context.get_call_arg_value("curve") or (
+            len(context.call_args) > arg_position[key_type]
+            and 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(config, key_type, key_size)
diff --git a/doc/source/plugins/b202_tarfile_unsafe_members.rst b/doc/source/plugins/b202_tarfile_unsafe_members.rst
new file mode 100644
index 0000000..3b99055
--- /dev/null
+++ b/doc/source/plugins/b202_tarfile_unsafe_members.rst
@@ -0,0 +1,5 @@
+----------------------------
+B202: tarfile_unsafe_members
+----------------------------
+
+.. automodule:: bandit.plugins.tarfile_unsafe_members
diff --git a/examples/dill.py b/examples/dill.py
index fbb9111..beb1e57 100644
--- a/examples/dill.py
+++ b/examples/dill.py
@@ -1,14 +1,14 @@
 import dill
-import StringIO
+import io
 
 # dill
 pick = dill.dumps({'a': 'b', 'c': 'd'})
 print(dill.loads(pick))
 
-file_obj = StringIO.StringIO()
+file_obj = io.BytesIO()
 dill.dump([1, 2, '3'], file_obj)
 file_obj.seek(0)
 print(dill.load(file_obj))
 
 file_obj.seek(0)
-print(dill.Undillr(file_obj).load())
+print(dill.Unpickler(file_obj).load())
diff --git a/examples/jinja2_templating.py b/examples/jinja2_templating.py
index d5aaa2d..0dce14d 100644
--- a/examples/jinja2_templating.py
+++ b/examples/jinja2_templating.py
@@ -20,6 +20,9 @@ Environment(loader=templateLoader, autoescape=select_autoescape())
 Environment(loader=templateLoader,
             autoescape=select_autoescape(['html', 'htm', 'xml']))
 
+Environment(loader=templateLoader,
+            autoescape=jinja2.select_autoescape(['html', 'htm', 'xml']))
+
 
 def fake_func():
     return 'foobar'
diff --git a/examples/pickle_deserialize.py b/examples/pickle_deserialize.py
index 4c405bb..3c56c80 100644
--- a/examples/pickle_deserialize.py
+++ b/examples/pickle_deserialize.py
@@ -1,29 +1,15 @@
-import cPickle
+import io
 import pickle
-import StringIO
 
 
 # pickle
 pick = pickle.dumps({'a': 'b', 'c': 'd'})
 print(pickle.loads(pick))
 
-file_obj = StringIO.StringIO()
+file_obj = io.BytesIO()
 pickle.dump([1, 2, '3'], file_obj)
 file_obj.seek(0)
 print(pickle.load(file_obj))
 
 file_obj.seek(0)
 print(pickle.Unpickler(file_obj).load())
-
-# cPickle
-serialized = cPickle.dumps({(): []})
-print(cPickle.loads(serialized))
-
-file_obj = StringIO.StringIO()
-cPickle.dump((1,), file_obj)
-file_obj.seek(0)
-print(cPickle.load(file_obj))
-
-file_obj.seek(0)
-print(cPickle.Unpickler(file_obj).load())
-
diff --git a/examples/ssl-insecure-version.py b/examples/ssl-insecure-version.py
index bd6f659..80d6e48 100644
--- a/examples/ssl-insecure-version.py
+++ b/examples/ssl-insecure-version.py
@@ -20,6 +20,13 @@ herp_derp(ssl_version=ssl.PROTOCOL_TLSv1)
 herp_derp(method=SSL.SSLv3_METHOD)
 herp_derp(method=SSL.TLSv1_METHOD)
 
+ssl.wrap_socket(ssl_version=ssl.PROTOCOL_TLSv1_1)
+SSL.Context(method=SSL.TLSv1_1_METHOD)
+
+herp_derp(ssl_version=ssl.PROTOCOL_TLSv1_1)
+herp_derp(method=SSL.TLSv1_1_METHOD)
+
+
 ssl.wrap_socket()
 
 def open_ssl_socket(version=ssl.PROTOCOL_SSLv2):
@@ -31,6 +38,9 @@ def open_ssl_socket(version=SSL.SSLv2_METHOD):
 def open_ssl_socket(version=SSL.SSLv23_METHOD):
     pass
 
-# this one will pass ok
 def open_ssl_socket(version=SSL.TLSv1_1_METHOD):
     pass
+
+# this one will pass ok
+def open_ssl_socket(version=SSL.TLSv1_2_METHOD):
+    pass
diff --git a/examples/tarfile_extractall.py b/examples/tarfile_extractall.py
new file mode 100644
index 0000000..2af3eb5
--- /dev/null
+++ b/examples/tarfile_extractall.py
@@ -0,0 +1,47 @@
+import sys
+import tarfile
+import tempfile
+
+
+def unsafe_archive_handler(filename):
+    tar = tarfile.open(filename)
+    tar.extractall(path=tempfile.mkdtemp())
+    tar.close()
+
+
+def managed_members_archive_handler(filename):
+    tar = tarfile.open(filename)
+    tar.extractall(path=tempfile.mkdtemp(), members=members_filter(tar))
+    tar.close()
+
+
+def list_members_archive_handler(filename):
+    tar = tarfile.open(filename)
+    tar.extractall(path=tempfile.mkdtemp(), members=[])
+    tar.close()
+
+
+def provided_members_archive_handler(filename):
+    tar = tarfile.open(filename)
+    tarfile.extractall(path=tempfile.mkdtemp(), members=tar)
+    tar.close()
+
+
+def members_filter(tarfile):
+    result = []
+    for member in tarfile.getmembers():
+        if '../' in member.name:
+            print('Member name container directory traversal sequence')
+            continue
+        elif (member.issym() or member.islnk()) and ('../' in member.linkname):
+            print('Symlink to external resource')
+            continue
+        result.append(member)
+    return result
+
+
+if __name__ == "__main__":
+    if len(sys.argv) > 1:
+        filename = sys.argv[1]
+        unsafe_archive_handler(filename)
+        managed_members_archive_handler(filename)
diff --git a/examples/weak_cryptographic_key_sizes.py b/examples/weak_cryptographic_key_sizes.py
index f2443b5..357a71e 100644
--- a/examples/weak_cryptographic_key_sizes.py
+++ b/examples/weak_cryptographic_key_sizes.py
@@ -64,3 +64,9 @@ pycryptodomex_rsa.generate(512)
 rsa.generate_private_key(public_exponent=65537,
                          key_size=some_key_size,
                          backend=backends.default_backend())
+
+# Can't reliably know which curve was passed, in some cases like below
+ec.generate_private_key(
+    curve=curves[self.curve]['create'](self.size),
+    backend=backends.default_backend()
+)
diff --git a/setup.cfg b/setup.cfg
index 66f6b68..dcf8663 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -140,6 +140,9 @@ bandit.plugins =
     # bandit/plugins/logging_config_insecure_listen.py
     logging_config_insecure_listen = bandit.plugins.logging_config_insecure_listen:logging_config_insecure_listen
 
+    #bandit/plugins/tarfile_unsafe_members.py
+    tarfile_unsafe_members = bandit.plugins.tarfile_unsafe_members:tarfile_unsafe_members
+
 [build_sphinx]
 all_files = 1
 build-dir = doc/build


Test patch:
diff --git a/tests/functional/test_functional.py b/tests/functional/test_functional.py
index 16d07c0..4c115fa 100644
--- a/tests/functional/test_functional.py
+++ b/tests/functional/test_functional.py
@@ -348,16 +348,16 @@ class FunctionalTests(testtools.TestCase):
     def test_pickle(self):
         """Test for the `pickle` module."""
         expect = {
-            "SEVERITY": {"UNDEFINED": 0, "LOW": 2, "MEDIUM": 6, "HIGH": 0},
-            "CONFIDENCE": {"UNDEFINED": 0, "LOW": 0, "MEDIUM": 0, "HIGH": 8},
+            "SEVERITY": {"UNDEFINED": 0, "LOW": 1, "MEDIUM": 3, "HIGH": 0},
+            "CONFIDENCE": {"UNDEFINED": 0, "LOW": 0, "MEDIUM": 0, "HIGH": 4},
         }
         self.check_example("pickle_deserialize.py", expect)
 
     def test_dill(self):
         """Test for the `dill` module."""
         expect = {
-            "SEVERITY": {"UNDEFINED": 0, "LOW": 1, "MEDIUM": 2, "HIGH": 0},
-            "CONFIDENCE": {"UNDEFINED": 0, "LOW": 0, "MEDIUM": 0, "HIGH": 3},
+            "SEVERITY": {"UNDEFINED": 0, "LOW": 1, "MEDIUM": 3, "HIGH": 0},
+            "CONFIDENCE": {"UNDEFINED": 0, "LOW": 0, "MEDIUM": 0, "HIGH": 4},
         }
         self.check_example("dill.py", expect)
 
@@ -454,12 +454,8 @@ class FunctionalTests(testtools.TestCase):
     def test_ssl_insecure_version(self):
         """Test for insecure SSL protocol versions."""
         expect = {
-            "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},
+            "SEVERITY": {"UNDEFINED": 0, "LOW": 1, "MEDIUM": 13, "HIGH": 9},
+            "CONFIDENCE": {"UNDEFINED": 0, "LOW": 0, "MEDIUM": 14, "HIGH": 9},
         }
         self.check_example("ssl-insecure-version.py", expect)
 
@@ -904,3 +900,11 @@ class FunctionalTests(testtools.TestCase):
             "CONFIDENCE": {"UNDEFINED": 0, "LOW": 0, "MEDIUM": 0, "HIGH": 3},
         }
         self.check_example("snmp.py", expect)
+
+    def test_tarfile_unsafe_members(self):
+        """Test insecure usage of tarfile."""
+        expect = {
+            "SEVERITY": {"UNDEFINED": 0, "LOW": 1, "MEDIUM": 2, "HIGH": 1},
+            "CONFIDENCE": {"UNDEFINED": 0, "LOW": 1, "MEDIUM": 2, "HIGH": 1},
+        }
+        self.check_example("tarfile_extractall.py", expect)