-
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.
Add rule for xmlrpc.server unrestricted bind (#368)
Checks xmlrpc.server classes that also binds sockets with parameters that allow unrestricted addresses (ANY) as input. Closes: #225 Signed-off-by: Eric Brown <[email protected]>
- Loading branch information
Showing
8 changed files
with
198 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
10 changes: 10 additions & 0 deletions
10
docs/rules/python/stdlib/xmlrpc-server-unrestricted-bind.md
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: PY032 | ||
title: xmlrpc — unrestricted bind | ||
hide_title: true | ||
pagination_prev: null | ||
pagination_next: null | ||
slug: /rules/PY032 | ||
--- | ||
|
||
::: precli.rules.python.stdlib.xmlrpc_server_unrestricted_bind |
109 changes: 109 additions & 0 deletions
109
precli/rules/python/stdlib/xmlrpc_server_unrestricted_bind.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,109 @@ | ||
# Copyright 2024 Secure Saurce LLC | ||
r""" | ||
# Binding to an Unrestricted IP Address in `xmlrpc.server` Module | ||
Sockets can be bound to the IPv4 address `0.0.0.0` or IPv6 equivalent of | ||
`::`, which configures the socket to listen for incoming connections on all | ||
network interfaces. While this can be intended in environments where | ||
services are meant to be publicly accessible, it can also introduce significant | ||
security risks if the service is not intended for public or wide network | ||
access. | ||
Binding a socket to `0.0.0.0` or `::` can unintentionally expose the | ||
application to the wider network or the internet, making it accessible from | ||
any interface. This exposure can lead to unauthorized access, data breaches, | ||
or exploitation of vulnerabilities within the application if the service is | ||
not adequately secured or if the binding is unintended. Restricting the socket | ||
to listen on specific interfaces limits the exposure and reduces the attack | ||
surface. | ||
## Example | ||
```python | ||
from http.server import BaseHTTPRequestHandler | ||
from http.server import HTTPServer | ||
def run(server_class: HTTPServer, handler_class: BaseHTTPRequestHandler): | ||
server_address = ("", 8000) | ||
httpd = server_class(server_address, handler_class) | ||
httpd.serve_forever() | ||
``` | ||
## Remediation | ||
All socket bindings MUST specify a specific network interface or localhost | ||
(127.0.0.1/localhost for IPv4, ::1 for IPv6) unless the application is | ||
explicitly designed to be accessible from any network interface. This | ||
practice ensures that services are not exposed more broadly than intended. | ||
```python | ||
from http.server import BaseHTTPRequestHandler | ||
from http.server import HTTPServer | ||
def run(server_class: HTTPServer, handler_class: BaseHTTPRequestHandler): | ||
server_address = ("127.0.0.1", 8000) | ||
httpd = server_class(server_address, handler_class) | ||
httpd.serve_forever() | ||
``` | ||
## See also | ||
- [xmlrpc.server.DocXMLRPCServer — Basic XML-RPC servers](https://docs.python.org/3/library/xmlrpc.server.html#xmlrpc.server.DocXMLRPCServer) | ||
- [xmlrpc.server.SimpleXMLRPCServer — HTTP servers](https://docs.python.org/3/library/xmlrpc.server.html#xmlrpc.server.SimpleXMLRPCServer) | ||
- [CWE-1327: Binding to an Unrestricted IP Address](https://cwe.mitre.org/data/definitions/1327.html) | ||
_New in version 0.3.14_ | ||
""" # noqa: E501 | ||
from precli.core.location import Location | ||
from precli.core.result import Result | ||
from precli.rules import Rule | ||
|
||
|
||
INADDR_ANY = "0.0.0.0" | ||
IN6ADDR_ANY = "::" | ||
|
||
|
||
class XmlrpcServerUnrestrictedBind(Rule): | ||
def __init__(self, id: str): | ||
super().__init__( | ||
id=id, | ||
name="unrestricted_bind", | ||
description=__doc__, | ||
cwe_id=1327, | ||
message="Binding to '{0}' exposes the application on all network " | ||
"interfaces, increasing the risk of unauthorized access.", | ||
targets=("call"), | ||
wildcards={ | ||
"xmlrpc.server.*": [ | ||
"DocXMLRPCServer", | ||
"SimpleXMLRPCServer", | ||
] | ||
}, | ||
) | ||
|
||
def analyze(self, context: dict, **kwargs: dict) -> Result: | ||
call = kwargs.get("call") | ||
if call.name_qualified not in [ | ||
"xmlrpc.server.DocXMLRPCServer", | ||
"xmlrpc.server.SimpleXMLRPCServer", | ||
]: | ||
return | ||
|
||
arg = call.get_argument(position=0, name="addr") | ||
addr = arg.value | ||
|
||
if isinstance(addr, tuple) and addr[0] in ( | ||
"", | ||
INADDR_ANY, | ||
IN6ADDR_ANY, | ||
): | ||
return Result( | ||
rule_id=self.id, | ||
location=Location(node=arg.node), | ||
message=self.message.format( | ||
"INADDR_ANY (0.0.0.0) or IN6ADDR_ANY (::)" | ||
), | ||
) |
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
Empty file.
13 changes: 13 additions & 0 deletions
13
tests/unit/rules/python/stdlib/xmlrpc/examples/xmlrpc_server_doc_xml_rpc_server.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: 12 | ||
# end_line: 12 | ||
# start_column: 25 | ||
# end_column: 39 | ||
from xmlrpc.server import DocXMLRPCRequestHandler | ||
from xmlrpc.server import DocXMLRPCServer | ||
|
||
|
||
def run(server_class: DocXMLRPCServer, handler_class: DocXMLRPCRequestHandler): | ||
server_address = ("::", 8000) | ||
httpd = server_class(server_address, handler_class) | ||
httpd.serve_forever() |
16 changes: 16 additions & 0 deletions
16
tests/unit/rules/python/stdlib/xmlrpc/examples/xmlrpc_server_simple_xml_rpc_server.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,16 @@ | ||
# level: WARNING | ||
# start_line: 15 | ||
# end_line: 15 | ||
# start_column: 25 | ||
# end_column: 39 | ||
from xmlrpc.server import SimpleXMLRPCRequestHandler | ||
from xmlrpc.server import SimpleXMLRPCServer | ||
|
||
|
||
def run( | ||
server_class: SimpleXMLRPCServer, | ||
handler_class: SimpleXMLRPCRequestHandler, | ||
): | ||
server_address = ("0.0.0.0", 8000) | ||
httpd = server_class(server_address, handler_class) | ||
httpd.serve_forever() |
46 changes: 46 additions & 0 deletions
46
tests/unit/rules/python/stdlib/xmlrpc/test_xmlrpc_server_unrestricted_bind.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,46 @@ | ||
# 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 XmlrpcServerUnrestrictedBindTests(test_case.TestCase): | ||
def setUp(self): | ||
super().setUp() | ||
self.rule_id = "PY032" | ||
self.parser = python.Python() | ||
self.base_path = os.path.join( | ||
"tests", | ||
"unit", | ||
"rules", | ||
"python", | ||
"stdlib", | ||
"xmlrpc", | ||
"examples", | ||
) | ||
|
||
def test_rule_meta(self): | ||
rule = Rule.get_by_id(self.rule_id) | ||
self.assertEqual(self.rule_id, rule.id) | ||
self.assertEqual("unrestricted_bind", 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("1327", rule.cwe.cwe_id) | ||
|
||
@parameterized.expand( | ||
[ | ||
"xmlrpc_server_doc_xml_rpc_server.py", | ||
"xmlrpc_server_simple_xml_rpc_server.py", | ||
] | ||
) | ||
def test(self, filename): | ||
self.check(filename) |