python.django.security.locals-as-template-context.locals-as-template-context

profile photo of semgrepsemgrep
Author
4,549
Download Count*

Using 'locals()' as a context to 'render(...)' is extremely dangerous. This exposes Python functions to the template that were not meant to be exposed. An attacker could use these functions to execute code that was not intended to run and could compromise the application. (This is server-side template injection (SSTI)). Do not use 'locals()'. Instead, specify each variable in a dictionary or 'django.template.Context' object, like '{"var1": "hello"}' and use that instead.

Run Locally

Run in CI

Defintion

rules:
  - id: locals-as-template-context
    languages:
      - python
    message: "Using 'locals()' as a context to 'render(...)' is extremely dangerous.
      This exposes Python functions to the template that were not meant to be
      exposed. An attacker could use these functions to execute code that was
      not intended to run and could compromise the application. (This is
      server-side template injection (SSTI)). Do not use 'locals()'. Instead,
      specify each variable in a dictionary or 'django.template.Context' object,
      like '{\"var1\": \"hello\"}' and use that instead."
    metadata:
      category: security
      cwe:
        - "CWE-96: Improper Neutralization of Directives in Statically Saved
          Code ('Static Code Injection')"
      owasp:
        - A03:2021 - Injection
      references:
        - https://docs.djangoproject.com/en/3.2/ref/settings/#templates
        - https://docs.djangoproject.com/en/3.2/topics/templates/#django.template.backends.django.DjangoTemplates
        - https://docs.djangoproject.com/en/3.2/ref/templates/api/#rendering-a-context
      technology:
        - django
      subcategory:
        - audit
      likelihood: LOW
      impact: HIGH
      confidence: LOW
      license: Commons Clause License Condition v1.0[LGPL-2.1-only]
      vulnerability_class:
        - Code Injection
    pattern-either:
      - pattern: django.shortcuts.render(..., locals(...), ...)
      - pattern: django.template.Template.render(..., locals(...), ...)
      - patterns:
          - pattern-inside: |
              $CONTEXT = locals(...)
              ...
          - pattern-either:
              - pattern: django.shortcuts.render(..., $CONTEXT, ...)
              - pattern: django.template.Template.render(..., $CONTEXT, ...)
    severity: ERROR

Examples

locals-as-template-context.py

import base64
import mimetypes
import os

from django.core.urlresolvers import reverse
from django.http import HttpResponse
from django.shortcuts import redirect, render
from django.views.decorators.csrf import csrf_exempt
from django.template import Template

# adapted from https://github.com/mpirnat/lets-be-bad-guys/blob/7cbf11014bfc6dc9e199dc0b8a64e4597bc2338f/badguys/vulnerable/views.py#L95

def file_access(request):
    msg = request.GET.get('msg', '')
    # ok: locals-as-template-context
    return render(request, 'vulnerable/injection/file_access.html',
            {'msg': msg})


def bad1(request):
    # ruleid: locals-as-template-context
    response = render(request, 'vulnerable/xss/form.html', locals())
    response.set_cookie(key='monster', value='omnomnomnomnom!')
    return response


def bad2(request, path='default'):
    env = locals()
    # ruleid: locals-as-template-context
    return render(request, 'vulnerable/xss/path.html', env)


def bad3(request):
    # ruleid: locals-as-template-context
    response = Template.render(request, 'vulnerable/xss/form.html', locals())
    response.set_cookie(key='monster', value='omnomnomnomnom!')
    return response


def bad4(request, path='default'):
    env = locals()
    # ruleid: locals-as-template-context
    return Template.render(request, 'vulnerable/xss/path.html', env)