-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add rule to check for improper random generator usage (#394)
Specifically in the hashlib module, it specifies that secure alternatives to the random module should be used for crypto functions. This rule checks various hashlib functions where a salt is provided via an insecure random function such as random.randbytes() or ssl.RAND_bytes() Closes #229 Signed-off-by: Eric Brown <eric.brown@securesauce.dev>
- Loading branch information
Showing
10 changed files
with
254 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
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,10 @@ | ||
--- | ||
id: PY035 | ||
title: hashlib — improper prng | ||
hide_title: true | ||
pagination_prev: null | ||
pagination_next: null | ||
slug: /rules/PY035 | ||
--- | ||
|
||
::: precli.rules.python.stdlib.hashlib_improper_prng |
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,142 @@ | ||
# Copyright 2024 Secure Saurce LLC | ||
r""" | ||
# Improper Randomness for Cryptographic `hashlib` Functions | ||
This rule detects the use of non-cryptographically secure randomness sources, | ||
such as Python's `random()` function, as inputs to cryptographic functions | ||
like `hashlib.scrypt()`. Using non-secure randomness sources can weaken the | ||
cryptographic strength of functions that rely on unpredictability for security. | ||
Cryptographic functions, including key generation, encryption, and hashing, | ||
require a source of randomness that is unpredictable and secure against | ||
attack. The standard `random()` function in Python is designed for statistical | ||
modeling and simulations, not for security purposes, as it generates | ||
predictable sequences that can be reproduced if the seed value is known. | ||
Using `random()` for cryptographic purposes, such as generating salts or keys, | ||
compromises security by making the output potentially predictable to attackers. | ||
Ensure all cryptographic operations utilize a cryptographically secure source | ||
of randomness. Python provides the `secrets` module for generating secure | ||
random numbers suitable for security-sensitive applications, including key | ||
generation and creating salts for hashing functions. | ||
## Example | ||
```python | ||
import hashlib | ||
import random | ||
password = b"my_secure_password" | ||
salt = random.randbytes(16) | ||
hashlib.scrypt(password, salt=salt, n=16384, r=8, p=1) | ||
``` | ||
## Remediation | ||
For security or cryptographic uses use a secure pseudo-random generator such | ||
as `os.urandom()` or `secrets.token_bytes()`. | ||
```python | ||
import hashlib | ||
import os | ||
password = b"my_secure_password" | ||
salt = os.urandom(16) | ||
hashlib.scrypt(password, salt=salt, n=16384, r=8, p=1) | ||
``` | ||
## See also | ||
- [random — Generate pseudo-random numbers](https://docs.python.org/3/library/random.html#random.randbytes) | ||
- [hashlib — Secure hashes and message digests](https://docs.python.org/3/library/hashlib.html) | ||
- [ssl — TLS_SSL wrapper for socket objects](https://docs.python.org/3/library/ssl.html#ssl.RAND_bytes) | ||
- [CWE-330: Use of Insufficiently Random Values](https://cwe.mitre.org/data/definitions/330.html) | ||
_New in version 0.4.3_ | ||
""" # noqa: E501 | ||
from precli.core.call import Call | ||
from precli.core.location import Location | ||
from precli.core.result import Result | ||
from precli.rules import Rule | ||
|
||
|
||
class HashlibImproperPrng(Rule): | ||
def __init__(self, id: str): | ||
super().__init__( | ||
id=id, | ||
name="improper_random", | ||
description=__doc__, | ||
cwe_id=330, | ||
message="The '{0}' pseudo-random generator should not be used for " | ||
"security purposes.", | ||
wildcards={ | ||
"hashlib.*": [ | ||
"blake2b", | ||
"blake2s", | ||
"pbkdf2_hmac", | ||
"scrypt", | ||
], | ||
"random.*": [ | ||
"randbytes", | ||
], | ||
"ssl.*": [ | ||
"RAND_bytes", | ||
], | ||
}, | ||
) | ||
|
||
def analyze_call(self, context: dict, call: Call) -> Result: | ||
if call.name_qualified not in ( | ||
"hashlib.blake2b", | ||
"hashlib.blake2s", | ||
"hashlib.pbkdf2_hmac", | ||
"hashlib.scrypt", | ||
): | ||
return | ||
|
||
""" | ||
hashlib.blake2b(data=b'', *, digest_size=64, key=b'', salt=b'', | ||
person=b'', fanout=1, depth=1, leaf_size=0, node_offset=0, | ||
node_depth=0, inner_size=0, last_node=False, usedforsecurity=True) | ||
hashlib.blake2s(data=b'', *, digest_size=32, key=b'', salt=b'', | ||
person=b'', fanout=1, depth=1, leaf_size=0, node_offset=0, | ||
node_depth=0, inner_size=0, last_node=False, usedforsecurity=True) | ||
hashlib.pbkdf2_hmac(hash_name, password, salt, iterations, dklen=None) | ||
hashlib.scrypt(password, *, salt, n, r, p, maxmem=0, dklen=64) | ||
""" | ||
|
||
if call.name_qualified == "hashlib.pbkdf2_hmac": | ||
argument = call.get_argument(position=2, name="salt") | ||
elif call.name_qualified in ( | ||
"hashlib.blake2b", | ||
"hashlib.blake2s", | ||
"hashlib.scrypt", | ||
): | ||
argument = call.get_argument(name="salt") | ||
|
||
if argument.value not in ( | ||
"random.randbytes", | ||
"ssl.RAND_bytes", | ||
): | ||
return | ||
|
||
fixes = Rule.get_fixes( | ||
context=context, | ||
deleted_location=Location(node=argument.node), | ||
description="The salt should be 16 or more bytes from a proper " | ||
"pseudo-random source such as `os.urandom()`.", | ||
inserted_content="os.urandom(16)", | ||
) | ||
|
||
return Result( | ||
rule_id=self.id, | ||
location=Location(node=argument.node), | ||
message=self.message.format(argument.value), | ||
fixes=fixes, | ||
) |
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
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
12 changes: 12 additions & 0 deletions
12
tests/unit/rules/python/stdlib/hashlib/examples/hashlib_improper_prng_blake2b.py
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,12 @@ | ||
# level: WARNING | ||
# start_line: 12 | ||
# end_line: 12 | ||
# start_column: 27 | ||
# end_column: 31 | ||
import hashlib | ||
import ssl | ||
|
||
|
||
data = b"super-secret-data" | ||
salt = ssl.RAND_bytes(16) | ||
hashlib.blake2b(data, salt=salt) |
12 changes: 12 additions & 0 deletions
12
tests/unit/rules/python/stdlib/hashlib/examples/hashlib_improper_prng_blake2s.py
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,12 @@ | ||
# level: WARNING | ||
# start_line: 12 | ||
# end_line: 12 | ||
# start_column: 27 | ||
# end_column: 31 | ||
import hashlib | ||
from random import randbytes | ||
|
||
|
||
data = b"super-secret-data" | ||
salt = randbytes(16) | ||
hashlib.blake2s(data, salt=salt) |
13 changes: 13 additions & 0 deletions
13
tests/unit/rules/python/stdlib/hashlib/examples/hashlib_improper_prng_pbkdf2_hmac.py
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 @@ | ||
# level: WARNING | ||
# start_line: 13 | ||
# end_line: 13 | ||
# start_column: 58 | ||
# end_column: 62 | ||
import hashlib | ||
import ssl | ||
|
||
|
||
password = b"my_secure_password" | ||
salt = ssl.RAND_bytes(16) | ||
our_app_iters = 500_000 | ||
hashed_password = hashlib.pbkdf2_hmac("sha256", password, salt, our_app_iters) |
12 changes: 12 additions & 0 deletions
12
tests/unit/rules/python/stdlib/hashlib/examples/hashlib_improper_prng_scrypt.py
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,12 @@ | ||
# level: WARNING | ||
# start_line: 12 | ||
# end_line: 12 | ||
# start_column: 48 | ||
# end_column: 52 | ||
import hashlib | ||
import random | ||
|
||
|
||
password = b"my_secure_password" | ||
salt = random.randbytes(16) | ||
hashed_password = hashlib.scrypt(password, salt=salt, n=16384, r=8, p=1) |
48 changes: 48 additions & 0 deletions
48
tests/unit/rules/python/stdlib/hashlib/test_hashlib_improper_prng.py
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,48 @@ | ||
# Copyright 2024 Secure Saurce LLC | ||
import os | ||
|
||
from parameterized import parameterized | ||
|
||
from precli.core.level import Level | ||
from precli.parsers import python | ||
from precli.rules import Rule | ||
from tests.unit.rules import test_case | ||
|
||
|
||
class HashlibImproperPrngTests(test_case.TestCase): | ||
def setUp(self): | ||
super().setUp() | ||
self.rule_id = "PY035" | ||
self.parser = python.Python() | ||
self.base_path = os.path.join( | ||
"tests", | ||
"unit", | ||
"rules", | ||
"python", | ||
"stdlib", | ||
"hashlib", | ||
"examples", | ||
) | ||
|
||
def test_rule_meta(self): | ||
rule = Rule.get_by_id(self.rule_id) | ||
self.assertEqual(self.rule_id, rule.id) | ||
self.assertEqual("improper_random", rule.name) | ||
self.assertEqual( | ||
f"https://docs.securesauce.dev/rules/{self.rule_id}", rule.help_url | ||
) | ||
self.assertEqual(True, rule.default_config.enabled) | ||
self.assertEqual(Level.WARNING, rule.default_config.level) | ||
self.assertEqual(-1.0, rule.default_config.rank) | ||
self.assertEqual("330", rule.cwe.cwe_id) | ||
|
||
@parameterized.expand( | ||
[ | ||
"hashlib_improper_prng_blake2b.py", | ||
"hashlib_improper_prng_blake2s.py", | ||
"hashlib_improper_prng_pbkdf2_hmac.py", | ||
"hashlib_improper_prng_scrypt.py", | ||
] | ||
) | ||
def test(self, filename): | ||
self.check(filename) |