Security & adversarial input
SeniorWhy eval/exec and untrusted pickle are dangerous, SQL injection in Python, and timing-safe comparison.
Created 2 September 2026 · Updated 12 September 2026
4 concepts · detail hidden
01Why `eval()`/`exec()` on "trusted" input is dangerous — shifting trust boundaries
#c-why-evalexec-on-trusted-input-is-dangerous-shifting-trust-boeval()parses and executes an arbitrary Python expression;exec()does the same for arbitrary Python statements — either one grants whatever string is passed to it the full power of the Python interpreter itself, not just, say, basic arithmetic evaluation, however innocuous the intended use case seems. The danger in “trusted” input sources is that trust boundaries shift over time — a config file that today is only ever hand-edited by a trusted engineer can, months later, become populated from a web form, an uploaded file, or a value pulled from an external API, without whoever made that later change realizing the config value flows into aneval()call somewhere downstream. Concrete exploit sketch: a config value intended to be a simple math expression like"2 + 2"could instead contain something like"__import__('os').system('rm -rf /')"(invoking theosmodule to run an arbitrary shell command) —evalwould happily execute that exactly as written, since it has no concept of “only allow arithmetic,” it just runs Python code.02Why unpickling untrusted data is arbitrary code execution — and what to use for a data contract
#c-why-unpickling-untrusted-data-is-arbitrary-code-execution-anpickleis Python’s built-in object serialization format (converting live Python objects to bytes and back) — critically, unpickling data doesn’t just passively reconstruct plain data structures the way parsing JSON does; the pickle format can include instructions to call arbitrary functions/constructors during reconstruction (this is by design, since it’s how pickle supports serializing custom class instances, not just plain built-in types) — meaning a maliciously-crafted pickle payload can be constructed to call any function reachable at unpickling time, including something as damaging as invoking a shell command, making unpickling untrusted data functionally equivalent to running arbitrary attacker-supplied code. Good engineers reach immediately for a format designed to represent inert data only, with no code-execution capability baked into deserialization, for anything crossing a genuine trust boundary (between separately-deployed services, or from user-controllable input) — JSON is the most common choice, with protobuf/msgpack as more efficient, still-safe binary alternatives;pickleremains a legitimate, fine choice only for genuinely trusted, same-codebase use (e.g., caching your own application’s internal objects to disk, where you fully control both what’s written and what’s ever read back).03SQL injection in Python — the f-string pattern, the parameterized fix, and why an ORM isn't automatic safety
#c-sql-injection-in-python-the-f-string-pattern-the-parameterizThe vulnerable pattern: building a SQL query string by directly interpolating user-supplied input, e.g.
f"SELECT * FROM users WHERE name = '{user_input}'"— ifuser_inputcontains something like"'; DROP TABLE users; --", the resulting string is no longer the query you intended at all, since the attacker-controlled text becomes part of the SQL syntax itself rather than staying safely “just data.” The fix is parameterized queries — passing the user value as a separate argument to the database driver’s execute method (cursor.execute("SELECT * FROM users WHERE name = %s", (user_input,))), letting the underlying database driver handle safely escaping/binding the value in a way that guarantees it’s always treated as pure data, never as executable query syntax, regardless of its content. Using an ORM (like Django’s ORM or SQLAlchemy) doesn’t automatically make you immune, because most ORMs still provide escape hatches for writing raw SQL when the ORM’s own query-building API can’t express what you need — Django’s.extra()method (largely deprecated for exactly this reason) or any.execute()-style raw-SQL call within an ORM’s toolkit is just as vulnerable to naive string interpolation as rawsqlite3/psycopg2code would be; the safety comes specifically from using parameterization, not from “using an ORM” as a blanket guarantee.04Timing attacks on secret comparison — why `hmac.compare_digest` instead of `==`
#c-timing-attacks-on-secret-comparison-why-hmaccompare_digest-iPython’s default string equality (
==) typically short-circuits — it stops comparing as soon as it finds the first differing character, meaning a comparison against a mostly-wrong guess returns slightly faster than one against a guess that happens to match many leading characters correctly. In most everyday code this timing difference is completely irrelevant, but for comparing secrets (an API token, a cryptographic signature, a password hash) against an attacker-supplied guess, this creates a genuine timing side-channel: by carefully measuring how long each comparison takes across many attempts, an attacker can in principle infer the secret one character at a time, since a guess with more correct leading characters takes measurably longer to reject than one with fewer.hmac.compare_digest()performs a constant-time comparison — it always takes the same amount of time regardless of where (or whether) the two values first differ, specifically closing off this timing side-channel, which is exactly why it (or an equivalent constant-time comparison) is the correct, standard tool for comparing anything security-sensitive, rather than a normal==.