diff --git a/src/skillspector/yara_rules/malware.yar.b64 b/src/skillspector/yara_rules/malware.yar.b64 index 047616559..ba26a9c4d 100644 --- a/src/skillspector/yara_rules/malware.yar.b64 +++ b/src/skillspector/yara_rules/malware.yar.b64 @@ -147,10 +147,19 @@ ID0gIkhJR0giDQogICAgICAgIGNvbmZpZGVuY2UgPSAiMC43NSINCiAgICAgICAgcmVmZXJlbmNl ID0gImh0dHBzOi8vZ2l0aHViLmNvbS9OZW8yM3gwL3NpZ25hdHVyZS1iYXNlIg0KICAgIHN0cmlu Z3M6DQogICAgICAgICRjaHJvbWVfbG9naW4gICAgID0gL0Nocm9tZS4qTG9naW5ccypEYXRhLyBu b2Nhc2UNCiAgICAgICAgJGZpcmVmb3hfbG9naW5zICAgPSAvbG9naW5zXC5qc29uLipmaXJlZm94 -LyBub2Nhc2UNCiAgICAgICAgJGJyb3dzZXJfY29va2llcyAgPSAvQ29va2llcy4qKGNocm9tZXxm -aXJlZm94fGVkZ2V8b3BlcmEpLyBub2Nhc2UNCiAgICAgICAgJHdhbGxldF9zdGVhbCAgICAgPSAv -d2FsbGV0XC5kYXQvIG5vY2FzZQ0KICAgICAgICAkbWltaWthdHogICAgICAgICA9ICJtaW1pa2F0 -eiIgbm9jYXNlDQogICAgICAgICRsYXphZ25lICAgICAgICAgID0gImxhemFnbmUiIG5vY2FzZQ0K -ICAgICAgICAkY3JlZF9kdW1wX3NhbSAgICA9IC9yZWdccytzYXZlXHMrLipcXHNhbS8gbm9jYXNl -DQogICAgICAgICRudGRzX2R1bXAgICAgICAgID0gL250ZHNcLmRpdC8gbm9jYXNlDQogICAgY29u -ZGl0aW9uOg0KICAgICAgICBhbnkgb2YgdGhlbQ0KfQ0K +LyBub2Nhc2UNCiAgICAgICAgJGJyb3dzZXJfY29va2llcyAgPSAvKENocm9tZXxDaHJvbWl1bXxF +ZGdlfEJyYXZlLUJyb3dzZXIpXHcqW1xcXC8nXHgyMiwgXXsxLDV9KFVzZXIgRGF0YVteXG5dezAs +ODB9fChEZWZhdWx0fFByb2ZpbGUgXGQrKVtcXFwvJ1x4MjIsIF17MSw1fShOZXR3b3JrW1xcXC8n +XHgyMiwgXXsxLDV9KT8pQ29va2llcy8gbm9jYXNlDQogICAgICAgICRjb29raWVzX2Jyb3dzZXIg +ID0gL0Nvb2tpZXNbXlxuXXswLDgwfShbXmEtel0oY2hyb21lfGNocm9taXVtfGVkZ2V8YnJhdmV8 +b3BlcmF8ZmlyZWZveCkoX3xcLlx3fFsoKl0pfFtcXFwvXC0uJ1x4MjJdKGNocm9tZXxjaHJvbWl1 +bXxlZGdlfGJyYXZlfG9wZXJhfGZpcmVmb3gpW1xcXC9dfFsnXHgyMl0oY2hyb21lfGNocm9taXVt +fGVkZ2V8YnJhdmV8b3BlcmF8ZmlyZWZveClbJ1x4MjJdKS8gbm9jYXNlDQogICAgICAgICRvcGVy +YV9jb29raWVzICAgID0gL09wZXJhIChTdGFibGV8R1ggU3RhYmxlKVteXG5dezAsODB9Q29va2ll +cy8gbm9jYXNlDQogICAgICAgICRmaXJlZm94X2Nvb2tpZXMgID0gL2Nvb2tpZXNcLnNxbGl0ZS8g +bm9jYXNlDQogICAgICAgICR3YWxsZXRfc3RlYWwgICAgID0gL3dhbGxldFwuZGF0LyBub2Nhc2UN +CiAgICAgICAgJG1pbWlrYXR6ICAgICAgICAgPSAibWltaWthdHoiIG5vY2FzZQ0KICAgICAgICAk +bGF6YWduZSAgICAgICAgICA9ICJsYXphZ25lIiBub2Nhc2UNCiAgICAgICAgJGNyZWRfZHVtcF9z +YW0gICAgPSAvcmVnXHMrc2F2ZVxzKy4qXFxzYW0vIG5vY2FzZQ0KICAgICAgICAkbnRkc19kdW1w +ICAgICAgICA9IC9udGRzXC5kaXQvIG5vY2FzZQ0KICAgIGNvbmRpdGlvbjoNCiAgICAgICAgYW55 +IG9mIHRoZW0NCn0NCg== diff --git a/tests/nodes/analyzers/test_static_yara.py b/tests/nodes/analyzers/test_static_yara.py index 71289029e..0e32ff348 100644 --- a/tests/nodes/analyzers/test_static_yara.py +++ b/tests/nodes/analyzers/test_static_yara.py @@ -613,6 +613,53 @@ def _single_line_perl_socket_shell() -> str: ).decode() +# Browser cookie-store paths an information stealer reads (base64 so the test +# source itself carries no stealer indicator). +_STEALER_COOKIE_PATHS = { + "chrome_windows": "b3MucGF0aC5qb2luKG9zLmVudmlyb25bJ0xPQ0FMQVBQREFUQSddLCAnR29vZ2xlJywgJ0Nocm9tZScsICdVc2VyIERhdGEnLCAnRGVmYXVsdCcsICdOZXR3b3JrJywgJ0Nvb2tpZXMnKQ==", + "edge_windows": "Y29weSAlTE9DQUxBUFBEQVRBJVxNaWNyb3NvZnRcRWRnZVxVc2VyIERhdGFcRGVmYXVsdFxOZXR3b3JrXENvb2tpZXMgb3V0LmRi", + "brave_linux": "fi8uY29uZmlnL0JyYXZlU29mdHdhcmUvQnJhdmUtQnJvd3Nlci9Vc2VyIERhdGEvRGVmYXVsdC9Db29raWVz", + "chrome_macos": "cGF0aCA9IGhvbWUgKyAiL0xpYnJhcnkvQXBwbGljYXRpb24gU3VwcG9ydC9Hb29nbGUvQ2hyb21lL0RlZmF1bHQvQ29va2llcyI=", + "opera_windows": "JHAgPSAiJGVudjpBUFBEQVRBXE9wZXJhIFNvZnR3YXJlXE9wZXJhIFN0YWJsZVxOZXR3b3JrXENvb2tpZXMi", + "firefox_profile": "c3JjID0gcHJvZmlsZV9kaXIgLyAnY29va2llcy5zcWxpdGUn", + "chrome_pathlib_spaced": "UGF0aC5ob21lKCkgLyAiQXBwRGF0YSIgLyAiTG9jYWwiIC8gIkdvb2dsZSIgLyAiQ2hyb21lIiAvICJVc2VyIERhdGEiIC8gIkRlZmF1bHQiIC8gIk5ldHdvcmsiIC8gIkNvb2tpZXMi", +} + + +def _stealer_cookie_path(name: str) -> str: + return base64.b64decode(_STEALER_COOKIE_PATHS[name]).decode() + + +# Code that names the store before the browser, as the previous pattern did. +_STEALER_COOKIE_CODE = { + "sqlite_connect": "Y29va2llc19kYiA9IHNxbGl0ZTMuY29ubmVjdChjaHJvbWVfcHJvZmlsZSArICIvQ29va2llcyIp", + "copy_to_output": "c2h1dGlsLmNvcHkocHJvZmlsZSAvICJDb29raWVzIiwgY2hyb21lX291dCk=", + "browser_constant": "ZGIgPSBvcy5wYXRoLmpvaW4oQ0hST01FX0RJUiwgIkRlZmF1bHQiLCAiQ29va2llcyIp", + "browser_argument": "c3RlYWwoIkNvb2tpZXMiLCAiZmlyZWZveCIp", + "keyword_argument": "Z3JhYl9jb29raWVzKGJyb3dzZXI9ImVkZ2UiKQ==", + "attribute_access": "Y29va2llcyA9IHJlYWQob3BlcmEucHJvZmlsZSk=", + "find_glob": "ZmluZCB+IC1uYW1lIENvb2tpZXMgLXBhdGggJypjaHJvbWUqJw==", + "quoted_path_segment": "c2h1dGlsLmNvcHkoaG9tZSArICIvQ29va2llcyIsICIvdG1wLyIgKyAiY2hyb21pdW0vIiArIG5hbWUp", + "dashed_path_segment": "c3JjID0gIkNvb2tpZXMiOyBkc3QgPSBwcm9maWxlX3Jvb3QgKyAiLy5jb25maWcvZ29vZ2xlLWNocm9tZS9vdXQi", +} + + +# Documentation that names the browser store and, later on the same line, a +# browser or product name (issue #751: a Supabase skill description). +_COOKIE_PROSE = [ + "description: Auth issues (login, logout, sessions, JWT, cookies, getSession, getUser, " + "getClaims, RLS); Supabase CLI or MCP server; debugging timeouts, Edge Function crashes\n", + "Set cookies for Edge Functions with the SSR helper.\n", + "Cookies set by the Edge middleware are httpOnly.\n", + "Forward cookies to Chrome headless through the Playwright context.\n", + "Clear cookies in Chrome.\n", + "Cookies are shared with Edge, Chrome and Firefox.\n", + "Third-party cookies are blocked by Chrome's tracking protection.\n", + "Session cookies are dropped by Firefox's Enhanced Tracking Protection.\n", + "Cookies: Chrome/Edge supported, Safari planned.\n", +] + + def _has_rule(findings: list, rule_name: str) -> bool: """Return True when a finding message references a specific YARA rule. @@ -2371,6 +2418,41 @@ def test_known_webshell_rule_matches_family_markers(self, fixture, filename): findings = _run_builtin(_webshell_fixture(fixture), filename) assert _has_rule(findings, "php_webshell_known") + @pytest.mark.parametrize( + "content", + _COOKIE_PROSE, + ids=[ + "supabase_description", + "edge_functions", + "edge_middleware", + "chrome_headless", + "clear_in_chrome", + "shared_with_browsers", + "chrome_possessive", + "firefox_possessive", + "browser_list_with_slash", + ], + ) + def test_info_stealer_ignores_cookie_prose(self, content): + findings = _run_builtin(content, "SKILL.md") + assert not _has_rule(findings, "info_stealer") + + @pytest.mark.parametrize("name", sorted(_STEALER_COOKIE_PATHS)) + def test_info_stealer_matches_browser_cookie_store_paths(self, name): + findings = _run_builtin(_stealer_cookie_path(name) + "\n", "collect.py") + assert _has_rule(findings, "info_stealer") + + @pytest.mark.parametrize("name", sorted(_STEALER_COOKIE_CODE)) + def test_info_stealer_matches_code_naming_the_store_first(self, name): + code = base64.b64decode(_STEALER_COOKIE_CODE[name]).decode() + findings = _run_builtin(code + "\n", "collect.py") + assert _has_rule(findings, "info_stealer") + + def test_info_stealer_matches_cookie_store_path_next_to_prose(self): + content = _COOKIE_PROSE[1] + _stealer_cookie_path("chrome_windows") + "\n" + findings = _run_builtin(content, "collect.py") + assert _has_rule(findings, "info_stealer") + # ── Rule caching ──────────────────────────────────────────────────────