From 68859936db06b01ee69adbd30f43137ec4d843de Mon Sep 17 00:00:00 2001 From: mokashang Date: Wed, 13 May 2026 09:17:20 -0700 Subject: [PATCH] Detect empty-string wildcard host in B104 bind() calls B104 (hardcoded_bind_all_interfaces) previously flagged only the "0.0.0.0" string literal, but Python's socket API also treats the empty string "" as the wildcard host (INADDR_ANY for IPv4 and the all-interfaces address for IPv6 via getaddrinfo). Calls like ``s.bind(("", 31137))`` therefore had the same exposure as ``s.bind(("0.0.0.0", 31137))`` but were silently ignored, leaving CWE-605 cases undetected. Adding "" to the existing string-level check produces too many false positives, since "" appears as a benign literal almost everywhere. Instead, register the existing plugin for ``Call`` nodes as well and, when a ``Call`` is visited, only flag the case where the function name is ``bind`` and the first argument is an address tuple/list whose host element is "". This keeps the plugin scoped to the dangerous pattern and preserves the existing "0.0.0.0" detection unchanged. The ``binding.py`` example is extended with the new tuple and list forms plus benign empty-string literals that must not be flagged, and the functional test now expects three medium findings instead of one. Fixes #1395 --- bandit/plugins/general_bind_all_interfaces.py | 37 +++++++++++++++++-- examples/binding.py | 6 +++ tests/functional/test_functional.py | 12 ++++-- 3 files changed, 49 insertions(+), 6 deletions(-) diff --git a/bandit/plugins/general_bind_all_interfaces.py b/bandit/plugins/general_bind_all_interfaces.py index 58b840e86..080cc24d3 100644 --- a/bandit/plugins/general_bind_all_interfaces.py +++ b/bandit/plugins/general_bind_all_interfaces.py @@ -9,8 +9,10 @@ Binding to all network interfaces can potentially open up a service to traffic on unintended interfaces, that may not be properly documented or secured. This -plugin test looks for a string pattern "0.0.0.0" that may indicate a hardcoded -binding to all network interfaces. +plugin test looks for a string pattern ``"0.0.0.0"`` that may indicate a +hardcoded binding to all network interfaces, and also for ``bind`` calls whose +host argument is the empty string ``""`` -- which the Python socket API treats +as ``INADDR_ANY`` (equivalent to ``"0.0.0.0"``). :Example: @@ -23,27 +25,56 @@ 3 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 4 s.bind(('0.0.0.0', 31137)) 5 s.bind(('192.168.0.1', 8080)) + 6 s.bind(('', 31137)) .. seealso:: - https://nvd.nist.gov/vuln/detail/CVE-2018-1281 - https://cwe.mitre.org/data/definitions/605.html + - https://docs.python.org/3/library/socket.html#socket-families .. versionadded:: 0.9.0 .. versionchanged:: 1.7.3 CWE information added +.. versionchanged:: 1.9.5 + Detect ``bind(("", port))`` -- empty string resolves to ``INADDR_ANY``. + """ import bandit from bandit.core import issue from bandit.core import test_properties as test -@test.checks("Str") +def _is_empty_string_bind(context): + # The empty string is a Python-socket idiom for INADDR_ANY (it also acts + # as the IPv6 wildcard via getaddrinfo), and so has the same exposure as + # binding to "0.0.0.0". A bare "" literal is far too common to flag + # broadly without an unreasonable false-positive rate, so this check is + # scoped to calls named ``bind`` whose first argument is an address + # tuple/list with "" as its host element. + if context.call_function_name != "bind": + return False + if not context.call_args_count: + return False + address = context.get_call_arg_at_position(0) + if not isinstance(address, (tuple, list)) or not address: + return False + return address[0] == "" + + +@test.checks("Str", "Call") @test.test_id("B104") def hardcoded_bind_all_interfaces(context): + # context.string_val is populated only for Str nodes; for Call nodes the + # plugin dispatches on the call's function name and argument shape. if context.string_val == "0.0.0.0": # nosec: B104 + flagged = True + else: + flagged = _is_empty_string_bind(context) + + if flagged: return bandit.Issue( severity=bandit.MEDIUM, confidence=bandit.MEDIUM, diff --git a/examples/binding.py b/examples/binding.py index fee248702..ee07f0a81 100644 --- a/examples/binding.py +++ b/examples/binding.py @@ -3,3 +3,9 @@ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(('0.0.0.0', 31137)) s.bind(('192.168.0.1', 8080)) +s.bind(('', 31137)) # empty string == INADDR_ANY +s.bind(['', 31137]) # list form, also accepted by socket.bind + +# Plain empty-string literals outside of a ``bind`` call must not be flagged. +default_host = '' +host_options = ('localhost', 'example.com') diff --git a/tests/functional/test_functional.py b/tests/functional/test_functional.py index 08b1c5c5b..c886b9676 100644 --- a/tests/functional/test_functional.py +++ b/tests/functional/test_functional.py @@ -110,10 +110,16 @@ def check_metrics(self, example_script, expect): self.assertEqual(expected, m["_totals"][label]) def test_binding(self): - """Test the bind-to-0.0.0.0 example.""" + """Test the bind-to-0.0.0.0 example. + + Expect three issues: one for the ``0.0.0.0`` literal and one each + for the empty-string wildcard host in the tuple and list forms of + ``bind``. Plain empty-string literals outside of a ``bind`` call + must not be flagged. + """ expect = { - "SEVERITY": {"UNDEFINED": 0, "LOW": 0, "MEDIUM": 1, "HIGH": 0}, - "CONFIDENCE": {"UNDEFINED": 0, "LOW": 0, "MEDIUM": 1, "HIGH": 0}, + "SEVERITY": {"UNDEFINED": 0, "LOW": 0, "MEDIUM": 3, "HIGH": 0}, + "CONFIDENCE": {"UNDEFINED": 0, "LOW": 0, "MEDIUM": 3, "HIGH": 0}, } self.check_example("binding.py", expect)