Link Search Menu Expand Document

OS Command Injection in Python

Vulnerable example

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)

Since the 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 /etc/passwd.

injection.png

Prevention

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 False.

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)

Setting shell as 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)

References

https://cwe.mitre.org/data/definitions/78.html https://www.owasp.org/index.php/Command_Injection https://docs.python.org/3/library/os.html https://docs.python.org/3/library/subprocess.html