How to protect Python applications from malicious script injection





Python applications use a lot of scripts. This is what cybercriminals use to put a "pig" on us - where we least expect to see it.



One of the advantages of Python is its ease of use: to run a script, you just need to save it in a .pyfile and execute the command python (, python my_file.py). It is also easy to break our file, such as modules my_app.pyand my_lib.pycontinue to connect the modules to use design import...from: import my_lib from my_app.py.



However, this simplicity and lightness has a downside: the easier it is for you to execute code from different locations, the more opportunities for an attacker to interfere.



Place Python code in a safe place



The Python security model is based on three principles:



  1. , sys.path , .
  2. , (main), sys.path.
  3. python , -c -m.


Suppose you want to run a Python application that has been "correctly" installed on your machine. In this case, the only location (besides the folder with Python installed) that will be automatically added to your sys.path by default is the folder with the main script or executable file.



For example, if pipis in /usr/bin, and you run /usr/bin/pip, then sys.pathonly will be added /usr/bin. Only root can write files to / usr / bin, so this location is considered safe.



Nevertheless, the agreement require us to do so: /path/to/python -m pip. This avoids problems with $PATHand conflicts with Windows documentation.



In general, everything will be fine anyway, if only you alone have the authority to add and edit files in the directories from which Python takes modules for import.



Downloads folder - vulnerable location



There are many ways to force browsers (and sometimes other software) to place arbitrary-named files in the Downloads folder without the user's knowledge. This vulnerability is exploited by an attack called DLL Spoofing. In our case, we will talk about Python scripts, but the idea is the same.



Browsers have become more serious about this and are gradually trying to make sure that sites visited by the user cannot secretly drop files into his Downloads folder.



However, this problem will be very difficult to solve completely: for example, an option is still available Content-Disposition HTTP header’s filename*that allows sites to choose a directory to place the downloaded files.



How the attack works



You run the installation: python -m pip. You are downloading a Python package from a completely trustworthy website, which for some reason, however, offers direct downloads rather than PyPI. Maybe it's some kind of internal version, maybe it's a preliminary release - no difference. So it goes to you totally-legit-package.whl:



~$ cd Downloads
~/Downloads$ python -m pip install ./totally-legit-package.whl


It seems to be okay, but it turns out that two weeks ago on a completely different site that you visited, some XSS JavaScript downloaded pip.py with malware into your Downloads folder without your knowledge.



Boom!



Crash!



~$ mkdir attacker_dir
~$ cd attacker_dir
~/attacker_dir$ echo 'print("lol ur pwnt")' > pip.py
~/attacker_dir$ python -m pip install requests
lol ur pwnt


Strange behavior PYTHONPATH



A few paragraphs above, I wrote:



the only location (other than the Python installed folder) that will be automatically added to your sys.path by default is the main script or executable folder.


So what does "default" do here? What other locations can you add?



Basically, $PYTHONPATHyou can write anything to an environment variable . But you wouldn't write your current location in $PYTHONPATH, would you?



Unfortunately, there is a situation where you might accidentally do this.

Let's sketch a "vulnerable" Python application for illustration:



# tool.py
try:
    import optional_extra
except ImportError:
    print("extra not found, that's fine")


Let's create 2 directories: install_dirand attacker_dir. Let's fail in install_dir, then execute cd attacker_dirand place our malicious code there by the name tool.py:



# optional_extra.py
print("lol ur pwnt")


All that remains is to run it:



~/attacker_dir$ python ../install_dir/tool.py
extra not found, that's fine


So far, everything is going well.



But now we will see a common mistake. Many people recommend adding in one $PYTHONPATHmore thing :



export PYTHONPATH="/new/useful/stuff:$PYTHONPATH";


At first glance, this makes sense: if you add project X to $PYTHONPATH, perhaps, according to project Y, something was added there too, or maybe not; in any case, you would not want to overwrite the changes associated with other projects. This is especially important when you are writing documentation that many people will use.



And now we are faced with "strange" behavior $PYTHONPATH. If it $PYTHONPATHwas empty or not installed before the first launch , an empty line will appear in it, which will be recognized as the current directory. Let's check this:



~/attacker_dir$ export PYTHONPATH="/a/perfectly/safe/place:$PYTHONPATH";
~/attacker_dir$ python ../install_dir/tool.py
lol ur pwnt


For added security, let's make it $PYTHONPATHempty and try again:



~/attacker_dir$ export PYTHONPATH="";
~/attacker_dir$ python ../install_dir/tool.py
lol ur pwnt


