/** * @name Binding a socket to all network interfaces * @description Binding a socket to all interfaces opens it up to traffic from any IPv4 address * and is therefore associated with security risks. * @kind problem * @tags security * @problem.severity error * @sub-severity low * @precision high * @id py/bind-socket-all-network-interfaces */ import python import semmle.python.dataflow.new.DataFlow import semmle.python.ApiGraphs /** Gets a reference to a hostname that can be used to bind to all interfaces. */ private DataFlow::LocalSourceNode vulnerableHostname(DataFlow::TypeTracker t, string hostname) { t.start() and exists(StrConst allInterfacesStrConst | hostname in [ // IPv4 "0.0.0.0", "", // IPv6 "::", "::0" ] | allInterfacesStrConst.getText() = hostname and result.asExpr() = allInterfacesStrConst ) or // Due to bad performance when using normal setup with `vulnerableHostname(t2, hostname).track(t2, t)` // we have inlined that code and forced a join exists(DataFlow::TypeTracker t2 | exists(DataFlow::StepSummary summary | vulnerableHostname_first_join(t2, hostname, result, summary) and t = t2.append(summary) ) ) } pragma[nomagic] private predicate vulnerableHostname_first_join( DataFlow::TypeTracker t2, string hostname, DataFlow::Node res, DataFlow::StepSummary summary ) { DataFlow::StepSummary::step(vulnerableHostname(t2, hostname), res, summary) } /** Gets a reference to a hostname that can be used to bind to all interfaces. */ DataFlow::Node vulnerableHostname(string hostname) { vulnerableHostname(DataFlow::TypeTracker::end(), hostname).flowsTo(result) } /** Gets a reference to tuple containing a hostname as the first element, that can be used to bind to all interfaces. */ private DataFlow::LocalSourceNode vulnerableAddressTuple(DataFlow::TypeTracker t, string hostname) { t.start() and result.asExpr() = any(Tuple tup | tup.getElt(0) = vulnerableHostname(hostname).asExpr()) or // Due to bad performance when using normal setup with `vulnerableAddressTuple(t2, hostname).track(t2, t)` // we have inlined that code and forced a join exists(DataFlow::TypeTracker t2 | exists(DataFlow::StepSummary summary | vulnerableAddressTuple_first_join(t2, hostname, result, summary) and t = t2.append(summary) ) ) } pragma[nomagic] private predicate vulnerableAddressTuple_first_join( DataFlow::TypeTracker t2, string hostname, DataFlow::Node res, DataFlow::StepSummary summary ) { DataFlow::StepSummary::step(vulnerableAddressTuple(t2, hostname), res, summary) } /** Gets a reference to tuple containing a hostname as the first element, that can be used to bind to all interfaces. */ DataFlow::Node vulnerableAddressTuple(string hostname) { vulnerableAddressTuple(DataFlow::TypeTracker::end(), hostname).flowsTo(result) } /** * Gets an instance of `socket.socket` using _some_ address family. * * See https://docs.python.org/3/library/socket.html */ API::Node socketInstance() { result = API::moduleImport("socket").getMember("socket").getReturn() } from DataFlow::CallCfgNode bindCall, DataFlow::Node address, string hostname where bindCall = socketInstance().getMember("bind").getACall() and address = bindCall.getArg(0) and address = vulnerableAddressTuple(hostname) select bindCall.asExpr(), "'" + hostname + "' binds a socket to all interfaces."