Command Injection in Django with Hmac Signatures
Command Injection in Django with Hmac Signatures — how this specific combination creates or exposes the vulnerability
Command injection occurs when untrusted input is concatenated into a system command, allowing an attacker to execute arbitrary shell commands. In Django, this risk can arise when you use Hmac Signatures to validate a webhook or callback and then directly incorporate validated data into a shell command. Even though the Hmac Signature confirms the request originates from a trusted source, it does not sanitize or type-check the content of the payload. If the application uses the Hmac-verified fields to construct a shell command—such as passing a filename, user-supplied tag, or path to subprocess.run, os.system, or os.popen—an attacker can inject shell metacharacters (e.g., ;, &, |, $()) and achieve command injection.
Consider a scenario where a payment provider sends a webhook with an order_id and a signature. The view verifies the Hmac Signature and then uses the order_id in a system call to generate a report:
import subprocess
import hmac
import hashlib
from django.http import HttpRequest, HttpResponse
from django.views.decorators.csrf import csrf_exempt
def webhook_view(request: HttpRequest) -> HttpResponse:
signature = request.META.get('HTTP_X_SIGNATURE')
body = request.body
expected = hmac.new(
key=b'super-secret-key',
msg=body,
digestmod=hashlib.sha256
).hexdigest()
if not hmac.compare_digest(signature, expected):
return HttpResponse(status=403)
data = json.loads(body)
order_id = data.get('order_id')
# Dangerous: order_id is used in a shell command
subprocess.run(['generate_report', str(order_id)], check=True)
return HttpResponse('ok')
If an attacker can influence order_id (for example, by controlling the JSON payload before the Hmac is verified), they might supply 123; cat /etc/passwd. Because the Hmac validates the integrity of the message but not the safety of its contents, the command injection bypasses authentication and runs with the web process privileges. This pattern is especially risky when the Hmac is used to authorize administrative actions or file operations, and it maps to the OWASP API Top 10 A03:2021-Injection and commonly appears in API testing results from scans such as those performed by middleBrick, which checks for insecure data handling and unsafe consumption patterns.
Another exposure path is when logs or error messages containing Hmac-verified data are passed to debugging or monitoring commands without escaping. For example, printing raw data to a shell-based log aggregator or feeding it into a templating engine that invokes a shell can reintroduce injection vectors despite the presence of the Hmac Signature. The key takeaway is that Hmac Signatures provide authenticity and integrity, but they do not provide input validation or sandboxing; additional controls are required to prevent command injection.
Hmac Signatures-Specific Remediation in Django — concrete code fixes
Remediation focuses on avoiding shell interpretation entirely and rigorously validating the shape of Hmac-verified data. The safest approach is to never pass user-influenced data directly to a shell command. Use structured APIs, strict allowlists, and parameterized operations instead of shell execution.
1. Avoid the shell entirely
Replace subprocess calls that accept a list with calls that avoid shell=True and do not concatenate strings into a command. Prefer using Python-native libraries for the task. For file operations, use pathlib or Django’s storage APIs rather than shelling out.
import subprocess
import hmac
import hashlib
import json
from django.http import HttpRequest, HttpResponse
from django.views.decorators.csrf import csrf_exempt
@csrf_exempt
def webhook_view(request: HttpRequest) -> HttpResponse:
signature = request.META.get('HTTP_X_SIGNATURE')
body = request.body
expected = hmac.new(
key=b'super-secret-key',
msg=body,
digestmod=hashlib.sha256
).hexdigest()
if not hmac.compare_digest(signature, expected):
return HttpResponse(status=403)
data = json.loads(body)
order_id = data.get('order_id')
# Safe: use structured APIs, no shell
# Example: generate_report is replaced with a Python function
generate_report_python(order_id=int(order_id))
return HttpResponse('ok')
def generate_report_python(order_id: int) -> None:
# Implement report generation using pure Python or database queries
pass
2. Strict input validation and allowlisting
If you must interact with external processes, validate the Hmac-verified input against a strict pattern (e.g., digits only) and use parameterized APIs. Never trust the Hmac to enforce content safety.
import re
import subprocess
import hmac
import hashlib
import json
from django.http import HttpRequest, HttpResponse
from django.views.decorators.csrf import csrf_exempt
@csrf_exempt
def webhook_view(request: HttpRequest) -> HttpResponse:
signature = request.META.get('HTTP_X_SIGNATURE')
body = request.body
expected = hmac.new(
key=b'super-secret-key',
msg=body,
digestmod=hashlib.sha256
).hexdigest()
if not hmac.compare_digest(signature, expected):
return HttpResponse(status=403)
data = json.loads(body)
order_id = data.get('order_id')
# Strict allowlist: only digits
if not re.fullmatch(r'\d+', str(order_id)):
return HttpResponse('invalid order_id', status=400)
# Safe subprocess usage: list args, no shell
subprocess.run(['generate_report', str(order_id)], check=True, shell=False)
return HttpResponse('ok')
3. Sanitization and escaping when shell is unavoidable
If you must use a shell (avoid this), use shlex.quote on every user-influenced argument and never concatenate strings to form a command line.
import subprocess
import hmac
import hashlib
import json
import shlex
from django.http import HttpRequest, HttpResponse
from django.views.decorators.csrf import csrf_exempt
@csrf_exempt
def webhook_view(request: HttpRequest) -> HttpResponse:
signature = request.META.get('HTTP_X_SIGNATURE')
body = request.body
expected = hmac.new(
key=b'super-secret-key',
msg=body,
digestmod=hashlib.sha256
).hexdigest()
if not hmac.compare_digest(signature, expected):
return HttpResponse(status=403)
data = json.loads(body)
order_id = data.get('order_id')
# Escape each argument when shell is truly required
cmd = ['generate_report', shlex.quote(str(order_id))]
subprocess.run(cmd, check=True, shell=True)
return HttpResponse('ok')
4. Monitoring and testing
Use automated security scans to detect command injection patterns and verify that Hmac-verified endpoints do not introduce unsafe subprocess usage. Incorporate checks into your CI/CD pipeline to fail builds when risky patterns are found.
middleBrick can help identify such issues by scanning your API endpoints for insecure consumption and unsafe handling of validated data, providing prioritized findings and remediation guidance without requiring authentication or agents.
Related CWEs: inputValidation
| CWE ID | Name | Severity |
|---|---|---|
| CWE-20 | Improper Input Validation | HIGH |
| CWE-22 | Path Traversal | HIGH |
| CWE-74 | Injection | CRITICAL |
| CWE-77 | Command Injection | CRITICAL |
| CWE-78 | OS Command Injection | CRITICAL |
| CWE-79 | Cross-site Scripting (XSS) | HIGH |
| CWE-89 | SQL Injection | CRITICAL |
| CWE-90 | LDAP Injection | HIGH |
| CWE-91 | XML Injection | HIGH |
| CWE-94 | Code Injection | CRITICAL |