From b5a589d9913fe1f78d4ec8dcec023ce4976a85a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Str=C3=B6mberg?= Date: Tue, 17 Dec 2024 17:16:53 -0500 Subject: [PATCH] Reduce Python CRITICAL false positives (setuptools, keylogger) (#717) * Address Python false-positives * Update testdata * yara fmt * rule tuning --- Makefile | 2 +- rules/exfil/stealer/keylogger.yara | 7 +-- rules/false_positives/conda_build.yara | 14 ------ rules/false_positives/py_hatch.yara | 4 +- rules/false_positives/setuptools.yara | 43 +++--------------- rules/impact/remote_access/py_setuptools.yara | 44 +++++++++++++++++-- .../python/2021.DiscordSafety/setup.py.simple | 2 +- tests/python/2024.Custom.RAT/output.py.simple | 2 +- .../clean/airflow/botocore_config.py.simple | 7 +++ tests/python/clean/airflow/db.py.simple | 13 ++++++ .../clean/airflow/kubernetes_engine.py.simple | 6 +++ .../conda-build/_load_setup_py_data.py.simple | 2 +- tests/python/clean/fonttools/psLib.py.simple | 3 ++ .../setup.py.simple | 1 + .../google-cloud-sdk/requests_setup.py.simple | 12 +++++ tests/python/clean/idna/setup.py.simple | 1 + .../clean/matplotlib/_backend_tk.py.simple | 6 +++ .../clean/matplotlib/backend_bases.py.simple | 7 +++ .../clean/matplotlib/backend_qt.py.simple | 9 ++++ .../clean/matplotlib/backend_wx.py.simple | 5 +++ .../clean/mitmproxy/raw_display.py.simple | 16 +++++++ tests/python/clean/ml_sdk/setup.py.simple | 1 + tests/python/clean/numba/support.py.simple | 1 - .../pydevd/setup_pydevd_cython.py.simple | 2 +- tests/python/clean/requests/setup.py.simple | 1 + .../setuptools/test_pyprojecttoml.py.simple | 1 - 26 files changed, 144 insertions(+), 68 deletions(-) delete mode 100644 rules/false_positives/conda_build.yara create mode 100644 tests/python/clean/airflow/botocore_config.py.simple create mode 100644 tests/python/clean/airflow/db.py.simple create mode 100644 tests/python/clean/airflow/kubernetes_engine.py.simple create mode 100644 tests/python/clean/fonttools/psLib.py.simple create mode 100644 tests/python/clean/google-cloud-sdk/requests_setup.py.simple create mode 100644 tests/python/clean/matplotlib/_backend_tk.py.simple create mode 100644 tests/python/clean/matplotlib/backend_bases.py.simple create mode 100644 tests/python/clean/matplotlib/backend_qt.py.simple create mode 100644 tests/python/clean/matplotlib/backend_wx.py.simple create mode 100644 tests/python/clean/mitmproxy/raw_display.py.simple diff --git a/Makefile b/Makefile index 621cf5bb1..d0712b81e 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ SAMPLES_REPO ?= chainguard-dev/malcontent-samples -SAMPLES_COMMIT ?= 38d8faef6bcbd63f7cc02bb243b12aaa3e1ba70c +SAMPLES_COMMIT ?= 528a7e975638d2c5ce06da1af32c5918aa4d6c7e # BEGIN: lint-install ../malcontent # http://github.com/tinkerbell/lint-install diff --git a/rules/exfil/stealer/keylogger.yara b/rules/exfil/stealer/keylogger.yara index 843dfa435..4065e768a 100644 --- a/rules/exfil/stealer/keylogger.yara +++ b/rules/exfil/stealer/keylogger.yara @@ -14,7 +14,7 @@ rule keylogger_discord_telegram: high { filesize < 256KB and any of ($http*) and any of ($k*) } -rule py_keylogger_pynput_exfil: critical { +rule py_keylogger_pynput_exfil: high { meta: description = "listens for keyboard events and exfiltrates them" filetypes = "py" @@ -33,7 +33,7 @@ rule py_keylogger_pynput_exfil: critical { filesize < 256KB and any of ($http*) and all of ($f*) } -rule py_keykeyboard_exfil: critical { +rule py_keykeyboard_exfil: high { meta: description = "listens for keyboard events and exfiltrates them" filetypes = "py" @@ -44,7 +44,8 @@ rule py_keykeyboard_exfil: critical { $http_Discord = "Discord" $http_keylogger = /[kK]eylogger/ $http_Telegram = "Telegram" - $f_pynput = "keyboard" fullword + $f_pynput = "pynput" fullword + $f_keyboard = "keyboard" fullword $f_key = ".name" $f_listener = "on_release" diff --git a/rules/false_positives/conda_build.yara b/rules/false_positives/conda_build.yara deleted file mode 100644 index 8f27b4fe7..000000000 --- a/rules/false_positives/conda_build.yara +++ /dev/null @@ -1,14 +0,0 @@ -rule conda_load_setup_py_data: override { - meta: - setuptools_eval = "low" - description = "_load_setup_py_data.py" - - strings: - $exec = "exec(code, ns, ns)" - $func = "load_setup_py_data" - $sbom1 = "# Copyright (C) 2014 Anaconda, Inc" - $sbom2 = "# SPDX-License-Identifier: BSD-3-Clause" - - condition: - filesize < 8KB and all of them -} diff --git a/rules/false_positives/py_hatch.yara b/rules/false_positives/py_hatch.yara index 6f0ab2eab..3c0f1f9e4 100644 --- a/rules/false_positives/py_hatch.yara +++ b/rules/false_positives/py_hatch.yara @@ -1,7 +1,7 @@ rule migrate_py: override { meta: - description = "migrate.py" - setuptools_eval = "medium" + description = "migrate.py" + setuptools_eval_high = "medium" strings: $env = "'_HATCHLING_PORT_ADD_'" diff --git a/rules/false_positives/setuptools.yara b/rules/false_positives/setuptools.yara index fbf129d32..510c127ae 100644 --- a/rules/false_positives/setuptools.yara +++ b/rules/false_positives/setuptools.yara @@ -1,25 +1,7 @@ -rule test_pyprojecttoml: override { - meta: - description = "namespaces.py, test_pyprojecttoml.py" - setuptools_eval = "low" - - strings: - $example = "EXAMPLE" - $func1 = "def create_example(" - $func2 = "def verify_example(" - $func3 = "def test_read_configuration(" - $import = "import setuptools" - $kv = "\"pyproject.toml\": EXAMPLE" - $pyproject = "pyproject.toml" - - condition: - filesize < 16KB and all of them -} - rule setuptools_namespaces: override { meta: - description = "namespaces.py" - setuptools_eval = "low" + description = "namespaces.py" + setuptools_exec_high = "low" strings: $func1 = "def iter_namespace_pkgs(" @@ -36,30 +18,15 @@ rule setuptools_namespaces: override { rule numba_support: override { meta: - description = "support.py" - setuptools_eval = "low" + description = "support.py" + setuptools_exec_high = "low" strings: $comment = "Assorted utilities for use in tests." - $gh_issue = "numba#" + $gh_issue = "numbsa#" $import = "from numba" $repository = "https://github.com/numba/numba" condition: filesize < 64KB and all of them } - -rule setup_pydevd_cython: override { - meta: - description = "setup_pydevd_cython.py" - setuptools_eval = "low" - - strings: - $example = "python setup_pydevd_cython build_ext --inplace" - $header = "A simpler setup version just to compile the speedup module." - $import = "from setuptools import setup" - $pydevd = "pydevd" - - condition: - filesize < 16KB and all of them -} diff --git a/rules/impact/remote_access/py_setuptools.yara b/rules/impact/remote_access/py_setuptools.yara index 796014f47..61bcbf970 100644 --- a/rules/impact/remote_access/py_setuptools.yara +++ b/rules/impact/remote_access/py_setuptools.yara @@ -39,7 +39,7 @@ rule setuptools_homedir: high { remote_access_pythonSetup and any of them } -rule setuptools_cmd_exec: suspicious { +rule setuptools_cmd_exec: high { meta: description = "Python library installer that executes external commands" @@ -51,6 +51,8 @@ rule setuptools_cmd_exec: suspicious { $not_comment = "Editable install to a prefix should be discoverable." $not_egg_info_requires = "os.path.join(egg_info_dir, 'requires.txt')" $not_requests = "'Documentation': 'https://requests.readthedocs.io'" + $not_sdist_publish = "python setup.py sdist bdist_wheel" + $not_twine_upload = "twine upload dist/*" condition: remote_access_pythonSetup and any of ($f*) and none of ($not*) @@ -70,13 +72,46 @@ rule setuptools_cmd_exec_start: critical { remote_access_pythonSetup and any of ($f*) } -rule setuptools_eval: critical { +rule setuptools_eval: medium { meta: description = "Python library installer that evaluates arbitrary code" strings: - $f_sys_val = /eval\([\"\'\w\ \-\)\/]{0,64}/ fullword - $f_subprocess_val = /exec\([\"\'\/\w\ \-\)]{0,64}/ fullword + $f_eval = /eval\([\"\'\/\w\,\.\ \-\)\(]{1,64}\)/ fullword + + condition: + remote_access_pythonSetup and any of ($f*) +} + +rule setuptools_eval_high: high { + meta: + description = "Python library installer that evaluates arbitrary code" + + strings: + $f_eval = /eval\([\"\'\/\w\,\.\ \-\)\(]{1,64}\)/ fullword + $not_namespaced = /eval\([\w\.\(\)\"\/\']{4,16}, [a-z]{1,6}[,\)]/ + + condition: + remote_access_pythonSetup and any of ($f*) and none of ($not*) +} + +rule setuptools_exec: medium { + meta: + description = "Python library installer that executes arbitrary code" + + strings: + $f_exec = /exec\([\"\'\/\w\,\.\ \-\)\(]{1,64}\)/ fullword + + condition: + remote_access_pythonSetup and any of ($f*) +} + +rule setuptools_exec_high: high { + meta: + description = "Python library installer that evaluates arbitrary code" + + strings: + $f_exec = /exec\([\"\'\/\w\,\.\ \-\)\(]{1,64}\)/ fullword $not_apache = "# Licensed under the Apache License, Version 2.0 (the \"License\")" $not_comment = "Editable install to a prefix should be discoverable." $not_google = /# Copyright [1-2][0-9]{3} Google Inc/ @@ -86,6 +121,7 @@ rule setuptools_eval: critical { $not_pyspark_ioerror = "\"Failed to load PySpark version file for packaging. You must be in Spark's python dir.\"" $not_requests = "'Documentation': 'https://requests.readthedocs.io'" $not_test_egg_class = "class TestEggInfo" + $not_namespaced = /exec\([\w\.\(\)\"\/\']{4,16}, [a-z]{1,6}[,\)]/ condition: remote_access_pythonSetup and any of ($f*) and none of ($not*) diff --git a/tests/python/2021.DiscordSafety/setup.py.simple b/tests/python/2021.DiscordSafety/setup.py.simple index 95d1fc67b..e1889aba2 100644 --- a/tests/python/2021.DiscordSafety/setup.py.simple +++ b/tests/python/2021.DiscordSafety/setup.py.simple @@ -15,6 +15,6 @@ exec/remote_commands/code_eval: critical exfil/stealer/browser: high fs/directory/create: low fs/path/users: medium -impact/remote_access/py_setuptools: critical +impact/remote_access/py_setuptools: high net/url/embedded: medium net/url/request: medium diff --git a/tests/python/2024.Custom.RAT/output.py.simple b/tests/python/2024.Custom.RAT/output.py.simple index 31ee7af65..51e46c620 100644 --- a/tests/python/2024.Custom.RAT/output.py.simple +++ b/tests/python/2024.Custom.RAT/output.py.simple @@ -33,7 +33,7 @@ exec/shell/power: medium exfil/discord: critical exfil/stealer/browser: high exfil/stealer/discord: high -exfil/stealer/keylogger: critical +exfil/stealer/keylogger: high exfil/upload: high fs/directory/create: low fs/directory/list: low diff --git a/tests/python/clean/airflow/botocore_config.py.simple b/tests/python/clean/airflow/botocore_config.py.simple new file mode 100644 index 000000000..c71e9a94f --- /dev/null +++ b/tests/python/clean/airflow/botocore_config.py.simple @@ -0,0 +1,7 @@ +# python/clean/airflow/botocore_config.py: medium +exec/imports/python: low +impact/remote_access/agent: medium +net/http/request: low +net/ip/host_port: medium +net/socket/connect: medium +net/url/embedded: low diff --git a/tests/python/clean/airflow/db.py.simple b/tests/python/clean/airflow/db.py.simple new file mode 100644 index 000000000..78f3cd6e7 --- /dev/null +++ b/tests/python/clean/airflow/db.py.simple @@ -0,0 +1,13 @@ +# python/clean/airflow/db.py: medium +collect/databases/leveldb: medium +collect/databases/mysql: medium +collect/databases/postgresql: medium +collect/databases/sqlite: medium +credential/password: low +credential/ssh: medium +exec/plugin: low +fs/tempdir: low +net/ip/host_port: medium +net/tcp/sftp: medium +net/url/embedded: low +os/fd/multiplex: low diff --git a/tests/python/clean/airflow/kubernetes_engine.py.simple b/tests/python/clean/airflow/kubernetes_engine.py.simple new file mode 100644 index 000000000..07bd2598a --- /dev/null +++ b/tests/python/clean/airflow/kubernetes_engine.py.simple @@ -0,0 +1,6 @@ +# python/clean/airflow/kubernetes_engine.py: medium +anti-static/obfuscation/python: medium +exec/imports/python: low +net/http/auth: low +net/url/embedded: low +net/url/request: medium diff --git a/tests/python/clean/conda-build/_load_setup_py_data.py.simple b/tests/python/clean/conda-build/_load_setup_py_data.py.simple index 8572bf45a..88fdc56a1 100644 --- a/tests/python/clean/conda-build/_load_setup_py_data.py.simple +++ b/tests/python/clean/conda-build/_load_setup_py_data.py.simple @@ -3,7 +3,7 @@ exec/imports/python: low exec/remote_commands/code_eval: medium fs/file/exists: low fs/file/open: low -impact/remote_access/py_setuptools: low +impact/remote_access/py_setuptools: medium net/download: medium net/url/embedded: low os/fd/read: low diff --git a/tests/python/clean/fonttools/psLib.py.simple b/tests/python/clean/fonttools/psLib.py.simple new file mode 100644 index 000000000..136b64901 --- /dev/null +++ b/tests/python/clean/fonttools/psLib.py.simple @@ -0,0 +1,3 @@ +# python/clean/fonttools/psLib.py: low +anti-static/obfuscation/python: low +exec/imports/python: low diff --git a/tests/python/clean/google-auth-library-python/setup.py.simple b/tests/python/clean/google-auth-library-python/setup.py.simple index 09d3a2a14..170dcde4b 100644 --- a/tests/python/clean/google-auth-library-python/setup.py.simple +++ b/tests/python/clean/google-auth-library-python/setup.py.simple @@ -7,5 +7,6 @@ exec/remote_commands/code_eval: medium exec/shell/command: medium fs/file/open: low fs/file/read: low +impact/remote_access/py_setuptools: medium net/url/embedded: low os/fd/read: low diff --git a/tests/python/clean/google-cloud-sdk/requests_setup.py.simple b/tests/python/clean/google-cloud-sdk/requests_setup.py.simple new file mode 100644 index 000000000..ba66aa37b --- /dev/null +++ b/tests/python/clean/google-cloud-sdk/requests_setup.py.simple @@ -0,0 +1,12 @@ +# python/clean/google-cloud-sdk/requests_setup.py: medium +exec/imports/python: low +exec/program: medium +exec/remote_commands/code_eval: medium +exec/shell/command: medium +fs/file/open: low +fs/path/usr_bin: low +impact/remote_access/py_setuptools: medium +net/url/embedded: low +net/url/parse: low +os/fd/read: low +process/multi: medium diff --git a/tests/python/clean/idna/setup.py.simple b/tests/python/clean/idna/setup.py.simple index 3a36d1ecf..008331d6e 100644 --- a/tests/python/clean/idna/setup.py.simple +++ b/tests/python/clean/idna/setup.py.simple @@ -2,5 +2,6 @@ exec/imports/python: low exec/remote_commands/code_eval: medium fs/file/open: low +impact/remote_access/py_setuptools: medium net/url/embedded: low os/fd/read: low diff --git a/tests/python/clean/matplotlib/_backend_tk.py.simple b/tests/python/clean/matplotlib/_backend_tk.py.simple new file mode 100644 index 000000000..c2b033ac2 --- /dev/null +++ b/tests/python/clean/matplotlib/_backend_tk.py.simple @@ -0,0 +1,6 @@ +# python/clean/matplotlib/_backend_tk.py: medium +anti-static/obfuscation/bitwise: low +c2/tool_transfer/os: medium +discover/system/platform: medium +exec/imports/python: low +net/url/embedded: low diff --git a/tests/python/clean/matplotlib/backend_bases.py.simple b/tests/python/clean/matplotlib/backend_bases.py.simple new file mode 100644 index 000000000..14e0fc17c --- /dev/null +++ b/tests/python/clean/matplotlib/backend_bases.py.simple @@ -0,0 +1,7 @@ +# python/clean/matplotlib/backend_bases.py: medium +c2/tool_transfer/os: low +discover/system/platform: medium +net/socket/connect: medium +net/socket/listen: medium +net/socket/pair: medium +net/url/embedded: low diff --git a/tests/python/clean/matplotlib/backend_qt.py.simple b/tests/python/clean/matplotlib/backend_qt.py.simple new file mode 100644 index 000000000..2fe4bb7b5 --- /dev/null +++ b/tests/python/clean/matplotlib/backend_qt.py.simple @@ -0,0 +1,9 @@ +# python/clean/matplotlib/backend_qt.py: medium +c2/tool_transfer/os: medium +discover/system/platform: medium +exec/imports/python: low +exec/remote_commands/code_eval: medium +net/socket/pair: medium +net/socket/receive: low +net/url/embedded: low +os/time/clock_sleep: medium diff --git a/tests/python/clean/matplotlib/backend_wx.py.simple b/tests/python/clean/matplotlib/backend_wx.py.simple new file mode 100644 index 000000000..045e6a281 --- /dev/null +++ b/tests/python/clean/matplotlib/backend_wx.py.simple @@ -0,0 +1,5 @@ +# python/clean/matplotlib/backend_wx.py: medium +c2/tool_transfer/os: medium +discover/system/platform: medium +exec/imports/python: low +net/url/embedded: low diff --git a/tests/python/clean/mitmproxy/raw_display.py.simple b/tests/python/clean/mitmproxy/raw_display.py.simple new file mode 100644 index 000000000..f8393396d --- /dev/null +++ b/tests/python/clean/mitmproxy/raw_display.py.simple @@ -0,0 +1,16 @@ +# python/clean/mitmproxy/raw_display.py: medium +c2/tool_transfer/os: low +exec/imports/python: low +exec/program: medium +exec/program/background: low +exec/shell/TERM: low +fs/file/write: low +fs/path/usr_bin: low +net/socket/connect: medium +net/socket/pair: medium +net/socket/receive: low +net/socket/send: low +net/url/embedded: low +os/fd/write: low +persist/daemon: medium +process/multithreaded: medium diff --git a/tests/python/clean/ml_sdk/setup.py.simple b/tests/python/clean/ml_sdk/setup.py.simple index 57f63afdb..f3cad667f 100644 --- a/tests/python/clean/ml_sdk/setup.py.simple +++ b/tests/python/clean/ml_sdk/setup.py.simple @@ -2,5 +2,6 @@ exec/imports/python: low exec/remote_commands/code_eval: medium fs/file/open: low +impact/remote_access/py_setuptools: medium net/url/embedded: low os/fd/read: low diff --git a/tests/python/clean/numba/support.py.simple b/tests/python/clean/numba/support.py.simple index 9f28257b0..585043a3a 100644 --- a/tests/python/clean/numba/support.py.simple +++ b/tests/python/clean/numba/support.py.simple @@ -6,7 +6,6 @@ discover/system/platform: medium exec/imports/python: low exec/program: medium exec/remote_commands/code_eval: medium -false-positives/setuptools: low fs/directory/create: low fs/directory/list: low fs/file/open: low diff --git a/tests/python/clean/pydevd/setup_pydevd_cython.py.simple b/tests/python/clean/pydevd/setup_pydevd_cython.py.simple index 543cd076e..7534b61bc 100644 --- a/tests/python/clean/pydevd/setup_pydevd_cython.py.simple +++ b/tests/python/clean/pydevd/setup_pydevd_cython.py.simple @@ -9,6 +9,6 @@ fs/file/open: low fs/file/read: low fs/file/write: low fs/tempdir/TEMP: low -impact/remote_access/py_setuptools: low +impact/remote_access/py_setuptools: medium os/fd/read: low os/fd/write: low diff --git a/tests/python/clean/requests/setup.py.simple b/tests/python/clean/requests/setup.py.simple index 108a1ec34..4d48c3d10 100644 --- a/tests/python/clean/requests/setup.py.simple +++ b/tests/python/clean/requests/setup.py.simple @@ -6,6 +6,7 @@ exec/remote_commands/code_eval: medium exec/shell/command: medium fs/file/open: low fs/path/usr_bin: low +impact/remote_access/py_setuptools: medium net/ip/parse: medium net/url/embedded: low net/url/parse: low diff --git a/tests/python/clean/setuptools/test_pyprojecttoml.py.simple b/tests/python/clean/setuptools/test_pyprojecttoml.py.simple index 102f278c2..3f4fc9686 100644 --- a/tests/python/clean/setuptools/test_pyprojecttoml.py.simple +++ b/tests/python/clean/setuptools/test_pyprojecttoml.py.simple @@ -4,6 +4,5 @@ discover/system/platform: medium exec/imports/python: low exec/shell/command: medium fs/file/open: low -impact/remote_access/py_setuptools: low net/url/embedded: low os/fd/write: low