python.django.security.injection.raw-html-format.raw-html-format

profile photo of semgrepsemgrep
Author
unknown
Download Count*

Detected user input flowing into a manually constructed HTML string. You may be accidentally bypassing secure methods of rendering HTML by manually constructing HTML and this could create a cross-site scripting vulnerability, which could let attackers steal sensitive user data. To be sure this is safe, check that the HTML is rendered safely. Otherwise, use templates (django.shortcuts.render) which will safely render HTML instead.

Run Locally

Run in CI

Defintion

rules:
  - id: raw-html-format
    languages:
      - python
    severity: WARNING
    message: Detected user input flowing into a manually constructed HTML string.
      You may be accidentally bypassing secure methods of rendering HTML by
      manually constructing HTML and this could create a cross-site scripting
      vulnerability, which could let attackers steal sensitive user data. To be
      sure this is safe, check that the HTML is rendered safely. Otherwise, use
      templates (`django.shortcuts.render`) which will safely render HTML
      instead.
    metadata:
      cwe:
        - "CWE-79: Improper Neutralization of Input During Web Page Generation
          ('Cross-site Scripting')"
      owasp:
        - A07:2017 - Cross-Site Scripting (XSS)
        - A03:2021 - Injection
      category: security
      technology:
        - django
      references:
        - https://docs.djangoproject.com/en/3.2/topics/http/shortcuts/#render
        - https://docs.djangoproject.com/en/3.2/topics/security/#cross-site-scripting-xss-protection
      license: Commons Clause License Condition v1.0[LGPL-2.1-only]
      cwe2022-top25: true
      cwe2021-top25: true
      subcategory:
        - vuln
      likelihood: HIGH
      impact: MEDIUM
      confidence: MEDIUM
      vulnerability_class:
        - Cross-Site-Scripting (XSS)
    mode: taint
    pattern-sanitizers:
      - pattern: django.utils.html.escape(...)
    pattern-sources:
      - patterns:
          - pattern: request.$ANYTHING
          - pattern-not: request.build_absolute_uri
    pattern-sinks:
      - patterns:
          - pattern-either:
              - patterns:
                  - pattern-either:
                      - pattern: '"$HTMLSTR" % ...'
                      - pattern: '"$HTMLSTR".format(...)'
                      - pattern: '"$HTMLSTR" + ...'
                      - pattern: f"$HTMLSTR{...}..."
              - patterns:
                  - pattern-inside: |
                      $HTML = "$HTMLSTR"
                      ...
                  - pattern-either:
                      - pattern: $HTML % ...
                      - pattern: $HTML.format(...)
                      - pattern: $HTML + ...
          - metavariable-pattern:
              metavariable: $HTMLSTR
              language: generic
              pattern: <$TAG ...

Examples

raw-html-format.py

from django.shortcuts import render
from django.shortcuts import render_to_response
from django.utils.html import escape

class FalsePositiveCheck499View(VulnerableTemplateView):
    title = '(almost) Cross-Site Scripting'
    tags = ['false-positive', 'GET', 'filtered']
    description = 'Echo query string parameter to HTML tag attribute removing'\
                  ' the single quotes which are present in the input.'
    url_path = '499_check.py?text=1'
    false_positive_check = True
    references = ['https://github.com/andresriancho/w3af/pull/499']

    def get(self, request, *args, **kwds):
        context = self.get_context_data()

        text = request.GET['text']
        text = text.replace('"', '')

        link = '<a href="http://external/abc/%s">Check link href</a>'

        # ruleid: raw-html-format
        context['html'] = link % text

        return render(request, self.template_name, context)

    def getB(self, request, *args, **kwds):
        context = self.get_context_data()

        text = request.GET['text']
        text = text.replace('"', '')

        # ruleid: raw-html-format
        context['html'] = '<a href="http://external/abc/' + text + '">Check link href</a>'

        return render(request, self.template_name, context)

    def get2(self, request, *args, **kwds):
        context = self.get_context_data()

        text = request.GET['text']
        text = text.replace('"', '')

        link = '<a href="http://external/abc/{}">Check link href</a>'

        # ruleid: raw-html-format
        context['html'] = link.format(text)

        return render(request, self.template_name, context)

    def get3(self, request, *args, **kwds):
        context = self.get_context_data()

        text = request.GET['text']
        text = text.replace('"', '')

        # ruleid: raw-html-format
        context['html'] = '<a href="http://external/abc/%s">Check link href</a>' % text

        return render(request, self.template_name, context)

    def get4(self, request, *args, **kwds):
        context = self.get_context_data()

        text = request.GET['text']
        text = text.replace('"', '')

        # ruleid: raw-html-format
        context['html'] = '<a href="http://external/abc/%s">Check link href</a>'.format(text)

        return render(request, self.template_name, context)

    def get5(self, request, *args, **kwds):
        context = self.get_context_data()

        text = request.GET['text']
        text = text.replace('"', '')

        # ruleid: raw-html-format
        context['html'] = f'<a href="http://external/abc/{text}">Check link href</a>'

        return render(request, self.template_name, context)

    def ok(self, request, *args, **kwds):
        context = self.get_context_data()

        text = request.GET['text']
        text = text.replace('"', '')

        link = 'something other than html, %s!'

        # ok: raw-html-format
        context['html'] = link % text

        return render(request, self.template_name, context)

    def ok2(self, request, *args, **kwds):
        context = self.get_context_data()

        text = request.GET['text']
        text = text.replace('"', '')

        # ok: raw-html-format
        context['html'] = 'this is a random string. {}'.format(text)

        return render(request, self.template_name, context)

    def ok3(self, request):
        # ok: raw-html-format
        msg += ' (<a href="{}" target="_blank" rel="noopener">{}</a>)'.format(
            request.build_absolute_uri(reverse('source')),
            gettext('source code')
        )

    def ok4(self, request):
        form = CreateQuestionForm(request.POST)
        if '_popup' in request.GET and not error:
            # ok: raw-html-format
            resp = '<script type="text/javascript">opener.dismissAddAnotherPopupDojo(window, "%s", "%s");</script>' \
                % (escape(form.cleaned_data['something']), escape(form.cleaned_data['text']))
            resp += '<script type="text/javascript">window.close();</script>'
            return HttpResponse(resp)