-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(python): path traversal CWE-22 (#403)
- Loading branch information
Showing
6 changed files
with
404 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,241 @@ | ||
imports: | ||
- python_shared_lang_dynamic_input | ||
- python_shared_lang_import1 | ||
- python_shared_lang_import2 | ||
patterns: | ||
- pattern: open($<UNSANITIZED_DYNAMIC_INPUT>$<...>) | ||
filters: | ||
- variable: UNSANITIZED_DYNAMIC_INPUT | ||
detection: python_lang_path_traversal_dynamic_input | ||
scope: result | ||
- pattern: $<FILEINPUT>($<...>files=$<UNSANITIZED_DYNAMIC_INPUT>$<...>) | ||
filters: | ||
- variable: FILEINPUT | ||
detection: python_shared_lang_import1 | ||
scope: cursor | ||
filters: | ||
- variable: MODULE1 | ||
values: [fileinput] | ||
- variable: NAME | ||
values: | ||
- input | ||
- FileInput | ||
- variable: UNSANITIZED_DYNAMIC_INPUT | ||
detection: python_lang_path_traversal_dynamic_input | ||
scope: result | ||
- pattern: $<IO>($<UNSANITIZED_DYNAMIC_INPUT>$<...>) | ||
filters: | ||
- variable: UNSANITIZED_DYNAMIC_INPUT | ||
detection: python_lang_path_traversal_dynamic_input | ||
scope: result | ||
- variable: IO | ||
detection: python_shared_lang_import1 | ||
scope: cursor | ||
filters: | ||
- variable: MODULE1 | ||
values: [io] | ||
- variable: NAME | ||
values: | ||
- open | ||
- open_code | ||
- pattern: $<OS>($<UNSANITIZED_DYNAMIC_INPUT>) | ||
filters: | ||
- variable: UNSANITIZED_DYNAMIC_INPUT | ||
detection: python_lang_path_traversal_dynamic_input | ||
scope: result | ||
- variable: OS | ||
detection: python_shared_lang_import1 | ||
scope: cursor | ||
filters: | ||
- variable: MODULE1 | ||
values: [os] | ||
- variable: NAME | ||
values: | ||
- listdir | ||
- chdir | ||
- mkdir | ||
- makedirs | ||
- open | ||
- rmdir | ||
- remove | ||
- rename | ||
- unlink | ||
- pattern: $<OS_PATH>($<UNSANITIZED_DYNAMIC_INPUT>) | ||
filters: | ||
- variable: UNSANITIZED_DYNAMIC_INPUT | ||
detection: python_lang_path_traversal_dynamic_input | ||
scope: result | ||
- variable: OS_PATH | ||
detection: python_shared_lang_import2 | ||
scope: cursor | ||
filters: | ||
- variable: MODULE1 | ||
values: [os] | ||
- variable: MODULE2 | ||
values: [path] | ||
- variable: NAME | ||
values: [join] | ||
- pattern: $<SHUTIL>($<SOURCE>, $<DEST>, $<...>) | ||
filters: | ||
- either: | ||
- variable: SOURCE | ||
detection: python_lang_path_traversal_dynamic_input | ||
scope: result | ||
- variable: DEST | ||
detection: python_lang_path_traversal_dynamic_input | ||
scope: result | ||
- variable: SHUTIL | ||
detection: python_shared_lang_import1 | ||
scope: cursor | ||
filters: | ||
- variable: MODULE1 | ||
values: [shutil] | ||
- variable: NAME | ||
values: | ||
- copy | ||
- copy2 | ||
- copyfile | ||
- copymode | ||
- copystat | ||
- copytree | ||
- pattern: $<SHUTIL>($<UNSANITIZED_DYNAMIC_INPUT>$<...>) | ||
filters: | ||
- variable: UNSANITIZED_DYNAMIC_INPUT | ||
detection: python_lang_path_traversal_dynamic_input | ||
scope: result | ||
- variable: SHUTIL | ||
detection: python_shared_lang_import1 | ||
scope: cursor | ||
filters: | ||
- variable: MODULE1 | ||
values: [shutil] | ||
- variable: NAME | ||
values: [rmtree] | ||
- pattern: $<PATH>.$<METHOD>($<...>) | ||
filters: | ||
- variable: PATH | ||
detection: python_lang_path_traversal_path_module_init_with_user_input | ||
scope: result | ||
- variable: METHOD | ||
values: | ||
- joinpath | ||
- mkdir | ||
- open | ||
- read_bytes | ||
- read_text | ||
- rename | ||
- replace | ||
- rmdir | ||
- symlink_to | ||
- touch | ||
- unlink | ||
- walk | ||
- write_bytes | ||
- write_text | ||
- pattern: $<PATH>.$<METHOD>($<UNSANITIZED_DYNAMIC_INPUT>) | ||
filters: | ||
- variable: UNSANITIZED_DYNAMIC_INPUT | ||
detection: python_lang_path_traversal_dynamic_input | ||
scope: result | ||
- variable: PATH | ||
detection: python_lang_path_traversal_path_module_init_without_user_input | ||
scope: result | ||
- variable: METHOD | ||
values: | ||
- joinpath | ||
- rename | ||
- replace | ||
- symlink_to | ||
- touch | ||
auxiliary: | ||
- id: python_lang_path_traversal_dynamic_input | ||
sanitizer: python_lang_path_traversal_sanitizer | ||
patterns: | ||
- pattern: $<UNSANITIZED_DYNAMIC_INPUT> | ||
filters: | ||
- variable: UNSANITIZED_DYNAMIC_INPUT | ||
detection: python_shared_lang_dynamic_input | ||
scope: result | ||
- id: python_lang_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] | ||
- id: python_lang_path_traversal_path_module_init_without_user_input | ||
patterns: | ||
- pattern: $<PATH_MODULE> | ||
filters: | ||
- variable: PATH_MODULE | ||
detection: python_lang_path_traversal_path_module | ||
- pattern: $<PATH_MODULE>($<...>) | ||
filters: | ||
- variable: PATH_MODULE | ||
detection: python_lang_path_traversal_path_module | ||
- id: python_lang_path_traversal_path_module_init_with_user_input | ||
patterns: | ||
- pattern: $<PATH_MODULE>($<UNSANITIZED_DYNAMIC_INPUT>) | ||
filters: | ||
- variable: PATH_MODULE | ||
detection: python_lang_path_traversal_path_module | ||
- variable: UNSANITIZED_DYNAMIC_INPUT | ||
detection: python_lang_path_traversal_dynamic_input | ||
scope: result | ||
- id: python_lang_path_traversal_path_module | ||
patterns: | ||
- pattern: $<PATH_MODULE> | ||
filters: | ||
- variable: PATH_MODULE | ||
detection: python_shared_lang_import1 | ||
scope: cursor | ||
filters: | ||
- variable: MODULE1 | ||
values: [pathlib] | ||
- variable: NAME | ||
values: | ||
- Path | ||
- PurePath | ||
- WindowsPath | ||
- PureWindowsPath | ||
- PosixPath | ||
- PurePosixPath | ||
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_lang_path_traversal | ||
documentation_url: https://docs.bearer.com/reference/rules/python_lang_path_traversal |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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: [] | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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: [] | ||
}) | ||
}) | ||
}) |
Oops, something went wrong.