Skip to content

Commit

Permalink
feat(python/django): path traversal rule
Browse files Browse the repository at this point in the history
  • Loading branch information
elsapet committed May 16, 2024
1 parent d4b11a7 commit 67b9262
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 0 deletions.
98 changes: 98 additions & 0 deletions rules/python/django/path_traversal.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
imports:
- python_shared_lang_dynamic_input
- python_shared_lang_import2
- python_shared_lang_import4
patterns:
- pattern: $<FILE_SYSTEM_STORAGE>($<...>$<UNSANITIZED_DYNAMIC_INPUT>$<...>)
filters:
- variable: UNSANITIZED_DYNAMIC_INPUT
detection: python_django_path_traversal_dynamic_input
scope: result
- variable: FILE_SYSTEM_STORAGE
detection: python_shared_lang_import4
scope: cursor
filters:
- variable: MODULE1
values: [django]
- variable: MODULE2
values: [core]
- variable: MODULE3
values: [files]
- variable: MODULE4
values: [storage]
- variable: NAME
values: [FileSystemStorage]
- pattern: $<DEFAULT_STORAGE>.save($<UNSANITIZED_DYNAMIC_INPUT>, $<...>)
filters:
- variable: DEFAULT_STORAGE
detection: python_shared_lang_import4
scope: cursor
filters:
- variable: MODULE1
values: [django]
- variable: MODULE2
values: [core]
- variable: MODULE3
values: [files]
- variable: MODULE4
values: [storage]
- variable: NAME
values: [default_storage]
- variable: UNSANITIZED_DYNAMIC_INPUT
detection: python_django_path_traversal_dynamic_input
scope: result
auxiliary:
- id: python_django_path_traversal_dynamic_input
sanitizer: python_django_path_traversal_sanitizer
patterns:
- pattern: $<UNSANITIZED_DYNAMIC_INPUT>
filters:
- variable: UNSANITIZED_DYNAMIC_INPUT
detection: python_shared_lang_dynamic_input
scope: cursor
- id: python_django_path_traversal_sanitizer
patterns:
- pattern: $<OS_PATH_NORMPATH>()
filters:
- variable: OS_PATH_NORMPATH
detection: python_shared_lang_import2
scope: result
filters:
- variable: MODULE1
values: [os]
- variable: MODULE2
values: [path]
- variable: NAME
values: [normpath]
languages:
- python
severity: high
metadata:
description: Unsanitized dynamic input in file path
remediation_message: |
## Description
Using unsanitized dynamic input to determine file paths can allow attackers to gain access to files and folders outside of the intended scope. This vulnerability occurs when input provided by users is directly used to access the filesystem without proper validation or sanitization.
## Remediations
- **Do not** directly use external input to construct file paths. This can lead to unauthorized file access.
- **Do** sanitize external input used in file paths. Use `os.path.normpath` to normalize paths and remove any redundant separators in order to prevent path traversal attacks.
```python
os.path.normpath(os.path.join(base_directory, user_input))
```
- **Do** use absolute path checks to confirm that the constructed path is within the expected directory
```python
base = os.path.abspath(base_directory)
user_path = os.path.abspath(os.path.join(base_directory, user_input))
if user_path.startswith(base)
# Handle or reject the input
```
## References
- [OWASP Path Traversal](https://owasp.org/www-community/attacks/Path_Traversal)
cwe_id:
- 22
id: python_django_path_traversal
documentation_url: https://docs.bearer.com/reference/rules/python_django_path_traversal
20 changes: 20 additions & 0 deletions tests/python/django/path_traversal/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const {
createNewInvoker,
getEnvironment,
} = require("../../../helper.js")
const { ruleId, ruleFile, testBase } = getEnvironment(__dirname)

describe(ruleId, () => {
const invoke = createNewInvoker(ruleId, ruleFile, testBase)

test("path_traversal", () => {
const testCase = "main.py"

const results = invoke(testCase)

expect(results).toEqual({
Missing: [],
Extra: []
})
})
})
13 changes: 13 additions & 0 deletions tests/python/django/path_traversal/testdata/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from django.core.files.storage import FileSystemStorage as FSS

# bearer:expected python_django_path_traversal
fs = FSS(sys.argv[0])
request_file = request.FILES['document']
file = fs.save(request_file)

from django.core.files.storage import default_storage
import os.path

default_storage.save(os.path.normpath(sys.argv[1]))
x = os.path.normpath(sys.argv[2])
default_storage.save(x)

0 comments on commit 67b9262

Please sign in to comment.