"""Tests for the Injector target-resolution logic (no live HTTP).""" from __future__ import annotations from intelliscan.modules.crawler import CrawlResult, Form, UrlParam from intelliscan.modules.injector import Injector class _FakeHttp: """Minimal stand-in; target resolution never performs requests.""" def get_csrf_token(self, url): # pragma: no cover - unused in these tests return None def _make_injector(crawl_result=None) -> Injector: return Injector(_FakeHttp(), "http://localhost:8080", crawl_result=crawl_result) def test_falls_back_to_dvwa_when_no_crawl(): inj = _make_injector(crawl_result=None) targets = inj._resolve_targets() assert targets, "expected DVWA fallback targets" assert all(t.source.startswith("dvwa") for t in targets) def test_skips_state_mutating_forms(): # security.php (changes the security level) and a password-change form must # never be injected — doing so corrupts the scan/session state mid-run. crawl = CrawlResult( target="http://localhost:8080", pages_visited=3, forms=[ Form( url="http://localhost:8080/security.php", action="http://localhost:8080/security.php", method="POST", inputs=[{"name": "security", "type": "text", "value": ""}], ), Form( url="http://localhost:8080/vulnerabilities/csrf/", action="http://localhost:8080/vulnerabilities/csrf/", method="GET", inputs=[ {"name": "password_new", "type": "password", "value": ""}, {"name": "password_conf", "type": "password", "value": ""}, ], ), Form( url="http://localhost:8080/vulnerabilities/sqli/", action="http://localhost:8080/vulnerabilities/sqli/", method="GET", inputs=[{"name": "id", "type": "text", "value": ""}], ), ], url_params=[], ) inj = _make_injector(crawl) targets = inj._resolve_targets() urls = {t.url for t in targets} assert urls == {"http://localhost:8080/vulnerabilities/sqli/"} def test_empty_crawl_does_not_fall_back_to_dvwa(): # A supplied-but-empty crawl (blocked/unreachable target) must NOT fabricate # DVWA targets — that produced phantom findings from WAF/error pages. crawl = CrawlResult( target="https://blocked.example", pages_visited=0, forms=[], url_params=[], blocked_responses=1, start_reachable=False, ) inj = _make_injector(crawl) assert inj._resolve_targets() == [] def test_resolves_form_inputs_and_skips_submit(): crawl = CrawlResult( target="http://localhost:8080", pages_visited=1, forms=[ Form( url="http://localhost:8080/login", action="http://localhost:8080/login", method="POST", inputs=[ {"name": "username", "type": "text", "value": ""}, {"name": "password", "type": "password", "value": ""}, {"name": "Login", "type": "submit", "value": "Login"}, ], ) ], url_params=[], ) inj = _make_injector(crawl) targets = inj._resolve_targets() assert len(targets) == 1 t = targets[0] assert t.source == "crawl-form" assert set(t.inject_params) == {"username", "password"} # text fields assert t.static_fields == {"Login": "Login"} # submit preserved def test_resolves_url_params(): crawl = CrawlResult( target="http://localhost:8080", pages_visited=1, forms=[], url_params=[UrlParam(url="http://localhost:8080/item?id=3&cat=2", params=["id", "cat"])], ) inj = _make_injector(crawl) targets = inj._resolve_targets() assert len(targets) == 1 t = targets[0] assert t.source == "crawl-param" assert t.method == "GET" assert t.url == "http://localhost:8080/item" # query stripped assert "lfi" in t.vuln_types # params probe LFI too def test_build_tasks_pairs_payloads_with_targets(): crawl = CrawlResult( target="http://localhost:8080", pages_visited=1, forms=[], url_params=[UrlParam(url="http://localhost:8080/x?q=1", params=["q"])], ) inj = _make_injector(crawl) tasks = list(inj._build_tasks(inj._resolve_targets())) # Every task is (target, vuln_type, payload) and only uses loaded vuln types. assert tasks for target, vuln_type, payload in tasks: assert vuln_type in target.vuln_types assert isinstance(payload, str) and payload