diff --git a/docs/Secure-Coding-Guide-for-Python/CWE-693/CWE-798/README.md b/docs/Secure-Coding-Guide-for-Python/CWE-693/CWE-798/README.md new file mode 100644 index 00000000..854469be --- /dev/null +++ b/docs/Secure-Coding-Guide-for-Python/CWE-693/CWE-798/README.md @@ -0,0 +1,186 @@ +# 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 example is simulating a `front-end`, `back-end`, and its deployment in one file. A real world example would have each run and delivered separately. The `TestSimulateDeployingFrontEnd` unit-test simulates a deployment of the `front_end`. The implementation of the `front_end` did not consider leaving connection details to the deployment and hardcoded them instead. + +[*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"] = "8080" + 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["PORT"]) + 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() +``` + +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:
- `RFC 9250` - [DNS over Dedicated QUIC Connections (ietf.org)](https://datatracker.ietf.org/doc/rfc9250/)
- `RFC 7858` - [Specification for DNS over Transport Layer Security (TLS) (ietf.org)](https://datatracker.ietf.org/doc/html/rfc7858)
- `RFC 6494` - [Certificate Profile and Certificate Management for SEcure Neighbor Discovery (SEND) (ietf.org) for IPV6](https://datatracker.ietf.org/doc/rfc6494/)
- `DNSSEC` [RFC 9364](https://datatracker.ietf.org/doc/html/rfc9364), `RFC 6014`, `5155`, `4641`....

The order and ways to resolve IPs is configured via `/etc/nsswitch.conf` on most Unix systems.

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 to decouple connection information. The deployment represented by `TestSimulateDeployingFrontEnd` is now in full control of proving connectivity information to the `front-end` and `back-end`. 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)`. The password based identity verfication is replaced with a certificate based solution. + +*[compliant01.py](compliant01.py):* + +```python +# SPDX-FileCopyrightText: OpenSSF project contributors +# SPDX-License-Identifier: MIT +"""Compliant Code Example""" + +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): + """Simulating front end implementation""" + # A compliant solution loads connection information from a well-protected 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 information only for convenience 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]. + +## Automated Detection + +|Tool|Version|Checker|Description| +|:---|:---|:---|:---| +|Bandit|1.7.4 on Python 3.10.4|B105
B106
B107|[B105: hardcoded_password_string — Bandit documentation](https://bandit.readthedocs.io/en/latest/plugins/b105_hardcoded_password_string.html)
[B106: hardcoded_password_funcarg — Bandit documentation](https://bandit.readthedocs.io/en/latest/plugins/b106_hardcoded_password_funcarg.html)
[B107: hardcoded_password_default — Bandit documentation](https://bandit.readthedocs.io/en/latest/plugins/b107_hardcoded_password_default.html)| +|sonarsource||RSPEC-2068
RSPEC-6437|[Python static code analysis: Hard-coded credentials are security-sensitive (sonarsource.com)](https://rules.sonarsource.com/python/RSPEC-2068)
[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]| diff --git a/docs/Secure-Coding-Guide-for-Python/CWE-693/CWE-798/compliant01.py b/docs/Secure-Coding-Guide-for-Python/CWE-693/CWE-798/compliant01.py index 0daa53f1..56ec0f91 100644 --- a/docs/Secure-Coding-Guide-for-Python/CWE-693/CWE-798/compliant01.py +++ b/docs/Secure-Coding-Guide-for-Python/CWE-693/CWE-798/compliant01.py @@ -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""" + +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): + """Simulating front end implementation""" + # A compliant solution loads connection information from a well-protected 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() diff --git a/docs/Secure-Coding-Guide-for-Python/CWE-693/CWE-798/envars.sh b/docs/Secure-Coding-Guide-for-Python/CWE-693/CWE-798/envars.sh deleted file mode 100644 index 2545e06e..00000000 --- a/docs/Secure-Coding-Guide-for-Python/CWE-693/CWE-798/envars.sh +++ /dev/null @@ -1,4 +0,0 @@ -# SPDX-FileCopyrightText: OpenSSF project contributors -# SPDX-License-Identifier: MIT -#!/bin/bash -export databaseIPAddress="192.168.0.1" # export the environment variable \ No newline at end of file diff --git a/docs/Secure-Coding-Guide-for-Python/CWE-693/CWE-798/noncompliant01.py b/docs/Secure-Coding-Guide-for-Python/CWE-693/CWE-798/noncompliant01.py index 1292e8cc..da707072 100644 --- a/docs/Secure-Coding-Guide-for-Python/CWE-693/CWE-798/noncompliant01.py +++ b/docs/Secure-Coding-Guide-for-Python/CWE-693/CWE-798/noncompliant01.py @@ -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"] = "8080" + 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["PORT"]) + 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() diff --git a/docs/Secure-Coding-Guide-for-Python/CWE-693/CWE-798/setup.sh b/docs/Secure-Coding-Guide-for-Python/CWE-693/CWE-798/setup.sh deleted file mode 100644 index 7aac2f9b..00000000 --- a/docs/Secure-Coding-Guide-for-Python/CWE-693/CWE-798/setup.sh +++ /dev/null @@ -1,9 +0,0 @@ -# SPDX-FileCopyrightText: OpenSSF project contributors -# SPDX-License-Identifier: MIT -#!/bin/bash -# creating the envars.sh file - -mkdir ~/secure # Make a directory for storing secure data -chmod 700 ~/secure # Make the directory only accessible by the logged in user -touch ~/secure/envars.sh # create a file for storing the environment variable -chmod +x ~/secure/envars.sh # make the file executable \ No newline at end of file diff --git a/docs/Secure-Coding-Guide-for-Python/CWE-693/CWE-798/start.sh b/docs/Secure-Coding-Guide-for-Python/CWE-693/CWE-798/start.sh deleted file mode 100644 index beb7d6cf..00000000 --- a/docs/Secure-Coding-Guide-for-Python/CWE-693/CWE-798/start.sh +++ /dev/null @@ -1,6 +0,0 @@ -# SPDX-FileCopyrightText: OpenSSF project contributors -# SPDX-License-Identifier: MIT -#!/bin/bash -source ~/secure/envars.sh # Set the environment variables for the current bash session -python3 compliant01.py # Run the script requiring the secured variable -rm ~/secure/envars.sh # Remove the file \ No newline at end of file diff --git a/docs/Secure-Coding-Guide-for-Python/readme.md b/docs/Secure-Coding-Guide-for-Python/readme.md index ae431fe1..ec0834c5 100644 --- a/docs/Secure-Coding-Guide-for-Python/readme.md +++ b/docs/Secure-Coding-Guide-for-Python/readme.md @@ -75,7 +75,7 @@ It is __not production code__ and requires code-style or python best practices t |:----------------------------------------------------------------|:----| |[CWE-184: Incomplete List of Disallowed Input](CWE-693/CWE-184/.)|| |[CWE-330: Use of Insufficiently Random Values](CWE-693/CWE-330/README.md)|[CVE-2020-7548](https://www.cvedetails.com/cve/CVE-2020-7548),
CVSSv3.1: __9.8__,
EPSS: __0.22__ (12.12.2024)| -|[CWE-798: Use of hardcoded credentials](CWE-693/CWE-798/.)|| +|[CWE-798: Use of hardcoded credentials](CWE-693/CWE-798/README.md)|| |[CWE-697: Incorrect Comparison](https://cwe.mitre.org/data/definitions/703.html)|Prominent CVE| |:----------------------------------------------------------------|:----|