Skip to the solution
MediumBackend14 min read

What are common Python security pitfalls?

881 views

The problem

Question presented to candidate: "You are reviewing a pull request for a Python web service. What security mistakes would you look for that are specific to Python or common in Python code, and what would you ask the author to use instead?"

What a strong answer should cover:

  • Turning data into code: eval / exec on input (use ast.literal_eval or json), pickle or an unsafe YAML loader on untrusted bytes (they can run code), user-controlled str.format templates that read attributes.
  • Injection into other interpreters: SQL built with f-strings (use ? / %s placeholders), shell commands with shell=True (pass an argument list), and path traversal with ../ (resolve and check is_relative_to).
  • Secrets: random is predictable, use secrets; compare tokens with hmac.compare_digest; store passwords with a slow, salted KDF (hashlib.scrypt, pbkdf2_hmac, or argon2/bcrypt), never a bare hash.
  • Python-specific traps: assert disappears under python -O; tarfile.extractall without filter="data" before 3.14; untrusted XML without defusedxml.
  • Supply chain and configuration: typosquatted packages, unpinned dependencies (lock with hashes), verify=False, debug mode and secrets in logs or source.

Clarifying questions expected:

  • Where does untrusted input enter (HTTP bodies, uploaded files, message queues), and what does the service do with it?

Code / implementation expected: Optional โ€” short before and after snippets, such as a parameterised query or literal_eval instead of eval.

  • security
  • eval
  • pickle

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

14 min read ยท 11 sections

Run the code

Edit the code and run it here. Each console line with an expected value becomes a test.

Pythoneval vs literal_eval, the empty-builtins myth, format templates and assert under -O
PythonPath traversal, random vs secrets, compare_digest and salted password hashing
PythonSQL injection vs placeholders and the tarfile data filter (needs sqlite3, CPython 3.12)
# Injection and archive pitfalls (sqlite3 and the tarfile filters are not
# available in the page runtime, so this one runs on CPython 3.12).
import io
import os
import sqlite3
import tarfile
import tempfile

db = sqlite3.connect(":memory:")
db.execute("CREATE TABLE users (name TEXT, is_admin INTEGER)")
db.executemany("INSERT INTO users VALUES (?, ?)", [("ana", 0), ("root", 1)])

def find_unsafe(name):
    sql = f"SELECT name FROM users WHERE name = '{name}'"
    return db.execute(sql).fetchall()

def find_safe(name):
    return db.execute("SELECT name FROM users WHERE name = ?", (name,)).fetchall()

attack = "x' OR '1'='1"
print("f-string SQL :", find_unsafe(attack))
print("placeholder  :", find_safe(attack))
print("normal lookup:", find_safe("ana"))
try:
    find_unsafe("x'; DROP TABLE users; --")
except sqlite3.Error as e:
    print("sqlite3 refuses a second statement:", type(e).__name__)

# A tar archive whose member name climbs out of the destination folder.
buf = io.BytesIO()
with tarfile.open(fileobj=buf, mode="w") as tar:
    data = b"pwned"
    info = tarfile.TarInfo("../escaped.txt")
    info.size = len(data)
    tar.addfile(info, io.BytesIO(data))

base = tempfile.mkdtemp()
dest = os.path.join(base, "extract")
os.makedirs(dest)
buf.seek(0)
with tarfile.open(fileobj=buf) as tar:
    try:
        tar.extractall(dest, filter="data")
    except tarfile.FilterError as e:
        print("data filter blocked it:", type(e).__name__)
print("escaped file written:", os.path.exists(os.path.join(base, "escaped.txt")))
print("filters available:", sorted(n for n in ("data_filter", "tar_filter", "fully_trusted_filter") if hasattr(tarfile, n)))

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 61 of 139 in Python

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Python questions
Up nextMedium

What is the difference between __new__ and __init__?

Continue