Secure Python coding: common mistakes
You find the most common security mistakes of Python projects in your own lab application, understand why they happen, fix them and prevent them coming back with tests.
Level 401 · 21 lessons · 24 practice labs
Lessons
Injection and code execution
- A threat model for a Python application
- eval, exec and untrusted input
- Command injection: os.system and shell=True
- SQL injection: building queries with f-strings
- Template injection: Jinja2 and format strings
- Short task: close three injections in the application
Files, data and network
- Path traversal: a file name from the user
- Unsafe deserialisation: pickle, yaml, marshal
- XML external entities
- Server-side request forgery and URL validation
- Temporary files and race conditions
- Denial of service through regular expressions
- Short task: make a file upload service safe
Cryptography, secrets and dependencies
- The difference between random and secrets
- Storing passwords: hashlib is not enough, argon2 and scrypt
- Cryptography mistakes: ECB, fixed keys, timing
- Secrets in code and managing the environment
- The supply chain: typosquatting, pip-audit, installing by hash
- Automated scanning with bandit and semgrep
- Short task: review and fix an authentication module
Capstone
- Capstone: a full security review of an application with a fix report
Practice labs
- Checking an archive's size before unpacking: Limit the file count and declared total size of an uploaded zip before unpacking and write the check that rejects an archive that inflates excessively.
- Closing option injection through file names: Harden an archiving wrapper where file names starting with a dash reach the command as options, using the '--' separator and name validation, and add a regression test.
- Removing eval from a calculator: Replace a function that evaluates user expressions with eval with a safe ast-based evaluator that rejects names and calls.
- Closing shell injection in a backup command: Rewrite a backup function that adds a file name to a shell=True command using an argument list and test it with a name containing ;.
- Fixing SQL injection in a login query: Parameterise a login query built with an f-string and show with a test that quoted input no longer logs in.
- Closing template injection in an e-mail template: Fix code that mixes user text into the template source by passing it only as a variable to a sandboxed Jinja2 environment.
- Closing settings leaked through str.format: Rewrite notification code where a user-supplied format string can reach object attributes, using safe placeholders.
- Blocking path traversal in a download endpoint: Fix a download function that joins the file name straight onto a path using resolve and a root-directory check.
- Moving a pickled session to JSON: Move session code that unpickles from a cookie to signed JSON, rejecting tampered cookies.
- Closing external entities in an XML import: Harden a product import function against samples with external entities and entity expansion using defusedxml.
- Fixing a predictable temp file: Rewrite report code that opens a fixed-name file under /tmp using tempfile's safe creation and tight permissions.
- Validating an uploaded file's name and type: Add a size limit, a magic-byte check and random naming to an upload handler, rejecting double-extension files.
- Limiting target addresses in a preview service: Add scheme and allowed-host checks to a preview function that requests a user-supplied URL.
- Blocking requests that reach internal addresses: Block requests to loopback and private addresses by checking the resolved IP, including bypasses through redirects.
- Replacing a slow pattern in e-mail validation: Replace an e-mail pattern that hangs on long input with a length limit and a simple pattern so the timing test passes.
- Generating invite codes with secrets: Replace invite codes produced with random by secrets and compare them with compare_digest.
- Storing passwords with scrypt: Rewrite a password module that uses unsalted MD5 with scrypt, a per-user salt and a format that stores the parameters.
- Fixing a hard-coded key and a fixed IV: Move a module that encrypts with a fixed key and IV to authenticated encryption with a key from the environment and a random nonce per message.
- Updating vulnerable dependencies: Upgrade the vulnerable packages pip-audit finds to safe versions and show the tests still pass.
- Pinning dependencies by hash: Turn a requirements file into a hashed one, install with --require-hashes and weed out a look-alike fake package.
- Writing your own semgrep rule: Write a semgrep rule that catches HTTP calls with verification turned off and test that it raises no false alarms on correct calls.
- Switching a YAML import to the safe loader: Replace the unsafe YAML load in an import routine with the safe loader, rejecting documents that carry object tags while ordinary documents load as before.
- Limiting request body size and depth: Add body size, nesting depth and item count limits to an endpoint that accepts JSON, rejecting over-limit requests with a clear error code before parsing them.
- Re-checking the target on redirects: Turn off automatic redirect following in a preview service, re-check the target address against the allowlist at every redirect step and cap the number of steps.
About this course
You find the most common security mistakes of Python projects in your own lab application, understand why they happen, fix them and prevent them coming back with tests.
What you will learn
- Injection and code execution
- Files, data and network
- Cryptography, secrets and dependencies
- Capstone
Prerequisites
You should have practised the topics of the same path up to level 301.
Lab environment
Lessons run in your own Kali Linux terminal in the browser, connected to a private lab network. Nothing to install.
Frequently asked questions
- Is this course right for my level?
- The course is at level 401; it assumes you know the topics of the lower-level courses.
- Is there anything to install?
- No. Every lesson runs in your own lab environment that opens in the browser.
- How do lessons work?
- Each lesson has 8 to 10 steps and a short quiz. Early steps show the command to type, later steps give less help; if two attempts fail, the solution opens.
- Can I try it for free?
- Yes. The first three lessons of every course are free.