Repository: PyCQA/bandit (python)

Issue:
ssh_no_host_key_verification is failing on Python 3.12
### Describe the bug

Using bandit on Python 3.12 with enabled `ssh_no_host_key_verification` plugin leads to internal error:

```python
from paramiko import SSHClient, WarningPolicy

client = SSHClient()

client.set_missing_host_key_policy(WarningPolicy())
```

```
[main]  INFO    profile include tests: None
[main]  INFO    profile exclude tests: None
[main]  INFO    cli include tests: None
[main]  INFO    cli exclude tests: None
[main]  INFO    running on Python 3.12.0
[node_visitor]  WARNING Unable to find qualified name for module: 1.py
[tester]        ERROR   Bandit internal error running: ssh_no_host_key_verification on file 1.py at line 5: 'Name' object has no attribute 'attr'Traceback (most recent call last):
  File "/home/maxim/Repo/onetl/venv2/lib/python3.12/site-packages/bandit/core/tester.py", line 53, in run_tests
    result = test(context)
             ^^^^^^^^^^^^^
  File "/home/maxim/Repo/onetl/venv2/lib/python3.12/site-packages/bandit/plugins/ssh_no_host_key_verification.py", line 59, in ssh_no_host_key_verification
    policy_argument_value = policy_argument.func.attr
                            ^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'Name' object has no attribute 'attr'
```

But changing module last line to this:
```python
client.set_missing_host_key_policy(client.WarningPolicy())
```
or this:
```python
client.set_missing_host_key_policy(client.WarningPolicy)
```

fixes the issue:
```
[main]  INFO    profile include tests: None
[main]  INFO    profile exclude tests: None
[main]  INFO    cli include tests: None
[main]  INFO    cli exclude tests: None
[main]  INFO    running on Python 3.12.0
[node_visitor]  WARNING Unable to find qualified name for module: 1.py
Run started:2023-12-12 11:47:08.155867

Test results:
>> Issue: [B507:ssh_no_host_key_verification] Paramiko call with policy set to automatically trust the unknown host key.
   Severity: High   Confidence: Medium
   CWE: CWE-295 (https://cwe.mitre.org/data/definitions/295.html)
   More Info: https://bandit.readthedocs.io/en/1.7.6/plugins/b507_ssh_no_host_key_verification.html
   Location: 1.py:5:0
4
5       client.set_missing_host_key_policy(client.WarningPolicy())
6
```
****

### Reproduction steps

```bash
pyenv install 3.12
pyenv shell 3.12
python -m venv venv
source venv/bin/activate
pip install bandit==1.7.6 paramiko==3.3.1
echo "file content from description above" > 1.py
bandit 1.py
```

### Expected behavior

Plugin `ssh_no_host_key_verification` should not fail on any Python version and any valid way of passing policy to `client.set_missing_host_key_policy(...)`

### Bandit version

1.7.6 (Default)

### Python version

3.12 (Default)

### Additional context

_No response_

Pull request:
Handle variant in how policy is passed in paramiko
Paramiko permits various ways of importing the missing host key policy. It allows paramiko.client.AutoAddPolicy or paramiko.AutoAddPolicy. The later isn't being handled in Bandit.

This change adds news tests and modifies the plugin to inspect the AST to determine whether the argument is an Attribute, Name, or Call.

Fixes #1077

Patch:
diff --git a/bandit/plugins/ssh_no_host_key_verification.py b/bandit/plugins/ssh_no_host_key_verification.py
index e8edaf9..51be2eb 100644
--- a/bandit/plugins/ssh_no_host_key_verification.py
+++ b/bandit/plugins/ssh_no_host_key_verification.py
@@ -55,8 +55,13 @@ def ssh_no_host_key_verification(context):
         policy_argument_value = None
         if isinstance(policy_argument, ast.Attribute):
             policy_argument_value = policy_argument.attr
+        elif isinstance(policy_argument, ast.Name):
+            policy_argument_value = policy_argument.id
         elif isinstance(policy_argument, ast.Call):
-            policy_argument_value = policy_argument.func.attr
+            if isinstance(policy_argument.func, ast.Attribute):
+                policy_argument_value = policy_argument.func.attr
+            elif isinstance(policy_argument.func, ast.Name):
+                policy_argument_value = policy_argument.func.id
 
         if policy_argument_value in ["AutoAddPolicy", "WarningPolicy"]:
             return bandit.Issue(
diff --git a/examples/no_host_key_verification.py b/examples/no_host_key_verification.py
index 2e092fe..1ac01e6 100644
--- a/examples/no_host_key_verification.py
+++ b/examples/no_host_key_verification.py
@@ -1,7 +1,14 @@
 from paramiko import client
+from paramiko import AutoAddPolicy
+from paramiko import WarningPolicy
 
 ssh_client = client.SSHClient()
 ssh_client.set_missing_host_key_policy(client.AutoAddPolicy)
 ssh_client.set_missing_host_key_policy(client.WarningPolicy)
 ssh_client.set_missing_host_key_policy(client.AutoAddPolicy())
 ssh_client.set_missing_host_key_policy(client.WarningPolicy())
+
+ssh_client.set_missing_host_key_policy(AutoAddPolicy)
+ssh_client.set_missing_host_key_policy(WarningPolicy)
+ssh_client.set_missing_host_key_policy(AutoAddPolicy())
+ssh_client.set_missing_host_key_policy(WarningPolicy())


Test patch:
diff --git a/tests/functional/test_functional.py b/tests/functional/test_functional.py
index 6917462..6bc047e 100644
--- a/tests/functional/test_functional.py
+++ b/tests/functional/test_functional.py
@@ -543,8 +543,8 @@ class FunctionalTests(testtools.TestCase):
     def test_host_key_verification(self):
         """Test for ignoring host key verification."""
         expect = {
-            "SEVERITY": {"UNDEFINED": 0, "LOW": 0, "MEDIUM": 0, "HIGH": 4},
-            "CONFIDENCE": {"UNDEFINED": 0, "LOW": 0, "MEDIUM": 4, "HIGH": 0},
+            "SEVERITY": {"UNDEFINED": 0, "LOW": 0, "MEDIUM": 0, "HIGH": 8},
+            "CONFIDENCE": {"UNDEFINED": 0, "LOW": 0, "MEDIUM": 8, "HIGH": 0},
         }
         self.check_example("no_host_key_verification.py", expect)