diff --git a/.github/workflows/ci-lite.yaml b/.github/workflows/ci-lite.yaml index 5ebcf2532f..df174ada6a 100644 --- a/.github/workflows/ci-lite.yaml +++ b/.github/workflows/ci-lite.yaml @@ -161,6 +161,12 @@ jobs: - meta - build_action steps: + # To use .trivyignore file, you must check out the repository + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: false + persist-credentials: false - name: Run docker vulnerability scanner uses: aquasecurity/trivy-action@master with: @@ -168,7 +174,9 @@ jobs: format: 'table' exit-code: '1' severity: 'CRITICAL,HIGH,MEDIUM,LOW' - + trivyignores: '.trivyignore' + scanners: "vuln" + test-container: runs-on: ubuntu-latest needs: diff --git a/.github/workflows/ci-main.yaml b/.github/workflows/ci-main.yaml index 0a44116cd7..d56ab76959 100644 --- a/.github/workflows/ci-main.yaml +++ b/.github/workflows/ci-main.yaml @@ -161,6 +161,12 @@ jobs: - meta - build_action steps: + # To use .trivyignore file, you must check out the repository + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: false + persist-credentials: false - name: Run docker vulnerability scanner uses: aquasecurity/trivy-action@master with: @@ -168,7 +174,9 @@ jobs: format: 'table' exit-code: '1' severity: 'CRITICAL,HIGH,MEDIUM,LOW' - + trivyignores: '.trivyignore' + scanners: "vuln" + test-container: runs-on: ubuntu-latest needs: diff --git a/.trivyignore b/.trivyignore new file mode 100644 index 0000000000..0a7be8250b --- /dev/null +++ b/.trivyignore @@ -0,0 +1,2 @@ +# This has been safeguarded directly in the code +CVE-2024-35515 \ No newline at end of file diff --git a/package/Dockerfile b/package/Dockerfile index c91e3bc53a..b730d652d6 100644 --- a/package/Dockerfile +++ b/package/Dockerfile @@ -28,7 +28,7 @@ RUN apk add -U --upgrade --no-cache \ less \ net-tools \ netcat-openbsd \ - openssl \ + "openssl>=3.3.2-r1" \ procps \ py3-pip \ python3 \ diff --git a/package/Dockerfile.lite b/package/Dockerfile.lite index 24f30686d2..e54ceee047 100644 --- a/package/Dockerfile.lite +++ b/package/Dockerfile.lite @@ -28,7 +28,7 @@ RUN apk add -U --upgrade --no-cache \ less \ net-tools \ netcat-openbsd \ - openssl \ + "openssl>=3.3.2-r1" \ procps \ py3-pip \ python3 \ diff --git a/package/etc/pylib/parser_source_cache.py b/package/etc/pylib/parser_source_cache.py index dc573a3449..1ffa3c114e 100644 --- a/package/etc/pylib/parser_source_cache.py +++ b/package/etc/pylib/parser_source_cache.py @@ -2,7 +2,6 @@ import traceback import socket import struct -from sqlitedict import SqliteDict import time @@ -17,7 +16,6 @@ class LogParser: class LogDestination: pass - def ip2int(addr): ip4_to_int = lambda addr: struct.unpack("!I", socket.inet_aton(addr))[0] @@ -53,8 +51,10 @@ def int_to_ip6(num): class psc_parse(LogParser): def init(self, options): + from sqlite_utils import RestrictedSqliteDict + self.logger = syslogng.Logger() - self.db = SqliteDict(f"{hostdict}.sqlite") + self.db = RestrictedSqliteDict(f"{hostdict}.sqlite") return True def deinit(self): @@ -80,9 +80,11 @@ def parse(self, log_message): class psc_dest(LogDestination): def init(self, options): + from sqlite_utils import RestrictedSqliteDict + self.logger = syslogng.Logger() try: - self.db = SqliteDict(f"{hostdict}.sqlite", autocommit=True) + self.db = RestrictedSqliteDict(f"{hostdict}.sqlite", autocommit=True) except Exception: exc_type, exc_value, exc_traceback = sys.exc_info() lines = traceback.format_exception(exc_type, exc_value, exc_traceback) @@ -123,7 +125,9 @@ def flush(self): if __name__ == "__main__": - db = SqliteDict(f"{hostdict}.sqlite", autocommit=True) + from sqlite_utils import RestrictedSqliteDict + + db = RestrictedSqliteDict(f"{hostdict}.sqlite", autocommit=True) db[0] = "seed" db.commit() db.close() \ No newline at end of file diff --git a/package/etc/pylib/parser_vps_cache.py b/package/etc/pylib/parser_vps_cache.py index a95162862b..61575c1f8c 100644 --- a/package/etc/pylib/parser_vps_cache.py +++ b/package/etc/pylib/parser_vps_cache.py @@ -2,7 +2,6 @@ import traceback import socket import struct -from sqlitedict import SqliteDict import time @@ -17,14 +16,15 @@ class LogParser: class LogDestination: pass - hostdict = str("/var/lib/syslog-ng/vps") class vpsc_parse(LogParser): def init(self, options): + from sqlite_utils import RestrictedSqliteDict + self.logger = syslogng.Logger() - self.db = SqliteDict(f"{hostdict}.sqlite") + self.db = RestrictedSqliteDict(f"{hostdict}.sqlite") return True def deinit(self): @@ -50,9 +50,11 @@ def parse(self, log_message): class vpsc_dest(LogDestination): def init(self, options): + from sqlite_utils import RestrictedSqliteDict + self.logger = syslogng.Logger() try: - self.db = SqliteDict(f"{hostdict}.sqlite", autocommit=True) + self.db = RestrictedSqliteDict(f"{hostdict}.sqlite", autocommit=True) except Exception: exc_type, exc_value, exc_traceback = sys.exc_info() lines = traceback.format_exception(exc_type, exc_value, exc_traceback) diff --git a/package/etc/pylib/sqlite_utils.py b/package/etc/pylib/sqlite_utils.py new file mode 100644 index 0000000000..fa16ace292 --- /dev/null +++ b/package/etc/pylib/sqlite_utils.py @@ -0,0 +1,28 @@ +import io +import pickle +from base64 import b64decode +from sqlitedict import SqliteDict + + +class RestrictedUnpickler(pickle.Unpickler): + def find_class(self, module, name): + """Override pickle.Unpickler.find_class() to prevent deserialization of class instances.""" + raise pickle.UnpicklingError("Class deserialization is disabled") + + +def restricted_loads(s): + """Helper function analogous to pickle.loads().""" + return RestrictedUnpickler(io.BytesIO(s)).load() + +def restricted_decode(obj): + """Overwrite sqlitedict.decode() to prevent code injection.""" + return restricted_loads(bytes(obj)) + +def restricted_decode_key(key): + """Overwrite sqlitedict.decode_key() to prevent code injection.""" + return restricted_loads(b64decode(key.encode("ascii"))) + + +class RestrictedSqliteDict(SqliteDict): + def __init__(self, *args, **kwargs): + super(RestrictedSqliteDict, self).__init__(*args, decode=restricted_decode, decode_key=restricted_decode_key, **kwargs) \ No newline at end of file diff --git a/tests/test_name_cache.py b/tests/test_name_cache.py index ce8a56f502..21ac8ad2c5 100644 --- a/tests/test_name_cache.py +++ b/tests/test_name_cache.py @@ -5,8 +5,10 @@ # https://opensource.org/licenses/BSD-2-Clause import datetime +import pickle import random import re +import tempfile import time from jinja2 import Environment @@ -16,6 +18,7 @@ from .sendmessage import sendsingle from .splunkutils import splunk_single from package.etc.pylib.parser_source_cache import ip2int, int2ip +from package.etc.pylib.sqlite_utils import RestrictedSqliteDict env = Environment() @@ -73,4 +76,37 @@ def test_ipv4_utils(): @pytest.mark.name_cache def test_ipv6_utils(): ip = generate_random_ipv6() - assert ip == int2ip(ip2int(ip)) \ No newline at end of file + assert ip == int2ip(ip2int(ip)) + +@pytest.mark.name_cache +def test_RestrictedSqliteDict_stores_and_retrieves_string(): + with tempfile.NamedTemporaryFile(delete=True) as temp_db_file: + cache = RestrictedSqliteDict(f"{temp_db_file.name}.db") + cache["key"] = "value" + cache.commit() + cache.close() + + cache = RestrictedSqliteDict(f"{temp_db_file.name}.db") + assert cache["key"] == "value" + cache.close() + +@pytest.mark.name_cache +def test_RestrictedSqliteDict_prevents_code_injection(): + class InjectionTestClass: + def __reduce__(self): + import os + return os.system, ('touch pwned.txt',) + + with tempfile.NamedTemporaryFile(delete=True) as temp_db_file: + # Initialize the RestrictedSqliteDict and insert an 'injected' object + cache = RestrictedSqliteDict(f"{temp_db_file.name}.db") + cache["key"] = InjectionTestClass() + cache.commit() + cache.close() + + # Re-open cache and attempt to deserialize 'injected' object + # Expecting UnpicklingError due to RestrictedSqliteDict restrictions + cache = RestrictedSqliteDict(f"{temp_db_file.name}.db") + with pytest.raises(pickle.UnpicklingError): + _ = cache["key"] + cache.close() \ No newline at end of file