Description
Since in Python functions are values, we can assign any function to any variable. This also applies to sources and sinks. At the moment, we rely on the sources and sinks to be named in a certain way for us to recognize them (recognition by name). This works for the majority of the use cases, but will not work for all since it is fundamentally not how Python works. Instead, we should fully qualify sinks and sources by their absolute names and propagate them through assignments as if they were variables. Only when these variables are called, we should interpret them as sources/sinks and mark them as such.
Example:
from os import system # introduce the sink into the variable system (1)
class MyClass():
def __init__(self):
self.worker = system # used in closure (2)
def exec(self, command):
self.worker(command) # should behave like a sink (3)
def wipe_clean(self):
self.worker = print # wipes the sink (4)
my_system = system
def func_sink(command):
my_system(command) # should behave like a sink (5)
taint = input()
my_object = MyClass() # call to constructor (6)
my_object.exec(taint) # should be detected as vulnerability (7)
my_object.wipe_clean()
my_object.exec(taint) # should not be detected as vulnerability (8)
In the above example, we would fully qualify the function os.system
to be a sink. This sink is introduced into the variable system
in the import call (1)
.
The sink-tainted system-variable is then used in the __init__
-closure and, as such, sink-taints the value system
in the assignment at (2)
.
in (6)
, this sink-tainted system
variable is propagated into self.worker
. The call at (7)
with a taint gets put into the sink-tained self.worker
in (3)
. Only here, we resolve self.worker
to be a sink function, since it is called.
We will, at (4)
then in turn be able to remove the sink-taint from self.worker
and be able to detect (8)
not to be problematic.
As such, sources and sinks should propagate through the code like taint does.