diff --git a/lib/core/option.py b/lib/core/option.py index 0af99b6fe..74f8feaa7 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -1330,7 +1330,7 @@ def _setHTTPHandlers(): match = re.search(r"\A([^:]*):([^:]*)@([^@]+)\Z", _.netloc) if match: - username, password = match.group(1), match.group(2) + username, password = _urllib.parse.unquote(match.group(1)), _urllib.parse.unquote(match.group(2)) else: username, password = None, None @@ -1375,6 +1375,9 @@ def _setHTTPHandlers(): if conf.proxyCred: # Reference: http://stackoverflow.com/questions/34079/how-to-specify-an-authenticated-proxy-for-a-python-http-connection proxyString = "%s@" % conf.proxyCred + elif match: + # Note: credentials from the proxy URL itself (kept URL-encoded, as urllib decodes them on its own) + proxyString = "%s:%s@" % (match.group(1), match.group(2)) else: proxyString = "" diff --git a/lib/core/settings.py b/lib/core/settings.py index 1dcbe1746..ec1d90f92 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from lib.core.enums import OS from thirdparty import six # sqlmap version (...) -VERSION = "1.10.10.25" +VERSION = "1.10.10.26" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) diff --git a/tests/test_option.py b/tests/test_option.py index c4e10cc1e..d48eb4d1e 100644 --- a/tests/test_option.py +++ b/tests/test_option.py @@ -1646,5 +1646,50 @@ class TestNonSqlTechniqueRegistry(unittest.TestCase): self.assertEqual(optDict["Techniques"].get(name), "boolean", name) +class TestSetHTTPHandlersProxyCredentials(unittest.TestCase): + """_setHTTPHandlers(): credentials embedded in the proxy URL (dropped for HTTP(S) and left + URL-encoded for SOCKS ever since the fix for #5875). Global state (shared proxy handler, + installed opener, SOCKS hooks) is pinned, so nothing leaks into other modules.""" + + _KEYS = ("proxy", "proxyCred", "proxyList", "hostname", "ignoreProxy", "cj", "keepAlive") + + def setUp(self): + from _testutils import save_attrs + + self._socks = [] + self._saved = save_attrs(option.socks, "setdefaultproxy", "wrapmodule", "unwrapmodule") + self._saved += save_attrs(option.proxyHandler, "proxies", "http_open", "https_open") + self._saved += save_attrs(option._urllib.request, "install_opener") + + option.socks.setdefaultproxy = lambda *args, **kwargs: self._socks.append((kwargs.get("username"), kwargs.get("password"))) + option.socks.wrapmodule = option.socks.unwrapmodule = lambda *args: None + option._urllib.request.install_opener = lambda *args: None + + def tearDown(self): + from _testutils import restore_attrs + + restore_attrs(self._saved) + + def _run(self, proxy, proxyCred=None): + with _preserve(conf, *self._KEYS), _preserve(kb, "proxies"): + conf.proxy, conf.proxyCred, conf.proxyList = proxy, proxyCred, None + option._setHTTPHandlers() + return (kb.proxies or {}).get("http") + + def test_http_url_credentials(self): + # Note: kept URL-encoded, as urllib decodes them itself when building 'Proxy-Authorization' + self.assertEqual(self._run("http://user:p%40ss@127.0.0.1:8080"), "user:p%40ss@127.0.0.1:8080") + + def test_proxy_cred_takes_precedence(self): + self.assertEqual(self._run("http://user:p%40ss@127.0.0.1:8080", "other:pw"), "other:pw@127.0.0.1:8080") + + def test_http_without_credentials(self): + self.assertEqual(self._run("http://127.0.0.1:8080"), "127.0.0.1:8080") + + def test_socks_url_credentials_decoded(self): + self._run("socks5://user:p%40ss@127.0.0.1:1080") + self.assertEqual(self._socks, [("user", "p@ss")]) + + if __name__ == "__main__": unittest.main(verbosity=2)