mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-04-08 03:00:28 -04:00
Merge commit from fork
SendEmailBlock accepted user-supplied smtp_server and smtp_port inputs and passed them directly to smtplib.SMTP() with no IP validation, bypassing the platform's SSRF protections in request.py. This fix: - Makes _resolve_and_check_blocked public in request.py so non-HTTP blocks can reuse the same IP validation - Validates the SMTP server hostname via resolve_and_check_blocked() before connecting - Restricts allowed SMTP ports to standard values (25, 465, 587, 2525) - Catches SMTPConnectError and SMTPServerDisconnected to prevent TCP banner leakage in error messages Fixes GHSA-4jwj-6mg5-wrwf
This commit is contained in:
@@ -21,6 +21,7 @@ from backend.data.model import (
|
||||
UserPasswordCredentials,
|
||||
)
|
||||
from backend.integrations.providers import ProviderName
|
||||
from backend.util.request import resolve_and_check_blocked
|
||||
|
||||
TEST_CREDENTIALS = UserPasswordCredentials(
|
||||
id="01234567-89ab-cdef-0123-456789abcdef",
|
||||
@@ -99,6 +100,8 @@ class SendEmailBlock(Block):
|
||||
is_sensitive_action=True,
|
||||
)
|
||||
|
||||
ALLOWED_SMTP_PORTS = {25, 465, 587, 2525}
|
||||
|
||||
@staticmethod
|
||||
def send_email(
|
||||
config: SMTPConfig,
|
||||
@@ -129,6 +132,17 @@ class SendEmailBlock(Block):
|
||||
self, input_data: Input, *, credentials: SMTPCredentials, **kwargs
|
||||
) -> BlockOutput:
|
||||
try:
|
||||
# --- SSRF Protection ---
|
||||
smtp_port = input_data.config.smtp_port
|
||||
if smtp_port not in self.ALLOWED_SMTP_PORTS:
|
||||
yield "error", (
|
||||
f"SMTP port {smtp_port} is not allowed. "
|
||||
f"Allowed ports: {sorted(self.ALLOWED_SMTP_PORTS)}"
|
||||
)
|
||||
return
|
||||
|
||||
await resolve_and_check_blocked(input_data.config.smtp_server)
|
||||
|
||||
status = self.send_email(
|
||||
config=input_data.config,
|
||||
to_email=input_data.to_email,
|
||||
@@ -180,7 +194,19 @@ class SendEmailBlock(Block):
|
||||
"was rejected by the server. "
|
||||
"Please verify your account is authorized to send emails."
|
||||
)
|
||||
except smtplib.SMTPConnectError:
|
||||
yield "error", (
|
||||
f"Cannot connect to SMTP server '{input_data.config.smtp_server}' "
|
||||
f"on port {input_data.config.smtp_port}."
|
||||
)
|
||||
except smtplib.SMTPServerDisconnected:
|
||||
yield "error", (
|
||||
f"SMTP server '{input_data.config.smtp_server}' "
|
||||
"disconnected unexpectedly."
|
||||
)
|
||||
except smtplib.SMTPDataError as e:
|
||||
yield "error", f"Email data rejected by server: {str(e)}"
|
||||
except ValueError as e:
|
||||
yield "error", str(e)
|
||||
except Exception as e:
|
||||
raise e
|
||||
|
||||
@@ -228,7 +228,7 @@ async def validate_url_host(
|
||||
return parsed, True, []
|
||||
|
||||
# If not allowlisted, go ahead with host resolution and IP target check
|
||||
return parsed, False, await _resolve_and_check_blocked(ascii_hostname)
|
||||
return parsed, False, await resolve_and_check_blocked(ascii_hostname)
|
||||
|
||||
|
||||
def matches_allowed_host(url: URL, allowed: URL) -> bool:
|
||||
@@ -242,7 +242,7 @@ def matches_allowed_host(url: URL, allowed: URL) -> bool:
|
||||
return url.port == allowed.port
|
||||
|
||||
|
||||
async def _resolve_and_check_blocked(hostname: str) -> list[str]:
|
||||
async def resolve_and_check_blocked(hostname: str) -> list[str]:
|
||||
"""
|
||||
Resolves hostname to IPs and raises ValueError if any resolve to
|
||||
a blocked network. Returns the list of resolved IP addresses.
|
||||
|
||||
Reference in New Issue
Block a user