-
Notifications
You must be signed in to change notification settings - Fork 1
π‘οΈ Sentinel: [HIGH] Fix SSRF via DNS resolution check #92
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| import unittest | ||
Check warningCode scanning / Pylint (reported by Codacy) Missing module docstring Warning test
Missing module docstring
Check warningCode scanning / Pylintpython3 (reported by Codacy) Missing module docstring Warning test
Missing module docstring
|
||
| from unittest.mock import patch | ||
| import sys | ||
| import os | ||
| import socket | ||
|
|
||
| # Add parent directory to path to import main | ||
| sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | ||
|
|
||
| from main import validate_folder_url | ||
Check warningCode scanning / Prospector (reported by Codacy) Import "from main import validate_folder_url" should be placed at the top of the module (wrong-import-position) Warning test
Import "from main import validate_folder_url" should be placed at the top of the module (wrong-import-position)
Check warningCode scanning / Pylint (reported by Codacy) Import "from main import validate_folder_url" should be placed at the top of the module Warning test
Import "from main import validate_folder_url" should be placed at the top of the module
Check warningCode scanning / Pylintpython3 (reported by Codacy) Import "from main import validate_folder_url" should be placed at the top of the module Warning test
Import "from main import validate_folder_url" should be placed at the top of the module
|
||
|
|
||
| class TestSSRF(unittest.TestCase): | ||
Check warningCode scanning / Pylint (reported by Codacy) Missing class docstring Warning test
Missing class docstring
Check warningCode scanning / Pylintpython3 (reported by Codacy) Missing class docstring Warning test
Missing class docstring
|
||
| def test_localhost_literal(self): | ||
| """Test that explicit localhost strings are rejected.""" | ||
| self.assertFalse(validate_folder_url("https://localhost/config.json")) | ||
| self.assertFalse(validate_folder_url("https://127.0.0.1/config.json")) | ||
| self.assertFalse(validate_folder_url("https://[::1]/config.json")) | ||
|
|
||
| @patch('socket.getaddrinfo') | ||
| def test_private_ipv4_resolution(self, mock_getaddrinfo): | ||
| """Test that domains resolving to private IPv4 are rejected.""" | ||
| # mock returns list of (family, type, proto, canonname, sockaddr) | ||
| mock_getaddrinfo.return_value = [ | ||
| (socket.AF_INET, socket.SOCK_STREAM, 6, '', ('192.168.1.1', 0)) | ||
| ] | ||
| url = "https://internal.private/config.json" | ||
|
|
||
| self.assertFalse(validate_folder_url(url), "Should reject domain resolving to private IPv4") | ||
|
|
||
| @patch('socket.getaddrinfo') | ||
| def test_private_ipv6_resolution(self, mock_getaddrinfo): | ||
| """Test that domains resolving to private IPv6 are rejected.""" | ||
| mock_getaddrinfo.return_value = [ | ||
| (socket.AF_INET6, socket.SOCK_STREAM, 6, '', ('fd00::1', 0, 0, 0)) | ||
| ] | ||
| url = "https://internal6.private/config.json" | ||
|
|
||
| self.assertFalse(validate_folder_url(url), "Should reject domain resolving to private IPv6") | ||
|
|
||
| @patch('socket.getaddrinfo') | ||
| def test_mixed_resolution_unsafe(self, mock_getaddrinfo): | ||
| """Test that if ANY resolved IP is private, it is rejected.""" | ||
| mock_getaddrinfo.return_value = [ | ||
| (socket.AF_INET, socket.SOCK_STREAM, 6, '', ('8.8.8.8', 0)), | ||
| (socket.AF_INET, socket.SOCK_STREAM, 6, '', ('192.168.1.1', 0)) | ||
| ] | ||
| url = "https://mixed.private/config.json" | ||
|
|
||
| self.assertFalse(validate_folder_url(url), "Should reject if any IP is private") | ||
|
|
||
| @patch('socket.getaddrinfo') | ||
| def test_public_resolution(self, mock_getaddrinfo): | ||
| """Test that domains resolving to only public IPs are accepted.""" | ||
| mock_getaddrinfo.return_value = [ | ||
| (socket.AF_INET, socket.SOCK_STREAM, 6, '', ('8.8.8.8', 0)) | ||
| ] | ||
| url = "https://google.com/config.json" | ||
|
|
||
| self.assertTrue(validate_folder_url(url), "Should accept domain resolving to public IP") | ||
|
|
||
| @patch('socket.getaddrinfo') | ||
| def test_dns_resolution_failure(self, mock_getaddrinfo): | ||
| """Test that domains failing resolution are rejected.""" | ||
| mock_getaddrinfo.side_effect = Exception("DNS lookup failed") | ||
| url = "https://nonexistent.domain/config.json" | ||
|
|
||
| self.assertFalse(validate_folder_url(url), "Should reject domain that fails resolution") | ||
|
|
||
| if __name__ == '__main__': | ||
Check warningCode scanning / Prospector (reported by Codacy) expected 2 blank lines after class or function definition, found 1 (E305) Warning test
expected 2 blank lines after class or function definition, found 1 (E305)
|
||
| unittest.main() | ||
Check warning
Code scanning / Pylint (reported by Codacy)
Line too long (101/100) Warning