-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
New Python rule to check for imaplib use without a timeout
New rule PY041. When imaplib.IMAP4 or IMAP4_SSL are used without specifiying a timeout, the connections have the potential to block forever. The default timeout is the global socket timeout setting which is None by default (effectively no timeout). Signed-off-by: Eric Brown <[email protected]>
- Loading branch information
Showing
21 changed files
with
244 additions
and
14 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: PY041 | ||
title: imaplib — no timeout | ||
hide_title: true | ||
pagination_prev: null | ||
pagination_next: null | ||
slug: /rules/PY041 | ||
--- | ||
|
||
::: precli.rules.python.imaplib.imaplib_no_timeout |
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,130 @@ | ||
# Copyright 2024 Secure Sauce LLC | ||
r""" | ||
# Synchronous Access of `IMAP4` without Timeout | ||
The `imaplib.IMAP4` and `imaplib.IMAP4_SSL` classes are used to connect to | ||
IMAP servers for retrieving emails over the Internet Message Access Protocol | ||
(IMAP). By default, these classes do not specify a timeout, which can result | ||
in the application blocking indefinitely while trying to communicate with an | ||
unresponsive server. This can lead to resource exhaustion, Denial of Service | ||
(DoS), or system instability, particularly in production environments where | ||
resilience is critical. | ||
This rule enforces the use of a timeout parameter when creating instances | ||
of `imaplib.IMAP4` and `imaplib.IMAP4_SSL` to avoid the risk of indefinite | ||
blocking and ensure graceful handling of network delays or failures. | ||
Failing to specify a timeout in these classes may cause the application to | ||
block indefinitely while waiting for a response from the mail server. This can | ||
lead to Denial of Service (DoS) vulnerabilities or cause the application to | ||
become unresponsive. | ||
# Example | ||
```python linenums="1" hl_lines="5" title="imaplib_imap_no_timeout.py" | ||
import imaplib | ||
import ssl | ||
imap = imaplib.IMAP4("imap.example.com") | ||
imap.starttls(ssl.create_default_context()) | ||
``` | ||
??? example "Example Output" | ||
``` | ||
> precli tests/unit/rules/python/stdlib/imaplib/examples/imaplib_imap_no_timeout.py | ||
⚠️ Warning on line 10 in tests/unit/rules/python/stdlib/imaplib/examples/imaplib_imap_no_timeout.py | ||
PY041: Synchronous Access of Remote Resource without Timeout | ||
The class 'imaplib.IMAP4' is used without a timeout, which may cause the application to block indefinitely if the remote server does not respond. | ||
``` | ||
# Remediation | ||
Always provide a timeout parameter when using `imaplib.IMAP4` or | ||
`imaplib.IMAP4_SSL`. This ensures that if the mail server is unreachable or | ||
unresponsive, the connection attempt will fail after a set period, preventing | ||
indefinite blocking and resource exhaustion. | ||
```python linenums="1" hl_lines="5" title="imaplib_imap_no_timeout.py" | ||
import imaplib | ||
import ssl | ||
imap = imaplib.IMAP4("imap.example.com", timeout=5) | ||
imap.starttls(ssl.create_default_context()) | ||
``` | ||
# See also | ||
!!! info | ||
- [imaplib.IMAP4 — imaplib — IMAP4 protocol client](https://docs.python.org/3/library/imaplib.html#imaplib.IMAP4) | ||
- [imaplib.IMAP4_SSL — imaplib — IMAP4 protocol client](https://docs.python.org/3/library/imaplib.html#imaplib.IMAP4_SSL) | ||
- [CWE-1088: Synchronous Access of Remote Resource without Timeout](https://cwe.mitre.org/data/definitions/1088.html) | ||
_New in version 0.6.7_ | ||
""" # 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 ImaplibNoTimeout(Rule): | ||
def __init__(self, id: str): | ||
super().__init__( | ||
id=id, | ||
name="no_timeout", | ||
description=__doc__, | ||
cwe_id=1088, | ||
message="The class '{0}' is used without a timeout, which may " | ||
"cause the application to block indefinitely if the remote server " | ||
"does not respond.", | ||
) | ||
|
||
def analyze_call(self, context: dict, call: Call) -> Result | None: | ||
if call.name_qualified not in ( | ||
"imaplib.IMAP4", | ||
"imaplib.IMAP4_SSL", | ||
): | ||
return | ||
|
||
if call.name_qualified == "imaplib.IMAP4": | ||
# IMAP4(host='', port=143, timeout=None) | ||
argument = call.get_argument(position=2, name="timeout") | ||
elif call.name_qualified == "imaplib.IMAP4_SSL": | ||
# IMAP4_SSL(host='', port=993, *, ssl_context=None, timeout=None) | ||
argument = call.get_argument(name="timeout") | ||
|
||
timeout = argument.value | ||
|
||
if argument.node is None: | ||
arg_list_node = call.arg_list_node | ||
fix_node = arg_list_node | ||
args = [child.string for child in arg_list_node.named_children] | ||
args.append("timeout=5") | ||
content = f"({', '.join(args)})" | ||
result_node = call.arg_list_node | ||
elif timeout is None: | ||
fix_node = argument.node | ||
result_node = argument.node | ||
content = "5" | ||
else: | ||
# If the timeout parameter is set to be zero, the class will raise | ||
# a ValueError to prevent the creation of a non-blocking socket. A | ||
# negative value also raises ValueError. So there is no need to | ||
# check for these values. | ||
return | ||
|
||
fixes = Rule.get_fixes( | ||
context=context, | ||
deleted_location=Location(fix_node), | ||
description="Set timeout parameter to a small number of seconds.", | ||
inserted_content=content, | ||
) | ||
return Result( | ||
rule_id=self.id, | ||
location=Location(node=result_node), | ||
message=self.message.format(call.name_qualified), | ||
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
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
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
11 changes: 11 additions & 0 deletions
11
tests/unit/rules/python/stdlib/imaplib/examples/imaplib_imap4_no_timeout.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,11 @@ | ||
# level: WARNING | ||
# start_line: 10 | ||
# end_line: 10 | ||
# start_column: 20 | ||
# end_column: 40 | ||
import imaplib | ||
import ssl | ||
|
||
|
||
imap = imaplib.IMAP4("imap.example.com") | ||
imap.starttls(ssl.create_default_context()) |
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
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
19 changes: 19 additions & 0 deletions
19
tests/unit/rules/python/stdlib/imaplib/examples/imaplib_imap4_ssl_timeout_none.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,19 @@ | ||
# level: WARNING | ||
# start_line: 11 | ||
# end_line: 11 | ||
# start_column: 76 | ||
# end_column: 80 | ||
import getpass | ||
import imaplib | ||
import ssl | ||
|
||
|
||
imap4 = imaplib.IMAP4_SSL(ssl_context=ssl.create_default_context(), timeout=None) | ||
imap4.login(getpass.getuser(), getpass.getpass()) | ||
imap4.select() | ||
typ, data = imap4.search(None, "ALL") | ||
for num in data[0].split(): | ||
typ, data = imap4.fetch(num, "(RFC822)") | ||
print(f"Message {num}\n{data[0][1]}\n") | ||
imap4.close() | ||
imap4.logout() |
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
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
7 changes: 7 additions & 0 deletions
7
tests/unit/rules/python/stdlib/imaplib/examples/imaplib_imap4_timeout_5.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,7 @@ | ||
# level: NONE | ||
import imaplib | ||
import ssl | ||
|
||
|
||
imap = imaplib.IMAP4("imap.example.com", timeout=5) | ||
imap.starttls(ssl.create_default_context()) |
49 changes: 49 additions & 0 deletions
49
tests/unit/rules/python/stdlib/imaplib/test_imaplib_no_timeout.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,49 @@ | ||
# Copyright 2024 Secure Sauce LLC | ||
import os | ||
|
||
import pytest | ||
|
||
from precli.core.level import Level | ||
from precli.parsers import python | ||
from precli.rules import Rule | ||
from tests.unit.rules import test_case | ||
|
||
|
||
class TestImaplibNoTimeout(test_case.TestCase): | ||
@classmethod | ||
def setup_class(cls): | ||
cls.rule_id = "PY041" | ||
cls.parser = python.Python() | ||
cls.base_path = os.path.join( | ||
"tests", | ||
"unit", | ||
"rules", | ||
"python", | ||
"stdlib", | ||
"imaplib", | ||
"examples", | ||
) | ||
|
||
def test_rule_meta(self): | ||
rule = Rule.get_by_id(self.rule_id) | ||
assert rule.id == self.rule_id | ||
assert rule.name == "no_timeout" | ||
assert ( | ||
rule.help_url | ||
== f"https://docs.securesauce.dev/rules/{self.rule_id}" | ||
) | ||
assert rule.default_config.enabled is True | ||
assert rule.default_config.level == Level.WARNING | ||
assert rule.default_config.rank == -1.0 | ||
assert rule.cwe.id == 1088 | ||
|
||
@pytest.mark.parametrize( | ||
"filename", | ||
[ | ||
"imaplib_imap4_no_timeout.py", | ||
"imaplib_imap4_ssl_timeout_none.py", | ||
"imaplib_imap4_timeout_5.py", | ||
], | ||
) | ||
def test(self, filename): | ||
self.check(filename) |