You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@allura.apache.org by br...@apache.org on 2026/09/10 17:05:57 UTC
(allura) 01/06: [#8621] test for FTP redirect bypassing internal-host protection
This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/allura.git
commit b71f771a1ddcdc80bcd96b2000133b6d55b43f97
Author: Dave Brondsema <da...@brondsema.net>
AuthorDate: Tue Aug 25 17:03:37 2026 -0400
[#8621] test for FTP redirect bypassing internal-host protection
NonPrivateUrl is only checked by our http/https handlers. A redirect
to ftp:// reaches urllib's default FTPHandler, which has no such
check, so it can reach internal/private hosts unvalidated.
Co-Authored-By: Claude Sonnet 5 <no...@anthropic.com>
---
Allura/allura/tests/test_helpers.py | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/Allura/allura/tests/test_helpers.py b/Allura/allura/tests/test_helpers.py
index 5ea729e59..fbee314fe 100644
--- a/Allura/allura/tests/test_helpers.py
+++ b/Allura/allura/tests/test_helpers.py
@@ -16,6 +16,7 @@
# under the License.
import io
+import urllib.request
from unittest import skipIf
from os import path
from datetime import datetime, timedelta
@@ -535,6 +536,19 @@ def test_internal_invalid(self, url):
def test_ok(self, url):
h.urlopen(url)
+ @pytest.mark.parametrize('url', [
+ f'https://{httpbin_domain}/redirect-to?url=ftp://127.0.0.1/',
+ f'https://{httpbin_domain}/redirect-to?url=ftp://localhost/',
+ ])
+ def test_internal_invalid_ftp_redirect(self, url):
+ # our opener has no ftp handler at all, so a redirect to ftp:// can't be used to bypass NonPrivateUrl
+ with pytest.raises(urllib.error.URLError, match='unknown url type'):
+ h.urlopen(url)
+
+ def test_ftp_not_supported(self):
+ with pytest.raises(urllib.error.URLError, match='unknown url type'):
+ urllib.request.urlopen('ftp://127.0.0.1/some/file') # noqa: S310
+
@patch('urllib.request.urlopen')
@patch.dict(config, {'urlopen_allow_internal_hostnames': 'true'})
def test_no_error(self, urlopen):