Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: fix sqlitedict CVE #2621

Merged
merged 10 commits into from
Nov 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
4 changes: 2 additions & 2 deletions charts/splunk-connect-for-syslog/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ apiVersion: v2
name: splunk-connect-for-syslog
description: Deploy Splunk Connect for Syslog
type: application
version: 3.31.0
appVersion: "3.31.0"
version: 3.32.0
appVersion: "3.32.0"
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
2 changes: 1 addition & 1 deletion package/etc/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.31.0
3.32.0
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)
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "splunk-connect-for-syslog"
version = "3.31.0"
version = "3.32.0"
description = ""
authors = ["rjha-splunk <rjha@splunk.com>"]
license = "Apache-2.0"
Expand Down
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()
Loading