The following snippet contains a Flask web application written in Python that executes the
nslookup command to resolve the host supplied by the user.
@app.route("/dns") def page(): hostname = request.values.get(hostname) cmd = 'nslookup ' + hostname return subprocess.check_output(cmd, shell=True)
hostname is simply appended to the command and executed on a subshell with
shell=True, an attacker could stack another command using
; in the
file_path GET parameter to inject additional commands. The screenshot shows an attack injecting the
cat command to disclose
Python has native APIs to execute commands. Some of them accept the
shell argument that might be set as
True to accept the command as a single string. This should be avoided, with commands being passed as a list of arguments, whenever possible.
Some methods in the
os library only accept the commands argument as single strings and are prone to introducing an injection vulnerability. These functions should not be used; however, in cases where it is unavoidable, they should be used very carefully by escaping or filtering out the characters against an allow list (such as filtering out everything that is not alphanumeric).
os.system(cmd) os.popen(cmd, ...)
The recommended approach is to execute commands using the
subprocess API, passing the command as a list of argument strings with the
shell option set to
subprocess.call(args, ..., shell=False) subprocess.run(args, ..., shell=False) subprocess.Popen(args, ..., shell=False) subprocess.check_output(args, ..., shell=False) subprocess.check_call(args, ..., shell=False)
True to pass the command as single string introduces the vulnerability.
subprocess.Popen('nslookup ' + hostname, ... , shell=True) # WRONG
Passing the command as a list of arguments is the safer approach that should always be used, but it might be vulnerable to argument injection depending on the binary.
subprocess.Popen([ 'nslookup', hostname ], ... , shell=False)