Skip to content

False negatives of Python rule BindToAllInterfaces #21582

@9iang22

Description

@9iang22

I found some FN in the BindToAllInterfaces rule. While the rule successfully catches direct literal bindings like s.bind(('0.0.0.0', 8080)), but I identified that it sometimes doesn't match some common real-world usage patterns.

First, the rule relies on tracking string literals directly into the bind() call. However, it misses cases where the address is stored in an object attribute or derived from environment variable defaults. The DataFlow::TypeTrackingNode configuration doesn't seem to propagate the taint through instance attributes or library default arguments. I am not sure whether this can be solved by configuring the dataflow tracking.

For example, storing the address in self.bind_addr bypasses the check:

class Server:
    def __init__(self):
        self.bind_addr = '0.0.0.0'  # Taint lost here
        self.port = 31137

    def start(self):
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.bind((self.bind_addr, self.port))  # Rule misses this

Similarly, using os.environ.get with an insecure default value isn't flagged:

import os
import socket

# The default '0.0.0.0' is not tracked as a taint source
host = os.environ.get('APP_HOST', '0.0.0.0')
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((host, 8080))  # Rule misses this

Second,
The rule is hardcoded to check API::moduleImport("socket"). This excludes widely used alternatives like gevent.socket and high-level framework runners that perform binding internally.

For instance, using gevent.socket evades the module check:

from gevent import socket  # Not 'import socket.'
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('0.0.0.0', 31137))  # Rule misses this

High-level framework methods like Sanic.run or aiohttp.web.run_app also bind sockets but aren't modeled as sinks:

from sanic import Sanic
app = Sanic(__name__)

if __name__ == "__main__":
    # Internally binds to 0.0.0.0, but doesn't call socket.bind() directly
    app.run(host='0.0.0.0', port=31137)  # Rule misses this

But I am not sure if there is another rule to handle these framework cases.

Metadata

Metadata

Assignees

No one assigned

    Labels

    questionFurther information is requested

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions