Skip to content

Commit

Permalink
fix: fix sqlitedict CVE (#2621)
Browse files Browse the repository at this point in the history
  • Loading branch information
mstopa-splunk authored Nov 14, 2024
1 parent dbf2e8c commit 60ee9cc
Show file tree
Hide file tree
Showing 9 changed files with 102 additions and 14 deletions.
10 changes: 9 additions & 1 deletion .github/workflows/ci-lite.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -161,14 +161,22 @@ 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:
image-ref: ${{ needs.meta.outputs.container_base }}
format: 'table'
exit-code: '1'
severity: 'CRITICAL,HIGH,MEDIUM,LOW'

trivyignores: '.trivyignore'
scanners: "vuln"

test-container:
runs-on: ubuntu-latest
needs:
Expand Down
10 changes: 9 additions & 1 deletion .github/workflows/ci-main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -161,14 +161,22 @@ 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:
image-ref: ${{ needs.meta.outputs.container_base }}
format: 'table'
exit-code: '1'
severity: 'CRITICAL,HIGH,MEDIUM,LOW'

trivyignores: '.trivyignore'
scanners: "vuln"

test-container:
runs-on: ubuntu-latest
needs:
Expand Down
2 changes: 2 additions & 0 deletions .trivyignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# This has been safeguarded directly in the code
CVE-2024-35515
2 changes: 1 addition & 1 deletion package/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down
2 changes: 1 addition & 1 deletion package/Dockerfile.lite
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down
14 changes: 9 additions & 5 deletions package/etc/pylib/parser_source_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import traceback
import socket
import struct
from sqlitedict import SqliteDict

import time

Expand All @@ -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]

Expand Down Expand Up @@ -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):
Expand All @@ -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)
Expand Down Expand Up @@ -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()
10 changes: 6 additions & 4 deletions package/etc/pylib/parser_vps_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import traceback
import socket
import struct
from sqlitedict import SqliteDict

import time

Expand All @@ -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):
Expand All @@ -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)
Expand Down
28 changes: 28 additions & 0 deletions package/etc/pylib/sqlite_utils.py
Original file line number Diff line number Diff line change
@@ -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)
38 changes: 37 additions & 1 deletion tests/test_name_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()

Expand Down Expand Up @@ -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))
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()

0 comments on commit 60ee9cc

Please sign in to comment.