Django globals() Misuse for Arbitrary Code Execution

HIGH

User input is used to index globals(), enabling arbitrary function dispatch and potential code execution.

Rule Information

Language
Python
Category
Django
Author
Shivasurya
Shivasurya
Last Updated
2026-03-22
Tags
pythondjangocode-injectionglobalsfunction-dispatchtaint-analysisinter-proceduralCWE-94OWASP-A03
CWE References

Interactive Playground

Experiment with the vulnerable code and security rule below. Edit the code to see how the rule detects different vulnerability patterns.

pathfinder scan --ruleset python/PYTHON-DJANGO-SEC-022 --project .
1
2
3
4
5
6
7
8
9
10
11
12
13
14
rule.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

About This Rule

Understanding the vulnerability and how it is detected

This rule detects code injection vulnerabilities in Django applications where untrusted user input from HTTP request parameters is used as a key to index the globals() dictionary for dynamic function dispatch.

Python's globals() returns the current module's global namespace as a dictionary. Using user-controlled input to retrieve entries from globals() (e.g., globals()[user_input]) and then calling the result allows an attacker to invoke any globally accessible callable, including imported functions, class constructors, and module-level objects. Depending on what is in scope, this can lead to OS command execution, file system access, database manipulation, or escalation to full code execution.

This pattern is sometimes used as an ad-hoc dispatch mechanism, but it exposes the entire global namespace to attackers. The correct approach is an explicit allowlist dictionary mapping safe string names to specific callable objects.

Security Implications

Potential attack scenarios if this vulnerability is exploited

1

Unrestricted Function Dispatch

globals() exposes every name in the module's global scope: imported functions, class definitions, module references, and built-in references. An attacker can call os.system, subprocess.run, open, or any other global, not just the intended dispatch targets. The attack surface is the entire module namespace.

2

Imported Module Access

If the module imports os, subprocess, shutil, or other powerful modules, their functions are accessible through globals(). An attacker supplying os as the function name gets the entire os module, from which they can call os.system(), os.remove(), or os.environ to access any OS function.

3

Class Constructor Invocation

Model classes, form classes, and serializer classes are typically in scope. An attacker who invokes a class constructor via globals() with crafted arguments can create arbitrary model instances, potentially bypassing validation logic that is enforced at the view layer but not at the model constructor level.

4

Chained Code Execution via Callable Objects

Callable objects retrieved through globals() can be chained. If the attacker retrieves a partial or lambda that leads to a code path with further dynamic execution, the initial globals() access becomes the entry point for a more complex attack chain.

How to Fix

Recommended remediation steps

  • 1Replace globals()[user_input] dispatch with an explicit dict mapping permitted string keys to specific callable objects.
  • 2The allowlist dict should contain only the specific functions intended to be callable -- not entire modules or class hierarchies.
  • 3Validate the action name is in the allowlist before attempting to call anything; return a 404 or 400 for unknown actions.
  • 4Never expose the globals() or locals() namespace to user input, even indirectly through getattr() on modules.
  • 5Audit all uses of getattr(module, user_input) for the same vulnerability pattern, as it is equivalent to globals()[user_input] for imported modules.

Detection Scope

How Code Pathfinder analyzes your code for this vulnerability

This rule performs inter-procedural taint analysis with global scope. Sources include calls("request.GET.get"), calls("request.POST.get"), calls("request.GET.__getitem__"), calls("request.POST.__getitem__"), calls("request.body"), and calls("request.read"). The sink is calls("globals") where the return value is indexed with a tainted key, tracked via .tracks(0). The rule also detects calls("getattr") where the attribute name argument is tainted (also equivalent to namespace traversal with user input). No sanitizers are recognized -- any user-controlled globals() indexing is flagged.

Compliance & Standards

Industry frameworks and regulations that require detection of this vulnerability

CWE Top 25
CWE-94 - Static Code Injection in software weaknesses list
OWASP Top 10
A03:2021 - Injection
PCI DSS v4.0
Requirement 6.2.4 - protect against injection attacks
NIST SP 800-53
SI-10: Information Input Validation; CM-7: Least Functionality
ISO 27001
A.14.2.5 - Secure system engineering principles

References

External resources and documentation

Similar Rules

Explore related security rules for Python

Frequently Asked Questions

Common questions about Django globals() Misuse for Arbitrary Code Execution

You cannot control what is in globals() at runtime -- it includes everything the module imports, defines, and references. Even if you only intend to dispatch to a few view functions, globals() also contains os, sys, django.conf.settings, and any other imports. An attacker who knows Python will try globals()['os'] and if the os module is imported anywhere in the module, it is accessible.
hasattr() (or 'key in globals()') checks only confirm that the name exists in the namespace -- they do not restrict which names are accessible. An attacker can supply any name that exists in globals(), including os, subprocess, or settings. The fix is not to add a hasattr() check but to use an explicit allowlist.
Yes. getattr(some_module, user_input) where user_input is tainted is equivalent to accessing the module's attribute namespace with user-controlled names. If the module has dangerous functions (like a utils module that imports subprocess), an attacker can access them. The fix is the same: use an explicit allowlist dict.
Replace the globals()-based lookup with a plugin registry. Each plugin registers itself in a dictionary at import time (a pattern called "registry" or "service locator"). The dispatch function looks up the plugin by name in this registry. The registry is populated at startup from trusted code, not from user input at runtime. This provides the same extensibility without exposing the full namespace.
Not reliably. Even if you check isinstance(result, SomeBaseClass), an attacker who can place a malicious object with the right class hierarchy into globals() (through a second-order injection or import manipulation) can bypass the check. Strict allowlisting by name is the only reliable protection.
globals() misuse is generally rated as High rather than Critical because the exploit requires knowledge of what names are in scope and may require chaining multiple calls. eval() and exec() are Critical because they directly execute arbitrary code strings. However, all three are Remote Code Execution vectors and require immediate remediation.

New feature

Get these findings posted directly on your GitHub pull requests

The Django globals() Misuse for Arbitrary Code Execution rule runs in CI and posts inline review comments on the exact lines — no dashboard, no SARIF viewer.

See how it works