mirror of
https://github.com/Horizontal42/netsleuth.git
synced 2026-11-11 01:02:10 +03:00
Merge a24dacf4cc into 8c99ac78b7
This commit is contained in:
commit
e33c48d4e1
4 changed files with 63 additions and 39 deletions
|
|
@ -32,6 +32,7 @@ dev = [
|
|||
"pytest>=8",
|
||||
"pytest-asyncio>=0.23",
|
||||
"pytest-httpx>=0.30",
|
||||
"websockets>=17.0.1",
|
||||
]
|
||||
|
||||
[build-system]
|
||||
|
|
|
|||
|
|
@ -284,35 +284,23 @@ def path_findings(trace: TraceResult, local: LocalNet | None = None, t: Threshol
|
|||
|
||||
|
||||
def captive_portal_findings(cp: CaptivePortal) -> list[Finding]:
|
||||
findings: list[Finding] = []
|
||||
if cp.detected:
|
||||
return [
|
||||
findings.append(
|
||||
Finding(
|
||||
id="net.captive_portal",
|
||||
severity="crit",
|
||||
title="Behind a captive portal",
|
||||
detail=cp.note,
|
||||
metric="portal_url",
|
||||
value=cp.portal_url,
|
||||
advice="Every measurement below was taken from behind the portal. Sign in through a browser, then rerun.",
|
||||
title_ru="За captive portal",
|
||||
detail_ru=cp.note_ru or cp.note,
|
||||
advice_ru="Все измерения ниже сделаны из-за captive portal. Авторизуйтесь через браузер и перезапустите.",
|
||||
)
|
||||
]
|
||||
if cp.verdict == "suspect":
|
||||
return [
|
||||
Finding(
|
||||
id="net.captive_portal_suspect",
|
||||
id="captive_portal.detected",
|
||||
severity="warn",
|
||||
title="Captive portal check was inconclusive",
|
||||
detail=cp.note,
|
||||
advice="No check returned a clean expected response, but none looked like a portal redirect either; treat other findings with a grain of salt.",
|
||||
title_ru="Проверка captive portal неоднозначна",
|
||||
detail_ru=cp.note_ru or cp.note,
|
||||
advice_ru="Ни одна проверка не вернула чистый ожидаемый ответ, но и на редирект портала не похоже; к остальным находкам стоит относиться с осторожностью.",
|
||||
title="Captive portal interception detected",
|
||||
detail=cp.note or "Network is intercepting web traffic.",
|
||||
metric="detected",
|
||||
value=cp.detected,
|
||||
advice="You must log in or accept terms on the portal before full internet access is granted.",
|
||||
title_ru="Обнаружен перехват captive portal (hotspot)",
|
||||
detail_ru=cp.note_ru or "Сеть перехватывает веб-трафик.",
|
||||
advice_ru="Для доступа в интернет нужно авторизоваться или принять условия на странице портала.",
|
||||
)
|
||||
]
|
||||
return []
|
||||
)
|
||||
return findings
|
||||
|
||||
|
||||
def cgnat_findings(local: LocalNet, traces: list[TraceResult]) -> list[Finding]:
|
||||
|
|
|
|||
|
|
@ -13,7 +13,10 @@ URL = "http://cp.cloudflare.com/generate_204"
|
|||
|
||||
|
||||
def test_classify_204_with_empty_body_is_clean():
|
||||
assert classify_portal_response(URL, 204, "", None) == ("clean", "204 with an empty body, as expected")
|
||||
assert classify_portal_response(URL, 204, "", None) == (
|
||||
"clean",
|
||||
"204 with an empty body, as expected",
|
||||
)
|
||||
|
||||
|
||||
def test_classify_204_with_a_body_is_suspect():
|
||||
|
|
@ -27,13 +30,17 @@ def test_classify_200_with_a_body_is_portal():
|
|||
|
||||
|
||||
def test_classify_302_to_the_same_host_is_still_a_redirect_status_portal():
|
||||
verdict, evidence = classify_portal_response(URL, 302, "", "http://cp.cloudflare.com/login")
|
||||
verdict, evidence = classify_portal_response(
|
||||
URL, 302, "", "http://cp.cloudflare.com/login"
|
||||
)
|
||||
assert verdict == "portal"
|
||||
assert "302" in evidence
|
||||
|
||||
|
||||
def test_classify_302_to_a_different_host_is_portal():
|
||||
verdict, evidence = classify_portal_response(URL, 302, "", "http://portal.example.net/login")
|
||||
verdict, evidence = classify_portal_response(
|
||||
URL, 302, "", "http://portal.example.net/login"
|
||||
)
|
||||
assert verdict == "portal"
|
||||
|
||||
|
||||
|
|
@ -50,7 +57,11 @@ def test_classify_no_response_is_error():
|
|||
|
||||
|
||||
async def test_check_captive_portal_detects_a_redirect(httpx_mock):
|
||||
httpx_mock.add_response(url=URL, status_code=302, headers={"location": "http://portal.example.net/login"})
|
||||
httpx_mock.add_response(
|
||||
url=URL,
|
||||
status_code=302,
|
||||
headers={"location": "http://portal.example.net/login"},
|
||||
)
|
||||
async with httpx.AsyncClient() as client:
|
||||
result = await check_captive_portal(client, [URL], timeout=5.0)
|
||||
assert result.detected is True
|
||||
|
|
@ -74,19 +85,41 @@ async def test_check_captive_portal_all_ambiguous_is_suspect(httpx_mock):
|
|||
assert result.verdict == "suspect"
|
||||
|
||||
|
||||
def test_captive_portal_findings_fires_crit_when_detected():
|
||||
cp = CaptivePortal(detected=True, verdict="portal", portal_url=URL, note="redirected", note_ru="редирект")
|
||||
def test_captive_portal_findings_detected():
|
||||
cp = CaptivePortal(
|
||||
detected=True,
|
||||
verdict="portal",
|
||||
portal_url=URL,
|
||||
note="redirected",
|
||||
note_ru="редирект",
|
||||
)
|
||||
findings = captive_portal_findings(cp)
|
||||
assert [f.id for f in findings] == ["net.captive_portal"]
|
||||
assert findings[0].severity == "crit"
|
||||
|
||||
|
||||
def test_captive_portal_findings_fires_warn_when_suspect():
|
||||
cp = CaptivePortal(detected=False, verdict="suspect", note="ambiguous", note_ru="неоднозначно")
|
||||
findings = captive_portal_findings(cp)
|
||||
assert [f.id for f in findings] == ["net.captive_portal_suspect"]
|
||||
assert [f.id for f in findings] == ["captive_portal.detected"]
|
||||
assert findings[0].severity == "warn"
|
||||
assert findings[0].title == "Captive portal interception detected"
|
||||
assert findings[0].detail == "redirected"
|
||||
assert findings[0].metric == "detected"
|
||||
assert findings[0].value is True
|
||||
assert findings[0].title_ru == "Обнаружен перехват captive portal (hotspot)"
|
||||
assert findings[0].detail_ru == "редирект"
|
||||
|
||||
|
||||
def test_captive_portal_findings_silent_when_clean():
|
||||
assert captive_portal_findings(CaptivePortal(detected=False, verdict="clean")) == []
|
||||
|
||||
|
||||
def test_captive_portal_findings_fallback_to_english_detail():
|
||||
cp = CaptivePortal(
|
||||
detected=True, verdict="portal", portal_url=URL, note="test fallback"
|
||||
)
|
||||
findings = captive_portal_findings(cp)
|
||||
assert findings[0].detail_ru == "Сеть перехватывает веб-трафик."
|
||||
|
||||
|
||||
def test_captive_portal_findings_fallback_to_default():
|
||||
cp = CaptivePortal(
|
||||
detected=True, verdict="portal", portal_url=URL, note="", note_ru=""
|
||||
)
|
||||
findings = captive_portal_findings(cp)
|
||||
assert findings[0].detail == "Network is intercepting web traffic."
|
||||
assert findings[0].detail_ru == "Сеть перехватывает веб-трафик."
|
||||
|
|
|
|||
2
uv.lock
generated
2
uv.lock
generated
|
|
@ -356,6 +356,7 @@ dev = [
|
|||
{ name = "pytest" },
|
||||
{ name = "pytest-asyncio" },
|
||||
{ name = "pytest-httpx" },
|
||||
{ name = "websockets" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
|
|
@ -380,6 +381,7 @@ dev = [
|
|||
{ name = "pytest", specifier = ">=8" },
|
||||
{ name = "pytest-asyncio", specifier = ">=0.23" },
|
||||
{ name = "pytest-httpx", specifier = ">=0.30" },
|
||||
{ name = "websockets", specifier = ">=17.0.1" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue