python.django.security.injection.path-traversal.path-traversal-join.path-traversal-join

profile photo of semgrepsemgrep
Author
6,591
Download Count*

Data from request is passed to os.path.join() and to open(). This is a path traversal vulnerability, which can lead to sensitive data being leaked. To mitigate, consider using os.path.abspath or os.path.realpath or Path library.

Run Locally

Run in CI

Defintion

rules:
  - id: path-traversal-join
    message: Data from request is passed to os.path.join() and to open(). This is a
      path traversal vulnerability, which can lead to sensitive data being
      leaked. To mitigate, consider using os.path.abspath or os.path.realpath or
      Path library.
    metadata:
      cwe:
        - "CWE-22: Improper Limitation of a Pathname to a Restricted Directory
          ('Path Traversal')"
      owasp:
        - A05:2017 - Broken Access Control
        - A01:2021 - Broken Access Control
      references:
        - https://owasp.org/www-community/attacks/Path_Traversal
      category: security
      technology:
        - django
      cwe2022-top25: true
      cwe2021-top25: true
      subcategory:
        - audit
      likelihood: LOW
      impact: LOW
      confidence: LOW
      license: Commons Clause License Condition v1.0[LGPL-2.1-only]
      vulnerability_class:
        - Path Traversal
    patterns:
      - pattern-inside: |
          def $F(...):
            ...
      - pattern-not-inside: |
          def $F(...):
            ...
            os.path.abspath(...)
            ...
      - pattern-not-inside: |
          def $F(...):
            ...
            os.path.realpath(...)
            ...
      - pattern-either:
          - pattern: open(os.path.join(..., request.$W.get(...), ...), ...)
          - pattern: open(os.path.join(..., request.$W(...), ...), ...)
          - pattern: open(os.path.join(..., request.$W, ...), ...)
          - pattern: open(os.path.join(..., request.$W[...], ...), ...)
          - pattern: |
              $P = os.path.join(..., request.$W.get(...), ...)
              ...
              open($P, ...)
          - pattern: |
              $P = os.path.join(..., request.$W(...), ...)
              ...
              open($P, ...)
          - pattern: |
              $P = os.path.join(..., request.$W, ...)
              ...
              open($P, ...)
          - pattern: |
              $P = os.path.join(..., request.$W[...], ...)
              ...
              open($P, ...)
          - pattern: |
              $V = request.$W.get($X)
              ...
              $P = os.path.join(..., $V, ...)
              ...
              open($P, ...)
          - pattern: |
              $V = request.$W($X)
              ...
              $P = os.path.join(..., $V, ...)
              ...
              open($P, ...)
          - pattern: |
              $V = request.$W[$X]
              ...
              $P = os.path.join(..., $V, ...)
              ...
              open($P, ...)
          - pattern: |
              $V = request.$W
              ...
              $P = os.path.join(..., $V, ...)
              ...
              open($P, ...)
          - pattern: |
              $P = request.$W.get(...)
              ...
              open(os.path.join(..., $P, ...), ...)
          - pattern: |
              $P = request.$W(...)
              ...
              open(os.path.join(..., $P, ...), ...)
          - pattern: |
              $P = request.$W
              ...
              open(os.path.join(..., $P, ...), ...)
          - pattern: |
              $P = request.$W[...]
              ...
              open(os.path.join(..., $P, ...), ...)
    languages:
      - python
    severity: WARNING

Examples

path-traversal-join.py

from django.http import HttpResponse
import os

def foo_1(request):
  # ruleid: path-traversal-join
  param = request.GET.get('param')
  file_path = os.path.join("MY_SECRET", param)
  f = open(file_path, 'r')
  return HttpResponse(content=f, content_type="text/plain")

def foo_2(request):
  # ok due to abspath
  param = request.GET.get('param')
  file_path = os.path.join("MY_SECRET", param)
  file_path = os.path.abspath(file_path)
  f = open(file_path, 'r')
  return HttpResponse(content=f, content_type="text/plain")

def user_pic(request):
    """A view that is vulnerable to malicious file access."""

    base_path = os.path.join(os.path.dirname(__file__), '../../badguys/static/images')
    # ruleid: path-traversal-join
    filename = request.GET.get('p')

    data = open(os.path.join(base_path, filename), 'rb').read()

    return HttpResponse(data, content_type=mimetypes.guess_type(filename)[0])