-
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 checks use of Telnet with no timeout
PY044 The Telnet class and open function on the object has timeout arguments that if undefined or set to None tell the library to use the global default timeout which has a default of None which has the effect of blocking forever. Signed-off-by: Eric Brown <[email protected]>
- Loading branch information
Showing
12 changed files
with
228 additions
and
3 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: PY043 | ||
title: poplib — no timeout | ||
hide_title: true | ||
pagination_prev: null | ||
pagination_next: null | ||
slug: /rules/PY043 | ||
--- | ||
|
||
::: precli.rules.python.stdlib.poplib_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,10 @@ | ||
--- | ||
id: PY044 | ||
title: telnetlib — no timeout | ||
hide_title: true | ||
pagination_prev: null | ||
pagination_next: null | ||
slug: /rules/PY044 | ||
--- | ||
|
||
::: precli.rules.python.stdlib.telnetlib_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,127 @@ | ||
# Copyright 2024 Secure Sauce LLC | ||
r""" | ||
# Synchronous Access of `Telnet` without Timeout | ||
The `telnetlib.Telnet` class and the `telnetlib.Telnet.open()` method are | ||
used to establish a connection to a remote server using the Telnet protocol. | ||
By default, these operations do not enforce a timeout on the connection, | ||
which can lead to indefinite blocking if the server is unresponsive. This | ||
can result in resource exhaustion, application hanging, or Denial of Service | ||
(DoS) vulnerabilities, especially in networked or production environments. | ||
This rule ensures that a timeout parameter is provided when using | ||
`telnetlib.Telnet` and `telnetlib.Telnet.open()` to prevent the risk of | ||
indefinite blocking during network communications. | ||
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="4" title="telnetlib_telnet_no_timeout.py" | ||
import telnetlib | ||
telnet = telnetlib.Telnet("example.com", 23) | ||
``` | ||
??? example "Example Output" | ||
``` | ||
> precli tests/unit/rules/python/stdlib/telnetlib/examples/telnetlib_telnet_no_timeout.py | ||
⚠️ Warning on line 9 in tests/unit/rules/python/stdlib/telnetlib/examples/telnetlib_telnet_no_timeout.py | ||
PY044: Synchronous Access of Remote Resource without Timeout | ||
The class 'telnetlib.Telnet' 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 `telnetlib.Telnet` or | ||
`telnetlib.Telnet.open()`. 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="4" title="telnetlib_telnet_no_timeout.py" | ||
import telnetlib | ||
telnet = telnetlib.Telnet("example.com", 23, timeout=5) | ||
``` | ||
# See also | ||
!!! info | ||
- [telnetlib.Telnet — telnetlib — Telnet client](https://docs.python.org/3/library/telnetlib.html#telnetlib.Telnet) | ||
- [telnetlib.Telnet.open — telnetlib — Telnet client](https://docs.python.org/3/library/telnetlib.html#telnetlib.Telnet.open) | ||
- [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 TelnetlibNoTimeout(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 ( | ||
"telnetlib.Telnet", | ||
"telnetlib.Telnet.open", | ||
): | ||
return | ||
|
||
if ( | ||
call.name_qualified == "telnetlib.Telnet" | ||
and call.get_argument(position=0, name="host").node is None | ||
): | ||
return | ||
|
||
# Telnet(host=None, port=0, timeout=GLOBAL_TIMEOUT) | ||
# Telnet.open(self, host, port=0, timeout=GLOBAL_TIMEOUT) | ||
argument = call.get_argument(position=2, 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
9 changes: 9 additions & 0 deletions
9
tests/unit/rules/python/stdlib/telnetlib/examples/telnetlib_telnet_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,9 @@ | ||
# level: WARNING | ||
# start_line: 9 | ||
# end_line: 9 | ||
# start_column: 25 | ||
# end_column: 44 | ||
import telnetlib | ||
|
||
|
||
telnet = telnetlib.Telnet("example.com", 23) |
10 changes: 10 additions & 0 deletions
10
tests/unit/rules/python/stdlib/telnetlib/examples/telnetlib_telnet_open_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,10 @@ | ||
# level: WARNING | ||
# start_line: 10 | ||
# end_line: 10 | ||
# start_column: 35 | ||
# end_column: 39 | ||
import telnetlib | ||
|
||
|
||
telnet = telnetlib.Telnet() | ||
telnet.open("example.com", timeout=None) |
5 changes: 5 additions & 0 deletions
5
tests/unit/rules/python/stdlib/telnetlib/examples/telnetlib_telnet_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,5 @@ | ||
# level: NONE | ||
import telnetlib | ||
|
||
|
||
telnet = telnetlib.Telnet("example.com", 23, 5) |
49 changes: 49 additions & 0 deletions
49
tests/unit/rules/python/stdlib/telnetlib/test_telnetlib_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 TestTelnetlibNoTimeout(test_case.TestCase): | ||
@classmethod | ||
def setup_class(cls): | ||
cls.rule_id = "PY044" | ||
cls.parser = python.Python() | ||
cls.base_path = os.path.join( | ||
"tests", | ||
"unit", | ||
"rules", | ||
"python", | ||
"stdlib", | ||
"telnetlib", | ||
"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", | ||
[ | ||
"telnetlib_telnet_no_timeout.py", | ||
"telnetlib_telnet_open_timeout_none.py", | ||
"telnetlib_telnet_timeout_5.py", | ||
], | ||
) | ||
def test(self, filename): | ||
self.check(filename, enabled=[self.rule_id]) |