Skip to content

Commit 21937cb

Browse files
authored
42 code example where tested with various SAST tools detecting flaws: (#653)
* 42 code example where tested with various SAST tools detecting flaws: - PyLint 6 - Bandit 3 - Pyre 1 - flake8 0 - Pysa 0 Some type hint related flaws are corrected as part of this PR fixed wrong type hints in: CWE-664/CWE-134/compliant01.py CWE-664/CWE-843/compliant01.py CWE-682/CWE-1335/01/compliant02.py CWE-693/CWE-184/compliant01.py CWE-693/CWE-184/noncompliant01.py renamed CWE-664/CWE-502/compliant01.py to example as it does not represent compliance. Signed-off-by: Helge Wehder <[email protected]> * CWE-664/CWE-1335/compliant02.py corrected type hints CWE-664/665/ removed example01.py as its just a code snippet Signed-off-by: Helge Wehder <[email protected]> --------- Signed-off-by: Helge Wehder <[email protected]>
1 parent e247db1 commit 21937cb

File tree

14 files changed

+145
-153
lines changed

14 files changed

+145
-153
lines changed

docs/Secure-Coding-Guide-for-Python/CWE-664/CWE-134/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ The `compliant01.py` solution uses the string template module and avoids mixing
6464
*[compliant01.py](compliant01.py):*
6565

6666
```python
67+
# SPDX-FileCopyrightText: OpenSSF project contributors
68+
# SPDX-License-Identifier: MIT
6769
""" Compliant Code Example """
6870
import sys
6971
from string import Template
@@ -85,7 +87,7 @@ class MicroService:
8587
return self.instance_name
8688

8789

88-
def front_end(customer) -> str:
90+
def front_end(customer):
8991
"""Display service instance"""
9092
mc = MicroService("big time microservice")
9193
print(MESSAGE.substitute(instance_name=mc.get_instance_name(),

docs/Secure-Coding-Guide-for-Python/CWE-664/CWE-134/compliant01.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ def get_instance_name(self) -> str:
2121
return self.instance_name
2222

2323

24-
def front_end(customer) -> str:
24+
def front_end(customer):
2525
"""Display service instance"""
2626
mc = MicroService("big time microservice")
2727
print(MESSAGE.substitute(instance_name=mc.get_instance_name(),

docs/Secure-Coding-Guide-for-Python/CWE-664/CWE-502/README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,11 +97,11 @@ message = p3.uncan(PAYLOAD)
9797
The deserializating `Preserver.uncan()` method has no solution to verify the content prior to unpickling it and runs the PAYLOAD even before turning it into an object. On Windows you have `calc.exe` and on Unix a bunch of commands such as `uname -a and ls -la /etc/shadow`.
9898

9999
> [!CAUTION]
100-
> The `compliant01.py` code only demonstrates integrity protection with hmac.
100+
> The `example01.py` code only demonstrates integrity protection with hmac.
101101
> The pickled object is not encrypted and key-handling is inappropriate!
102102
> Consider using proper key management with `x509` and encryption [[pyca/cryptography 2023]](https://cryptography.io/en/latest/).
103103
104-
*[compliant01.py](compliant01.py):*
104+
*[example01.py](example01.py):*
105105

106106
```py
107107
""" Compliant Code Example """
@@ -198,11 +198,11 @@ The integrity verification in `compliant01.py` throws an exception `ValueError:
198198

199199
Text-based formats, such as `JSON` and `YAML`, should always be preferred. They have a lower set of capabilities and reduce the attack surface [python.org comparison-with-json 2023] when compared to `pickle`.
200200

201-
The `compliant02.py` code only allows serializing and deserialization of object data and not object methods as in `noncompliant01.py` or `compliant01.py`.
201+
The `compliant01.py` code only allows serializing and deserialization of object data and not object methods as in `noncompliant01.py` or `compliant01.py`.
202202

203203
Consider converting binary data into text using `Base64` encoding for performance and size irrelevant operations.
204204

205-
*[compliant02.py](compliant02.py):*
205+
*[compliant01.py](compliant01.py):*
206206

207207
```py
208208
""" Compliant Code Example """
Lines changed: 28 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,69 @@
11
# SPDX-FileCopyrightText: OpenSSF project contributors
22
# SPDX-License-Identifier: MIT
33
""" Compliant Code Example """
4-
import hashlib
5-
import hmac
64
import platform
7-
import pickle
8-
import secrets
5+
import json
96

107

118
class Message(object):
129
"""Sample Message Object"""
13-
sender_id = 42
14-
text = "Some text"
10+
sender_id = int()
11+
text = str()
12+
13+
def __init__(self):
14+
self.sender_id = 42
15+
self.text = "Some text"
1516

1617
def printout(self):
17-
"""prints content to stdout to demonstrate active content"""
18-
print(f"Message:sender_id={self.sender_id} text={self.text}")
18+
print(f"sender_id: {self.sender_id}\ntext: {self.text}")
1919

2020

2121
class Preserver(object):
2222
"""Demonstrating deserialisation"""
23-
def __init__(self, _key):
24-
self._key = _key
2523

26-
def can(self, _message: Message) -> tuple:
24+
def can(self, _message: Message) -> str:
2725
"""Serializes a Message object.
2826
Parameters:
2927
_message (Message): Message object
3028
Returns:
31-
_digest (String): HMAC digest string
32-
_jar (bytes): pickled jar as string
29+
_jar (bytes): jar as string
3330
"""
34-
_jar = pickle.dumps(_message)
35-
_digest = hmac.new(self._key, _jar, hashlib.sha256).hexdigest()
36-
return _digest, _jar
31+
return json.dumps(vars(_message))
3732

38-
def uncan(self, _expected_digest, _jar) -> Message:
33+
def uncan(self, _jar) -> Message:
3934
"""Verifies and de-serializes a Message object.
4035
Parameters:
41-
_expected_digest (String): Message HMAC digest
42-
_jar (bytes): Pickled jar
36+
_jar (String): Pickled jar
4337
Returns:
4438
(Message): Message object
4539
"""
46-
_digest = hmac.new(self._key, _jar, hashlib.sha256).hexdigest()
47-
if _expected_digest != _digest:
48-
raise ValueError("Integrity of jar compromised")
49-
return pickle.loads(_jar)
40+
j = json.loads(_jar)
41+
_message = Message()
42+
_message.sender_id = int(j["sender_id"])
43+
_message.text = str(j["text"])
44+
return _message
5045

5146

5247
# serialization of a normal package
53-
key = secrets.token_bytes()
54-
print(f"key={key}")
55-
p1 = Preserver(key)
48+
p1 = Preserver()
5649
message = Message()
57-
message.printout()
58-
digest, jar = p1.can(message)
50+
jar = p1.can(message)
51+
print(jar)
52+
print(type(json.loads(jar)))
5953

6054
# sending or storing would happen here
61-
p2 = Preserver(key)
55+
p2 = Preserver()
6256
message = None
63-
message = p2.uncan(digest, jar)
57+
message = p2.uncan(jar)
6458
message.printout()
59+
print(message.sender_id)
6560

6661
#####################
6762
# exploiting above code example
6863
#####################
6964
print("-" * 10)
7065
print("Attacker trying to read the message")
71-
message = pickle.loads(jar)
66+
print(jar)
7267
message.printout()
7368

7469
print("-" * 10)
@@ -83,6 +78,6 @@ def uncan(self, _expected_digest, _jar) -> Message:
8378
(S'whoami;uptime;uname -a;ls -la /etc/shadow'
8479
tR."""
8580
print("Attacker trying to inject PAYLOAD")
86-
p3 = Preserver(b"dont know")
81+
p3 = Preserver()
8782
message = None
88-
message = p3.uncan(digest, PAYLOAD)
83+
message = p3.uncan(PAYLOAD)

docs/Secure-Coding-Guide-for-Python/CWE-664/CWE-502/compliant02.py

Lines changed: 0 additions & 83 deletions
This file was deleted.
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# SPDX-FileCopyrightText: OpenSSF project contributors
2+
# SPDX-License-Identifier: MIT
3+
""" Compliant Code Example """
4+
import hashlib
5+
import hmac
6+
import platform
7+
import pickle
8+
import secrets
9+
10+
11+
class Message(object):
12+
"""Sample Message Object"""
13+
sender_id = 42
14+
text = "Some text"
15+
16+
def printout(self):
17+
"""prints content to stdout to demonstrate active content"""
18+
print(f"Message:sender_id={self.sender_id} text={self.text}")
19+
20+
21+
class Preserver(object):
22+
"""Demonstrating deserialisation"""
23+
def __init__(self, _key):
24+
self._key = _key
25+
26+
def can(self, _message: Message) -> tuple:
27+
"""Serializes a Message object.
28+
Parameters:
29+
_message (Message): Message object
30+
Returns:
31+
_digest (String): HMAC digest string
32+
_jar (bytes): pickled jar as string
33+
"""
34+
_jar = pickle.dumps(_message)
35+
_digest = hmac.new(self._key, _jar, hashlib.sha256).hexdigest()
36+
return _digest, _jar
37+
38+
def uncan(self, _expected_digest, _jar) -> Message:
39+
"""Verifies and de-serializes a Message object.
40+
Parameters:
41+
_expected_digest (String): Message HMAC digest
42+
_jar (bytes): Pickled jar
43+
Returns:
44+
(Message): Message object
45+
"""
46+
_digest = hmac.new(self._key, _jar, hashlib.sha256).hexdigest()
47+
if _expected_digest != _digest:
48+
raise ValueError("Integrity of jar compromised")
49+
return pickle.loads(_jar)
50+
51+
52+
# serialization of a normal package
53+
key = secrets.token_bytes()
54+
print(f"key={key}")
55+
p1 = Preserver(key)
56+
message = Message()
57+
message.printout()
58+
digest, jar = p1.can(message)
59+
60+
# sending or storing would happen here
61+
p2 = Preserver(key)
62+
message = None
63+
message = p2.uncan(digest, jar)
64+
message.printout()
65+
66+
#####################
67+
# exploiting above code example
68+
#####################
69+
print("-" * 10)
70+
print("Attacker trying to read the message")
71+
message = pickle.loads(jar)
72+
message.printout()
73+
74+
print("-" * 10)
75+
if platform.system() == "Windows":
76+
PAYLOAD = b"""cos
77+
system
78+
(S'calc.exe'
79+
tR."""
80+
else:
81+
PAYLOAD = b"""cos
82+
system
83+
(S'whoami;uptime;uname -a;ls -la /etc/shadow'
84+
tR."""
85+
print("Attacker trying to inject PAYLOAD")
86+
p3 = Preserver(b"dont know")
87+
message = None
88+
message = p3.uncan(digest, PAYLOAD)

docs/Secure-Coding-Guide-for-Python/CWE-664/CWE-665/README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ ThreadPoolExecutor-0_1: Working concurrently as User.ADMIN
118118
The two worker threads have been initialized only once using the initializer method. `ThreadPoolExecutor-0_1` has completed the `work_as_admin()` task and has been reused to complete one of the `work_as_guest()`. Because the local values have been changed by the first task, the changed values persisted to the second task.
119119

120120
Table listing a possible execution order:
121+
121122
|Task|Workder Thread|Executed Method|User|
122123
|:---|:---|:---|:---|
123124
|1|0|`work_as_admin()`|ADMIN|
@@ -207,8 +208,6 @@ for future in futures:
207208

208209
The increased thread pool size circumvents the problem for this specific example. However, the problem has not been resolved since expanding the number of submitted tasks will cause it to reoccur. For example, if we change the `fututes` list as follows:
209210

210-
*[example01.py](example01.py):*
211-
212211
```py
213212
futures = [
214213
sp.execute_task(sp.work_as_admin), # Thread 1, works as ADMIN

docs/Secure-Coding-Guide-for-Python/CWE-664/CWE-665/example01.py

Lines changed: 0 additions & 10 deletions
This file was deleted.

docs/Secure-Coding-Guide-for-Python/CWE-664/CWE-843/compliant01.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
""" Compliant Code Example """
44

55

6-
def shopping_bag(price: int, qty: int) -> int:
6+
def shopping_bag(price: int, qty: str) -> int:
77
return int(price) * int(qty)
88

99

docs/Secure-Coding-Guide-for-Python/CWE-664/CWE-843/noncompliant01.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
""" Non-compliant Code Example """
44

55

6-
def shopping_bag(price: int, qty: int) -> int:
6+
def shopping_bag(price: int, qty: str) -> int:
77
return price * qty
88

99

0 commit comments

Comments
 (0)