diff --git a/prowler/changelog.d/iac-connection-test-ssrf.security.md b/prowler/changelog.d/iac-connection-test-ssrf.security.md index 4a175c4d07..dfbf7c6e50 100644 --- a/prowler/changelog.d/iac-connection-test-ssrf.security.md +++ b/prowler/changelog.d/iac-connection-test-ssrf.security.md @@ -1 +1 @@ -IaC connection test rejects repository URLs resolving to loopback, private or link-local hosts and stops echoing raw errors to the caller +IaC repository URLs resolving to loopback, private or otherwise non-public hosts rejected before both the connection test and the scan clone, with raw errors no longer echoed to the caller diff --git a/prowler/providers/iac/iac_provider.py b/prowler/providers/iac/iac_provider.py index 3c6a05445d..ca9e7bf10a 100644 --- a/prowler/providers/iac/iac_provider.py +++ b/prowler/providers/iac/iac_provider.py @@ -330,6 +330,9 @@ class IacProvider(Provider): Returns: tuple[str, str]: (temporary_directory, branch_name) """ + validate_outbound_url( + repository_url, allowed_schemes=("http", "https", "ssh", "git") + ) try: original_url = repository_url diff --git a/tests/lib/network/__init__.py b/tests/lib/network/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/providers/iac/iac_provider_test.py b/tests/providers/iac/iac_provider_test.py index b08b11a84f..a87464a509 100644 --- a/tests/providers/iac/iac_provider_test.py +++ b/tests/providers/iac/iac_provider_test.py @@ -7,6 +7,7 @@ from unittest.mock import MagicMock, patch import pytest from prowler.lib.check.models import CheckReportIAC +from prowler.lib.network.ssrf import OutboundURLNotAllowedError from prowler.providers.iac.exceptions.exceptions import ( IacRepositoryCloneError, IacScanError, @@ -903,6 +904,40 @@ class TestIacProvider: assert connection.error == "Repository URL is not an allowed destination." mock_ls_remote.assert_not_called() + def test_clone_repository_rejects_a_non_public_url(self): + provider = IacProvider.__new__(IacProvider) + with patch("prowler.providers.iac.iac_provider.porcelain.clone") as mock_clone: + with pytest.raises(OutboundURLNotAllowedError): + provider._clone_repository("https://169.254.169.254/org/repo.git") + + mock_clone.assert_not_called() + + def test_clone_repository_rejects_shared_address_space(self): + provider = IacProvider.__new__(IacProvider) + with patch("prowler.providers.iac.iac_provider.porcelain.clone") as mock_clone: + with pytest.raises(OutboundURLNotAllowedError): + provider._clone_repository("https://100.100.100.200/org/repo.git") + + mock_clone.assert_not_called() + + def test_clone_repository_does_not_validate_the_token_bearing_url(self): + provider = IacProvider.__new__(IacProvider) + with patch( + "prowler.lib.network.ssrf.socket.getaddrinfo", + return_value=[(None, None, None, None, ("140.82.121.4", 0))], + ) as mock_getaddrinfo: + with patch("prowler.providers.iac.iac_provider.porcelain.clone"): + with patch.object( + IacProvider, "_detect_branch_name", return_value="main" + ): + provider._clone_repository( + "https://github.com/org/repo.git", + github_username="user", + personal_access_token="token", + ) + + assert mock_getaddrinfo.call_args[0][0] == "github.com" + def test_test_connection_allows_public_url(self): with ( patch(