mirror of
https://github.com/hansjone/netx.git
synced 2026-10-09 03:10:46 +08:00
Ignore Huawei/ZTE post-login banners when classifying auth failures.
Bastion hop success notices mention authentication failure count and were misread as target_auth_rejected. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
015b935f81
commit
60186a12e3
2 changed files with 36 additions and 1 deletions
|
|
@ -26,12 +26,24 @@ _AUTH_PATTERNS: tuple[re.Pattern[str], ...] = tuple(
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Huawei / ZTE post-login security banner (success path via SSH or bastion hop).
|
||||||
|
# Example: "Afterwards, 0 authentication failure occurred."
|
||||||
|
_LOGIN_SUCCESS_NOTICE = re.compile(
|
||||||
|
r"^.*(?:"
|
||||||
|
r"last\s+successful\s+login\s+was\s+performed"
|
||||||
|
r"|afterwards,\s*\d+\s+authentication\s+failures?\s+occurred"
|
||||||
|
r"|上次成功登录"
|
||||||
|
r"|之后发生了?\s*\d+\s*次认证失败"
|
||||||
|
r").*$",
|
||||||
|
re.I | re.M,
|
||||||
|
)
|
||||||
|
|
||||||
_PROMPT_TIMEOUT = re.compile(r"pattern not detected|readtimeout|read timeout", re.I)
|
_PROMPT_TIMEOUT = re.compile(r"pattern not detected|readtimeout|read timeout", re.I)
|
||||||
|
|
||||||
|
|
||||||
def find_auth_failure_snippet(text: str, *, max_len: int = 220) -> str | None:
|
def find_auth_failure_snippet(text: str, *, max_len: int = 220) -> str | None:
|
||||||
"""Return a short matching auth-failure line/snippet, or None."""
|
"""Return a short matching auth-failure line/snippet, or None."""
|
||||||
blob = str(text or "")
|
blob = _LOGIN_SUCCESS_NOTICE.sub("", str(text or ""))
|
||||||
if not blob.strip():
|
if not blob.strip():
|
||||||
return None
|
return None
|
||||||
for pat in _AUTH_PATTERNS:
|
for pat in _AUTH_PATTERNS:
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,29 @@ class CliAuthClassifyTests(unittest.TestCase):
|
||||||
msg = format_cli_failure(AuthenticationException())
|
msg = format_cli_failure(AuthenticationException())
|
||||||
self.assertTrue(msg.startswith("auth_rejected:"))
|
self.assertTrue(msg.startswith("auth_rejected:"))
|
||||||
|
|
||||||
|
def test_huawei_post_login_banner_not_auth_failure(self):
|
||||||
|
"""Bastion/SSH hop success banner must not be classified as auth reject."""
|
||||||
|
text = (
|
||||||
|
"Info: The max number of VTY users is 21, "
|
||||||
|
"the number of current VTY users online is 1.\n"
|
||||||
|
"The last successful login was performed at 19:28:16 08-02-2026 "
|
||||||
|
"from 10.229.147.122 through SSH. Afterwards, 0 authentication "
|
||||||
|
"failure occurred.\n"
|
||||||
|
"<HUAWEI>"
|
||||||
|
)
|
||||||
|
self.assertIsNone(find_auth_failure_snippet(text))
|
||||||
|
msg = format_cli_failure("ReadTimeout: Pattern not detected", text)
|
||||||
|
self.assertFalse(msg.startswith("auth_rejected:"))
|
||||||
|
|
||||||
|
def test_real_auth_failure_still_detected_near_banner(self):
|
||||||
|
text = (
|
||||||
|
"The last successful login was performed at 19:28:16 08-02-2026 "
|
||||||
|
"from 10.229.147.122 through SSH. Afterwards, 0 authentication "
|
||||||
|
"failure occurred.\n"
|
||||||
|
"Error: Username or password is wrong.\n"
|
||||||
|
)
|
||||||
|
self.assertIn("Username or password is wrong", find_auth_failure_snippet(text) or "")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue