generated from ossf/project-template
-
Notifications
You must be signed in to change notification settings - Fork 184
pySCG: doc 798 #807
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
pySCG: doc 798 #807
Changes from 5 commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
7f7dd8d
in progress
myteron 70f0565
Merge branch 'main' of github.com:myteron/wg-best-practices-os-develo…
myteron 928df16
Merge branches 'main' and 'main' of github.com:myteron/wg-best-practi…
myteron 40369a1
adding documentation for CWE-798
myteron 9beb3dc
remove wrongly staged file
myteron e637469
adressed comments
myteron 7169edc
added most of the changes, updated code
myteron b1a4738
Update docs/Secure-Coding-Guide-for-Python/CWE-693/CWE-798/README.md
myteron 9c20398
changed noncompliant explaination text as per discussion with Hubert
myteron File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
187 changes: 187 additions & 0 deletions
187
docs/Secure-Coding-Guide-for-Python/CWE-693/CWE-798/README.md
This file contains hidden or 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,187 @@ | ||
# CWE-798: Use of hardcoded credentials | ||
|
||
Ensure that unique keys or secrets can be replaced or rejected at runtime and never hard-code sensitive information, such as passwords, and encryption keys in a component. | ||
|
||
User accounts are either for human or machine type of users. Machine users, such as a front end connecting to a backend `SQL`, have it easy to use complexity during identity verification. Hardcoded credentials for machine users are typically caused by a missing strategy or architecture infrastructure to establish trust between components at deployment time. Human users need a level of usability for their identity verification such as a combination of what they have and what they can remember. A human user Identity Management (IDM) system needs to support initial access and users forgetting passphrases or passwords without jeopardizing security. | ||
|
||
Examples of hard-coded sensitive information: | ||
|
||
* Default usernames with default password | ||
* Database credentials | ||
* API keys, tokens, SSH keys | ||
* Service account credentials or keys used for installation or management. | ||
* Default passwords for administrators after installation. | ||
* Backend IP Addressess | ||
|
||
Storing sensitive data as part of a components source code or deliverable package can result in legal consequences governed by: | ||
|
||
* Health Insurance Portability and Accountability Act (HIPAA) [US Congress 1996](https://aspe.hhs.gov/reports/health-insurance-portability-accountability-act-1996), | ||
* General Data Protection Regulation (GDPR) [European Parliament 2016](https://gdpr-info.eu/)" | ||
* California Consumer Privacy Act (CCPA) DIVISION 3. OBLIGATIONS 1427 - 3273.16 [CPPA 2025](https://leginfo.legislature.ca.gov/faces/codes_displaySection.xhtml?lawCode=CIV§ionNum=1798.150) | ||
|
||
Issues with hard-coded sensitive information include: | ||
|
||
* Implementation does not scale | ||
* Customers know each others passwords | ||
* Attackers can extract them from packages or byte-code `.pyo` or `.pyc` files | ||
* Hard to replace at runtime. | ||
|
||
## Non-Compliant Code Example | ||
|
||
The `noncompliant01.py` code `front_end` method simulates our front-end service that wants to connect to a simulated back-end service together with its deployment in one file. A real world example would have each running and delivered on their own. The `TestSimulateDeployingFrontEnd` unit test simulates a deployment of the `front_end`. The implementation of the `front_end` did not consider deployment in separation to delivering the functionality it provides. A real world example would have `front_end` packaging the password and IP information. | ||
myteron marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
||
[*noncompliant01.py*](noncompliant01.py) | ||
|
||
```py | ||
# SPDX-FileCopyrightText: OpenSSF project contributors | ||
# SPDX-License-Identifier: MIT | ||
""" Non-compliant Code Example """ | ||
import logging | ||
import unittest | ||
|
||
logging.basicConfig(encoding="utf-8", level=logging.DEBUG) | ||
|
||
|
||
def front_end(): | ||
"""Dummy method demonstrating noncompliant implementation""" | ||
# A noncompliant implementation would typically hardcode server_config | ||
# and load it from a project global python file or variable | ||
server_config = {} | ||
server_config["IP"] = "192.168.0.1" | ||
server_config["PORT"] = "192.168.0.1" | ||
server_config["USER"] = "admin" | ||
server_config["PASS"] = "SuperSecret123" | ||
|
||
# it would then use the configuration | ||
logging.debug("connecting to server IP %s", server_config["IP"]) | ||
logging.debug("connecting to server PORT %s", server_config["IP"]) | ||
logging.debug("connecting to server USER %s", server_config["USER"]) | ||
logging.debug("connecting to server PASS %s", server_config["PASS"]) | ||
|
||
|
||
class TestSimulateDeployingFrontEnd(unittest.TestCase): | ||
""" | ||
Simulate the deployment starting the front_end to connect | ||
to the backend | ||
""" | ||
|
||
def test_front_end(self): | ||
"""Verifiy front_end implementation""" | ||
front_end() | ||
|
||
|
||
|
||
myteron marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
myteron marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
if __name__ == "__main__": | ||
unittest.main() | ||
``` | ||
|
||
The `noncompliant01.py` example will print the hardcoded connection information and credential information `PASS SuperSecret123` in use. | ||
|
||
## Compliant Solution | ||
|
||
Create reusable components by separating deployment such as connection information and trust between a deployed front-end and back-end. | ||
|
||
||| | ||
|:---|:---| | ||
|__Anti-pattern__|__Recommended pattern__| | ||
|Passwords for machine to machine identity verification|time limited keys or access tokens that are unique per deployment or instances and get assigned at deployment time.| | ||
|Shared usernames|RBAC, ABAC or policy engines| | ||
|Hardcoded `UIDs`, `GIDs`|identity names| | ||
|Hardcoded `IPs` or ports|Rather than hardcoding IP addresses DNS should be properly implemented in the deployment in combination with solutions such as:<br>- `RFC 9250` - [DNS over Dedicated QUIC Connections (ietf.org)](https://datatracker.ietf.org/doc/rfc9250/)<br>- `RFC 7858` - [Specification for DNS over Transport Layer Security (TLS) (ietf.org)](https://datatracker.ietf.org/doc/html/rfc7858)<br>- `RFC 6494` - [Certificate Profile and Certificate Management for SEcure Neighbor Discovery (SEND) (ietf.org) for IPV6](https://datatracker.ietf.org/doc/rfc6494/)<br>- `DNSSEC` [RFC 9364](https://datatracker.ietf.org/doc/html/rfc9364), `RFC 6014`, `5155`, `4641`....<br><br>The order and ways to resolve IPs is configured via `/etc/nsswitch.conf` on most Unix systems.<br><br>Using `mTLS` with a high granularity of machine identities can reduce or remove `DNS` related risks.| | ||
|
||
The `compliant01.py` code is using a `config.ini` file that is created by the deployment represented by `TestSimulateDeployingFrontEnd` and provided to the deployed front_end method representing the component we deploy. Using configuration files such as `ini`, `yaml` or `json` allows a language independent solution (`bash` vs `python`). The deployment, represented by `TestSimulateDeployingFrontEnd`, steering these files also secures them by making them read only to a single user via `self.config_file_path.chmod(0o400)`. | ||
myteron marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
||
*[compliant01.py](compliant01.py):* | ||
|
||
```python | ||
""" Compliant Code Example """ | ||
# SPDX-FileCopyrightText: OpenSSF project contributors | ||
# SPDX-License-Identifier: MIT | ||
""" Compliant Code Example """ | ||
myteron marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
myteron marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
import logging | ||
from pathlib import Path | ||
import unittest | ||
import configparser | ||
|
||
logging.basicConfig(encoding="utf-8", level=logging.DEBUG) | ||
|
||
|
||
def front_end(config_file_path: Path): | ||
"""Dummy method demonstrating noncompliant implementation""" | ||
myteron marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
myteron marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
# A compliant loads connection information from a well protect file | ||
_config = configparser.ConfigParser() | ||
_config.read(config_file_path) | ||
|
||
# it would then use the configuration | ||
logging.debug("Loading deployment config %s", config_file_path.absolute()) | ||
logging.debug("connecting to server IP %s", _config["SERVER"]["IP"]) | ||
logging.debug("connecting to server PORT %s", _config["SERVER"]["PORT"]) | ||
logging.debug("connecting to server USER %s", _config["SERVER"]["USER"]) | ||
logging.debug("connecting to server pem %s", _config["SERVER"]["CERT_FILE"]) | ||
|
||
|
||
class TestSimulateDeployingFrontEnd(unittest.TestCase): | ||
""" | ||
Simulate the deployment starting the front_end to connect | ||
to the backend | ||
""" | ||
|
||
def setUp(self): | ||
|
||
config = configparser.ConfigParser() | ||
config["SERVER"] = { | ||
"IP": "192.168.0.1", | ||
"PORT": "8080", | ||
"USER": "admin", | ||
"CERT_FILE": "example.pem", | ||
} | ||
|
||
config["LOGGING"] = { | ||
"level": "DEBUG", | ||
} | ||
self.config_file_path = Path("config.ini", exist_ok=True) | ||
with open(self.config_file_path, "w", encoding="utf-8") as config_file: | ||
config.write(config_file) | ||
self.config_file_path.chmod(0o400) | ||
|
||
def test_front_end(self): | ||
"""Verify front_end implementation""" | ||
front_end(self.config_file_path) | ||
|
||
def tearDown(self): | ||
"""Clean up after us and remove the config file""" | ||
self.config_file_path.unlink() | ||
|
||
|
||
if __name__ == "__main__": | ||
unittest.main() | ||
``` | ||
|
||
The `compliant01.py` code avoids using password based authentication in the first place. It prints connection iformation only for convience here and should not be considered in a real world implementation as per [CWE-532: Insertion of Sensitive Information into Log File](https://best.openssf.org/Secure-Coding-Guide-for-Python/CWE-664/CWE-532/) [OSSF 2025]. | ||
myteron marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
||
## Automated Detection | ||
|
||
|Tool|Version|Checker|Description| | ||
|:---|:---|:---|:---| | ||
|Bandit|1.7.4 on Python 3.10.4|B105<br>B106<br>B107|[B105: hardcoded_password_string — Bandit documentation](https://bandit.readthedocs.io/en/latest/plugins/b105_hardcoded_password_string.html)<br>[B106: hardcoded_password_funcarg — Bandit documentation](https://bandit.readthedocs.io/en/latest/plugins/b106_hardcoded_password_funcarg.html)<br>[B107: hardcoded_password_default — Bandit documentation](https://bandit.readthedocs.io/en/latest/plugins/b107_hardcoded_password_default.html)| | ||
|sonarsource||RSPEC-2068<br>RSPEC-6437|[Python static code analysis: Hard-coded credentials are security-sensitive (sonarsource.com)](https://rules.sonarsource.com/python/RSPEC-2068)<br>[Credentials should not be hard-coded (sonarsource.com)](https://rules.sonarsource.com/python/type/Vulnerability/RSPEC-6437/)| | ||
|codeQL|||[Hard-coded credentials — CodeQL query help documentation (github.com)](https://codeql.github.com/codeql-query-help/python/py-hardcoded-credentials/)| | ||
|
||
## Related Guidelines | ||
|
||
||| | ||
|:---|:---| | ||
|[MITRE CWE](http://cwe.mitre.org/)|Pillar: [CWE-693: Protection Mechanism Failure (4.12) (mitre.org)](https://cwe.mitre.org/data/definitions/693.html)| | ||
|[MITRE CWE](http://cwe.mitre.org/)|Base: [CWE-798: Use of hardcoded credentials](https://cwe.mitre.org/data/definitions/798.html)| | ||
|[MITRE CWE](http://cwe.mitre.org/)|Variant: [CWE-259: Use of hardcoded password](https://cwe.mitre.org/data/definitions/259.html)| | ||
|[MITRE CWE](http://cwe.mitre.org/)|Variant: [CWE-321: Use of hardcode cryptographic key](https://cwe.mitre.org/data/definitions/321.html)| | ||
|[SEI CERT Oracle Codign Standard for Java](https://wiki.sei.cmu.edu/confluence/display/java/SEI+CERT+Oracle+Coding+Standard+for+Java)|[MSC03-J: Never hardcode sensitive information](https://wiki.sei.cmu.edu/confluence/display/java/MSC03-J.+Never+hard+code+sensitive+information)| | ||
|
||
## Bibliography | ||
|
||
||| | ||
|:---|:---| | ||
| [US Congress 1996] | Health Insurance Portability and Accountability Act (HIPAA) [online].Available from: [https://aspe.hhs.gov/reports/health-insurance-portability-accountability-act-1996](https://aspe.hhs.gov/reports/health-insurance-portability-accountability-act-1996) [accessed 27 Februrary 2025]| | ||
| [European Parliament 2016] | General Data Protection Regulation (GDPR) [online]. Available from: [https://leginfo.legislature.ca.gov/faces/codes_displaySection.xhtml?lawCode=CIV§ionNum=1798.150](https://leginfo.legislature.ca.gov/faces/codes_displaySection.xhtml?lawCode=CIV§ionNum=1798.150) [accessed 27 Februrary 2025]| | ||
| [CPPA 2025] |DIVISION 3. OBLIGATIONS [1427 - 3273.16] [online]. Available from: [https://leginfo.legislature.ca.gov/faces/codes_displaySection.xhtml?lawCode=CIV§ionNum=1798.150](https://leginfo.legislature.ca.gov/faces/codes_displaySection.xhtml?lawCode=CIV§ionNum=1798.150) [accessed 27 Februrary 2025]| | ||
| [OSSF 2025] | CWE-532: Insertion of Sensitive Information into Log File [online]. Available from: [https://best.openssf.org/Secure-Coding-Guide-for-Python/CWE-664/CWE-532/](https://best.openssf.org/Secure-Coding-Guide-for-Python/CWE-664/CWE-532/) [accessed 27 Februrary 2025]| |
61 changes: 58 additions & 3 deletions
61
docs/Secure-Coding-Guide-for-Python/CWE-693/CWE-798/compliant01.py
This file contains hidden or 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 |
---|---|---|
@@ -1,5 +1,60 @@ | ||
# SPDX-FileCopyrightText: OpenSSF project contributors | ||
# SPDX-License-Identifier: MIT | ||
""" Compliant Code Example """ | ||
import os | ||
print(os.environ["databaseIPAddress"]) | ||
"""Compliant Code Example""" | ||
myteron marked this conversation as resolved.
Show resolved
Hide resolved
myteron marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
import logging | ||
from pathlib import Path | ||
import unittest | ||
import configparser | ||
|
||
logging.basicConfig(encoding="utf-8", level=logging.DEBUG) | ||
|
||
|
||
def front_end(config_file_path: Path): | ||
"""Dummy method demonstrating noncompliant implementation""" | ||
myteron marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
myteron marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
# A compliant loads connection information from a well protect file | ||
myteron marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
_config = configparser.ConfigParser() | ||
_config.read(config_file_path) | ||
|
||
# it would then use the configuration | ||
myteron marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
logging.debug("Loading deployment config %s", config_file_path.absolute()) | ||
logging.debug("connecting to server IP %s", _config["SERVER"]["IP"]) | ||
logging.debug("connecting to server PORT %s", _config["SERVER"]["PORT"]) | ||
logging.debug("connecting to server USER %s", _config["SERVER"]["USER"]) | ||
logging.debug("connecting to server pem %s", _config["SERVER"]["CERT_FILE"]) | ||
|
||
|
||
class TestSimulateDeployingFrontEnd(unittest.TestCase): | ||
""" | ||
Simulate the deployment starting the front_end to connect | ||
to the backend | ||
""" | ||
|
||
def setUp(self): | ||
config = configparser.ConfigParser() | ||
config["SERVER"] = { | ||
"IP": "192.168.0.1", | ||
"PORT": "8080", | ||
"USER": "admin", | ||
"CERT_FILE": "example.pem", | ||
} | ||
|
||
config["LOGGING"] = { | ||
"level": "DEBUG", | ||
} | ||
self.config_file_path = Path("config.ini", exist_ok=True) | ||
with open(self.config_file_path, "w", encoding="utf-8") as config_file: | ||
config.write(config_file) | ||
self.config_file_path.chmod(0o400) | ||
|
||
def test_front_end(self): | ||
"""Verify front_end implementation""" | ||
front_end(self.config_file_path) | ||
|
||
def tearDown(self): | ||
"""Clean up after us and remove the config file""" | ||
self.config_file_path.unlink() | ||
|
||
|
||
if __name__ == "__main__": | ||
unittest.main() |
4 changes: 0 additions & 4 deletions
4
docs/Secure-Coding-Guide-for-Python/CWE-693/CWE-798/envars.sh
This file was deleted.
Oops, something went wrong.
41 changes: 38 additions & 3 deletions
41
docs/Secure-Coding-Guide-for-Python/CWE-693/CWE-798/noncompliant01.py
This file contains hidden or 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 |
---|---|---|
@@ -1,5 +1,40 @@ | ||
# SPDX-FileCopyrightText: OpenSSF project contributors | ||
# SPDX-License-Identifier: MIT | ||
""" Non-compliant Code Example """ | ||
databaseIPAddress = "192.168.0.1" | ||
print(databaseIPAddress) | ||
"""Non-compliant Code Example""" | ||
|
||
import logging | ||
import unittest | ||
|
||
logging.basicConfig(encoding="utf-8", level=logging.DEBUG) | ||
|
||
|
||
def front_end(): | ||
"""Dummy method demonstrating noncompliant implementation""" | ||
# A noncompliant implementation would typically hardcode server_config | ||
# and load it from a project global python file or variable | ||
server_config = {} | ||
server_config["IP"] = "192.168.0.1" | ||
server_config["PORT"] = "192.168.0.1" | ||
myteron marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
server_config["USER"] = "admin" | ||
server_config["PASS"] = "SuperSecret123" | ||
|
||
# it would then use the configuration | ||
myteron marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
logging.debug("connecting to server IP %s", server_config["IP"]) | ||
logging.debug("connecting to server PORT %s", server_config["IP"]) | ||
myteron marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
logging.debug("connecting to server USER %s", server_config["USER"]) | ||
logging.debug("connecting to server PASS %s", server_config["PASS"]) | ||
|
||
|
||
class TestSimulateDeployingFrontEnd(unittest.TestCase): | ||
""" | ||
Simulate the deployment starting the front_end to connect | ||
to the backend | ||
""" | ||
|
||
def test_front_end(self): | ||
"""Verifiy front_end implementation""" | ||
front_end() | ||
|
||
|
||
if __name__ == "__main__": | ||
unittest.main() |
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains hidden or 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
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.