I'm developing a command line application in Python which processes information that users provide. I've been reading the Python documentation on this topic and found that the built-in module subprocess
provides the call
method, which executes a string as if it was run on a command line. The disadvantage of this approach is that it can lead to shell injection.
The solution I came up with uses a set of illegal substrings, such as rm
or -rf
, and then uses regex to check whether the provided string contains a substring from the set.
import re
from string import ascii_letters, punctuation
# Generate a set of characters used for string sanitization.
punctuation_minus = set(punctuation) - set(('.', '_', '-'))
substrings = set(("rm", "&&", "-f", "null", "/dev/null", "2>&1", "-rf",
"rf"))
illegal = [re.escape(item) for item in (punctuation_minus | substrings)]
illegal_as_str = "|".join(illegal)
def sanitize_string(s):
"""Cleans a string from illegal characters."""
# Get all legal strings.
operations = re.split(illegal_as_str, s)
# Remove trailing spaces
no_trailings = [x.strip() for x in operations]
# Remove empty strings.
list_of_operations = filter(None, no_trailings)
# Generate a safe string to return.
operations_str = " ".join(list_of_operations)
try:
operations_str.decode("utf-8")
return operations_str
except UnicodeDecodeError as detail:
print("UnicodeDecodeError: " + str(detail))
return False
The inherent issue with this approach is that it heavily depends on the set of illegal characters I'm using. Is there any way I can sanitize a string taking into account the common attacks? And if not, how can I improve my code?
The user is expected to provide git commands, such as git add
or git push
. Each one of these git commands needs certain parameters that the user needs to provide. The next example shows how all the illegal substrings are removed from a user input when adding files to the staging area:
>>> git_add_str = "git add {}"
>>> files_to_add = input("Files to add as string:")
Files to add as string: "file1.py file2.py; rm -rf /"
# Clean files_to_add before modifying git_add_str.
>>> clean_str = sanitize_string(files_to_add)
>>> clean_str
'file1.py file2.py'
>>> git_add_str = git_add_str.format(clean_str)
>>> git_add_str
'git add file1.py file2.py'
This output is considered legal as it does not contain any of the illegal substrings. Now it is safe to run subprocess.call
with this string and shell=True
.
git push [-server] [-branch]
, or a commit message ingit commit -m [-message]
. What I'm trying to achieve is a method that "cleans" these parameters so that string gets executed safely. \$\endgroup\$ – tulians Feb 21 '17 at 3:46shell=False
to avoid the secondary command? You could also escape each argument passed in. Running the process under a very low-privilege account might also limit your risk. \$\endgroup\$ – Scovetta Feb 21 '17 at 3:50shell=False
but I always get this errorOSError: [Errno 2] No such file or directory
. \$\endgroup\$ – tulians Feb 21 '17 at 3:53