diff --git a/lib/core/settings.py b/lib/core/settings.py index 6fd91c82f..431dff718 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from lib.core.enums import OS from thirdparty import six # sqlmap version (...) -VERSION = "1.10.10.15" +VERSION = "1.10.10.16" 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) @@ -216,6 +216,9 @@ DUCKDUCKGO_REGEX = r']*>]*href="(?:https?://www\.bing\.com/ck/a\?[^"]*?[?;]u=a1([^&"]+)[^"]*|([^"]+))"' +# Markers of search engine abuse/captcha pages (i.e. used IP address got blocked) +SEARCH_ABUSE_MARKERS = ("detected unusual traffic", "issue with the Tor Exit Node you are currently using", "anomaly.js", "Verifying your browser", "automated queries") + # Dummy user agent for search (if default one returns different results) DUMMY_SEARCH_USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64; rv:141.0) Gecko/20100101 Firefox/141.0" diff --git a/lib/utils/search.py b/lib/utils/search.py index abef010cc..95495eca2 100644 --- a/lib/utils/search.py +++ b/lib/utils/search.py @@ -29,6 +29,7 @@ from lib.core.settings import DUMMY_SEARCH_USER_AGENT from lib.core.settings import GOOGLE_CONSENT_COOKIE from lib.core.settings import GOOGLE_REGEX from lib.core.settings import HTTP_ACCEPT_ENCODING_HEADER_VALUE +from lib.core.settings import SEARCH_ABUSE_MARKERS from lib.core.settings import UNICODE_ENCODING from lib.request.basic import decodePage from thirdparty.six.moves import http_client as _http_client @@ -124,7 +125,7 @@ def _search(dork): break # Note: stop at the first engine that actually returns results (others are only fallbacks) # Note: switch proxy (if available) when an abuse/captcha page was served (instead of pointlessly falling through to the next engine from the same blocked IP) - if conf.proxyList and (("detected unusual traffic" in page) or ("issue with the Tor Exit Node you are currently using" in page)): + if conf.proxyList and any(marker in page for marker in SEARCH_ABUSE_MARKERS): warnMsg = "%s has detected 'unusual' traffic from the used IP address" % name raise SqlmapBaseException(warnMsg) diff --git a/tests/test_dork_search.py b/tests/test_dork_search.py new file mode 100644 index 000000000..00c046a60 --- /dev/null +++ b/tests/test_dork_search.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Unit tests for lib/utils/search.py (dork search), exercising the engine loop by +MOCKING the network layer (_fetch). + +No network is involved: _fetch is patched to return canned search engine result +pages, so we can assert both on the links extracted per engine and on the +abuse/captcha detection that triggers proxy rotation (SEARCH_ABUSE_MARKERS). +""" + +import os +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap + +bootstrap() + +from lib.core.data import conf +from lib.core.exception import SqlmapBaseException +from lib.core.settings import SEARCH_ABUSE_MARKERS +import lib.utils.search as smod + +BING_PAGE = '

a

' +DUCKDUCKGO_PAGE = 'b' + +class TestDorkSearch(unittest.TestCase): + def setUp(self): + conf.googlePage = 1 + conf.proxyList = None + self._fetch = smod._fetch + + def tearDown(self): + smod._fetch = self._fetch + + def _pages(self, *pages): + """Patches _fetch to serve `pages` in engine order (None meaning no reply)""" + + served = list(pages) + smod._fetch = lambda *args, **kwargs: served.pop(0) if served else None + + def test_duckduckgo_results(self): + self._pages(DUCKDUCKGO_PAGE) + self.assertEqual(smod._search("dork"), ["https://www.example.com/b.php?id=2"]) + + def test_bing_fallback_decodes_redirect(self): + self._pages(None, BING_PAGE) + self.assertEqual(smod._search("dork"), ["https://www.example.com/a.php?id=1"]) + + def test_abuse_page_switches_proxy(self): + # Note: without '--proxy-list' there is nothing to switch to, hence no exception + for marker in SEARCH_ABUSE_MARKERS: + self._pages("%s" % marker) + conf.proxyList = None + self.assertEqual(smod._search("dork"), []) + + self._pages("%s" % marker) + conf.proxyList = ["http://127.0.0.1:8080"] + self.assertRaises(SqlmapBaseException, smod._search, "dork") + + def test_results_page_is_not_mistaken_for_abuse(self): + self._pages(DUCKDUCKGO_PAGE) + conf.proxyList = ["http://127.0.0.1:8080"] + self.assertEqual(smod._search("dork"), ["https://www.example.com/b.php?id=2"]) + +if __name__ == "__main__": + unittest.main()