Exactly, it's not safe at all!



And one more thing : it turns out that situations when a variable is $PYTHONPATHempty and when a variable is $PYTHONPATHnot set are two different stories:



os.environ.get("PYTHONPATH") == ""</code>  <code>os.environ.get("PYTHONPATH") == None.


If you want to be sure to clean up $PYTHONPATH, use the command unset:



~/attacker_dir$ python ../install_dir/tool.py
extra not found, that's fine


In general, writing to a variable $PYTHONPATHwas the most common way of setting up the environment for running Python applications. Fortunately, it went out of style with the advent of virtual environments and virtualenv. If you have an old configuration that writes something to $PYTHONPATH, but you can do without it, then now is the time to delete it.



If for some reason you cannot do this, then use a life hack :



export PYTHONPATH="${PYTHONPATH:+${PYTHONPATH}:}new_entry_1"
export PYTHONPATH="${PYTHONPATH:+${PYTHONPATH}:}new_entry_2"


In bash and zsh, this will give this output:



$ echo "${PYTHONPATH}"
new_entry_1:new_entry_2


Well, now there are no extra colons and empty entries.



Finally, if you are still working with $PYTHONPATH, use absolute paths. Is always!



The problem is much more serious



There are several options for the dangerous development of events associated with the launch of Python files from the Downloads folder:



  • Running python ~/Downloads/anything.py(even if it anything.pyis itself safe). Your Downloads folder will be added to sys.path.
  • Once launched, the jupyter notebook ~/Downloads/anything.ipynbDownloads folder will also be added to sys.path.


Therefore, it is better to remove the .py and .ipynb files from this folder before running.



Execution cd Downloadsand subsequent launch python -cwith instructions importinside or interactive launch pythonwith subsequent import does not completely solve the problem if the imported files are in the Downloads folder.



Unfortunately, this is ~/Downloads/not the only location that can contain malicious files. For example, if you are administering a server to which users can upload files, make sure that no one and nothing can start cd public_uploadsbefore running the command python.



In this case, it might be worth considering that the code that handles uploading the files appends to their names .uploaded. This will help avoid unplanned script execution .py.



Cautions



If you have Python applications that you want to use while in your Downloads folder, make it a rule to enter the path to the script ( / path / to / venv / bin / pip) rather than the module ( / path / to / venv / bin / python -m pip).



In general, just avoid using Downloads as your current working directory and move any scripts you wish to use to a more suitable location before starting.



It's important to understand where Python gets the code it will execute from. Letting someone execute even one line of the left-hand Python script is tantamount to giving complete control of your computer!



By necessity



Reading such an article with "tips and life hacks" on security, it is very easy to assume that the author is very smart, knows a bunch of little things that others should know and constantly think about it. But in fact, this is not entirely true. I will explain.



Over the years, I have observed with enviable frequency how users did not understand where Python is downloading code from. For example, people put their first program using Twisted in a file called twisted.py. But in this case, the import of the library is simply impossible!



Cybercriminals rejoice a lot more if they were able to successfully attack system administrators or developers. If you hack a user, you will get the data of that user. But if you hack an administrator or developer and do it right, then you can gain access to thousands of users whose systems are under the control of the administrator, or even millions of users who use developer software.



Therefore, “just be careful all the time” is not a reliable recipe. Those IT pros who are responsible for products with a large number of users really need to be more careful. At the very least, they should be informed about the peculiarities of the work of certain development tools.



Bug or feature ...



None of what I've described above is actually a real "bug" or "vulnerability". I don't think the Python or Jupyter developers did anything by mistake. The system works the way it is designed and that can probably be explained. Personally, I have no idea how something can be changed in it without curtailing the power of Python for which we value it so much.



One of my favorite inventions is SawStop, a unique safety system for table saws. It allows you to stop the rotation of the saw blade within 5 milliseconds - upon contact with the human body. Before the invention of this system, table saws were extremely dangerous tools, but they performed an important production function. Many very useful and important things have been done on table saws. However, it is also true that table saws accounted for a disproportionate share of accidents in woodworking shops. SawStop now saves a lot of fingers every year.



So, by detailing the complexities of Python scripting, I also hope to give some thought to some security engineers. Can SawStop be made to execute arbitrary code for interactive interpreters? What solution could prevent some of the above problems?



Stay safe friends.






Advertising



VDSina offers secure servers on Linux or Windows - choose one of the pre-installed OS, or install from your image.






All Articles