Skip to content

Commit

Permalink
feat(python): file permissions (CWE-276) (#420)
Browse files Browse the repository at this point in the history
  • Loading branch information
elsapet authored May 27, 2024
1 parent 4290656 commit 5190cbf
Show file tree
Hide file tree
Showing 6 changed files with 263 additions and 0 deletions.
67 changes: 67 additions & 0 deletions rules/python/django/file_permissions.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
imports:
- python_shared_lang_import4
patterns:
- pattern: FILE_UPLOAD_PERMISSIONS = $<MODE>
filters:
- variable: MODE
detection: python_django_file_permissions_modes
- pattern: $<FILE_SYSTEM_STORAGE>($<...>$<PARAM>=$<MODE>$<...>)
filters:
- variable: PARAM
values:
- file_permissions_mode
- directory_permissions_mode
- variable: MODE
detection: python_django_file_permissions_modes
- 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]
auxiliary:
- id: python_django_file_permissions_modes
patterns:
- pattern: $<MASK>
filters:
- either:
- variable: MASK
regex: \A0o?7
- variable: MASK
regex: \A0o?\d[1-7]
- variable: MASK
regex: \A0o?\d\d[1-7]
languages:
- python
severity: high
metadata:
description: Permissive file assignment
remediation_message: |-
## Description
Permissive file assignment exposes sensitive information by granting unnecessary read, write, or execute permissions to users without ownership privileges.
## Remediations
- **Do** keep file permissions as restrictive as possible to minimize the risk of unauthorized access. Use the principle of least privilege to only grant permissions that are absolutely necessary for the operation of the application.
```python
FileSystemStorage(location="my_file.txt", file_permissions_mode=0o500) # only you have full read and write access
```
- **Do** prefer assigning file permissions to 'groups' rather than 'other' when you need to extend privileges to users who are not the owners. This approach helps in limiting access to a more controlled set of users.
- **Do** set an appropriate default value for file permissions mode in setting.py
```python
# settings.py
FILE_UPLOAD_PERMISSIONS = 0o600
```
cwe_id:
- 276
id: python_django_file_permissions
documentation_url: https://docs.bearer.com/reference/rules/python_django_file_permissions
120 changes: 120 additions & 0 deletions rules/python/lang/file_permissions.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
imports:
- python_shared_lang_import1
patterns:
- pattern: $<OS>($<_>, $<MODE>$<...>)
filters:
- variable: OS
detection: python_shared_lang_import1
scope: cursor
filters:
- variable: MODULE1
values: [os]
- variable: NAME
values:
- chmod
- fchmod
- lchmod
- variable: MODE
detection: python_lang_file_permissions_modes
scope: result
- pattern: $<OS>(0)
filters:
- variable: OS
detection: python_shared_lang_import1
scope: cursor
filters:
- variable: MODULE1
values: [os]
- variable: NAME
values:
- umask
- pattern: $<PATH>.$<METHOD>($<MODE>$<...>)
filters:
- variable: PATH
detection: python_lang_file_permissions_path_module_init
scope: cursor
- variable: METHOD
values:
- chmod
- lchmod
- variable: MODE
detection: python_lang_file_permissions_modes
scope: result
auxiliary:
- id: python_lang_file_permissions_modes
patterns:
- pattern: $<STAT_PERMISSION>
filters:
- variable: STAT_PERMISSION
detection: python_shared_lang_import1
scope: cursor
filters:
- variable: MODULE1
values: [stat]
- variable: NAME
values:
- S_IWGRP
- S_IXGRP
- S_IRWXG
- S_IWOTH
- S_IXOTH
- S_IRWXO
- pattern: $<MASK>
filters:
- either:
- variable: MASK
regex: \A0o?7
- variable: MASK
regex: \A0o?\d[1-7]
- variable: MASK
regex: \A0o?\d\d[1-7]
- id: python_lang_file_permissions_path_module_init
patterns:
- pattern: $<PATH_MODULE>($<...>)
filters:
- variable: PATH_MODULE
detection: python_lang_file_permissions_path_module
scope: cursor
- id: python_lang_file_permissions_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: Permissive file assignment
remediation_message: |-
## Description
Permissive file assignment exposes sensitive information by granting unnecessary read, write, or execute permissions to users without ownership privileges.
## Remediations
- **Do** keep file permissions as restrictive as possible to minimize the risk of unauthorized access. Use the principle of least privilege to only grant permissions that are absolutely necessary for the operation of the application.
```python
os.chmod("my_private_file.txt", 0o600) # only you have full read and write access
```
- **Do** prefer assigning file permissions to 'groups' rather than 'other' when you need to extend privileges to users who are not the owners. This approach helps in limiting access to a more controlled set of users.
- **Do not** set a permissive `umask` value, as this can lead to overly permissive default permissions for new files and directories
```python
os.umask(0) # unsafe
```
cwe_id:
- 276
id: python_lang_file_permissions
documentation_url: https://docs.bearer.com/reference/rules/python_lang_file_permissions
20 changes: 20 additions & 0 deletions tests/python/django/file_permissions/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("file_permissions", () => {
const testCase = "main.py"

const results = invoke(testCase)

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

# bearer:expected python_django_file_permissions
fs = FileSystemStorage(location="my_file.txt", file_permissions_mode=0o777)

# ok
fs = FileSystemStorage(location="my_file.txt", file_permissions_mode=0o600)

# settings.py
# bearer:expected python_django_file_permissions
FILE_UPLOAD_PERMISSIONS = 0o777

# ok
FILE_UPLOAD_PERMISSIONS = 0o600
20 changes: 20 additions & 0 deletions tests/python/lang/file_permissions/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("file_permissions", () => {
const testCase = "main.py"

const results = invoke(testCase)

expect(results).toEqual({
Missing: [],
Extra: []
})
})
})
22 changes: 22 additions & 0 deletions tests/python/lang/file_permissions/testdata/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import os
import stat
from pathlib import Path

def bad():
p = Path("sensitive_file.py")
# bearer:expected python_lang_file_permissions
p.chmod(0o777)

# bearer:expected python_lang_file_permissions
os.umask(0)

# bearer:expected python_lang_file_permissions
os.lchmod("sensitive_file.py", stat.S_IWOTH)

def ok():
p = Path("sensitive_file.py")
p.chmod(0o600)

os.umask(18)

os.lchmod("sensitive_file.py", stat.S_IXUSR)

0 comments on commit 5190cbf

Please sign in to comment.