# Security for Python Developers **Menno Finlay-Smits** `
` --- ## Scope Security concerns developers should be thinking about Mostly Python specific ... or with a Python perspective --- ## Use a Current Python version * Python interpreter security issues are found all the time * The version of Python that comes with your OS is often out of date --- ## Python Vulnerabilities Over Time (>="medium")
--- ## Keeping Python Up-to-date - pyenv - Linux, macOS, WSL - pyenv-win - Windows - Homebrew - macOS, Linux - Nix - Linux, macOS - Anaconda - Windows, Linux, macOS - Docker - Windows, Linux, macOS --- ## Typo-squatting - A malicious package is uploaded to PyPI - Name is similar to a popular package - Often a modified clone of the popular package - User unwittingly includes the bad package in their project - Everyone who installs the project is affected --- ## Example: `request` package - `request` instead of `requests` - Attempts to download a payload to victim's machine in `setup.py` - Downloaded over 20,000 times ---
---
--- ## More on typo-squatting - Some malicious packages stay persistent - Other attacks have targeted crypto related packages - Not just a Python problem - PyPI now blocks new packages with similar names to existing packages --- ## Scan your code - Tools exist to look for issues in your code - Including security issues - Run alongside tests in CI --- ## pip-audit - Looks for vulnerabilities in project dependencies - Scans within a virtual environment * or `requirements.txt` - Uses the Python Packaging Advisory Database - By Trail of Bits with support from Google --- ## pip-audit Example ``` ❯ python3 -m env ve ❯ source ve/bin/activate ❯ pip install flask==0.5 # has known vulns ❯ pip-audit Found 4 known vulnerabilities in 2 packages Name Version ID Fix Versions ----- ------- -------------- ------------ flask 0.5 PYSEC-2019-179 1.0 flask 0.5 PYSEC-2018-66 0.12.3 flask 0.5 PYSEC-2023-62 2.2.5,2.3.2 pip 23.2.1 PYSEC-2023-228 23.3 ``` Unexpected pip issue! --- ## More on pip-audit - Can only detect known vulnerabilities - Can end up running malicious code when it installs packages - JSON output for custom filtering/suppression --- ## bandit - Parses your code - Applies a set of checks to find security issues - For example: * Use of insecure hash functions * Use of SSL with bad defaults * bypassing the Django ORM with raw SQL - Can create own checks --- ## pysa - Data flow analysis - See where untrusted data gets used - Heavyweight - requires code decoration - Can find injection issues and more - Created by and used at Facebook - Likely only worth it for high threat scenarios --- ## Debug mode in production - Web frameworks have helpful debug views for unhandled errors - Show program state at the point of error - Django: `DEBUG = True` setting - Great for developers but dangerous in production --- ## Django example
--- ## Django example
--- ## More on debug mode - Django has some protections * Won't leak variables with names containing words like `SECRET` * Won't show debug views to localhost by default - Have CI or deploy automation ensure that `DEBUG` is off --- ## Injection - (No)SQL Injection - Command Injection - Cross-site scripting - Any insufficiently sanitised input could be a problem --- ## Command injection example ```python import subprocess from flask import Flask app = Flask(__name__) @app.route("/
") def hash(text): return subprocess.run( f"echo {text} | md5sum", shell=True, capture_output=True, text=True, ).stdout ``` --- ## Command injection example ``` ❯ curl http://localhost:5000/foo d41d8cd98f00b204e9800998ecf8427e - ❯ curl 'http://localhost:5000/foo;ls%20~;echo%20bar' foo Desktop Documents Downloads Music Pictures Public TODO.txt c157a79031e1c40f85931829bc5fc552 - ``` Command became: ``` echo foo;ls ~;echo bar | md5sum ``` --- ## SQL injection example Don't do this! ```python def get_user_id(username: str) -> int: with connection.cursor() as cursor: cursor.execute(""" SELECT id FROM users WHERE username = '%s' """ % username) result = cursor.fetchone() if not result: raise ValueError("no such username: %s" % username) return result[0] ``` --- ## Triggering SQL injection ```python get_user_id("'; select 1; --") get_user_id("'; drop table users; --") ``` --- ## Avoiding SQL injection ```python def get_user_id(username: str) -> int: with connection.cursor() as cursor: cursor.execute(""" SELECT id FROM users WHERE username = %s -- HERE! """, [username]) # and HERE! result = cursor.fetchone() if not result: raise ValueError("no such username: %s" % username) return result[0] ``` - Or better yet use a higher level layer * Django ORM * sqlalchemy --- ## Problematic pickles - Pickle is convenient but... - Pickle files/strings are little programs * opcodes and data * Execute in the "pickle machine" (a VM) - Can be used to execute arbitrary code --- # Pickle example Using the fickling package to mess with a pickle. ```python from fickling.fickle import Pickled import pickle # Create innocent pickle pkl = pickle.dumps("hello world") # Make the pickle malicious p = Pickled.load(pkl) p.insert_python_exec('print("pwned!")') pkl = p.dumps() # unpickle the malicious pickle data = pickle.loads(pkl) print(data) ``` ``` > python main.py pwned! hello world ``` --- Maybe just avoid pickles in most cases? --- ## Logging is Important - Log important events to allow detection of problems * Logins * Password changes * User creation * Access to sensitive resources --- ## Logging is Important - Use a format that can be easily ingested into security and/or log aggregations tools - Structured JSON is common - But JSON is hard for people to read! * Human readable to console or debug mode * JSON to file - The `structlog` package is great --- ## What else? - OWASP Top 10 * https://owasp.org/ - "Developer's security guide: 50 online resources to shift left" * https://techbeacon.com/security/developers-security-guide-50-online-resources-shift-left