Skip to content

Commit 4b198d4

Browse files
committed
fix: fix sqlitedict CVE (#2621)
1 parent d56e7f9 commit 4b198d4

File tree

9 files changed

+102
-14
lines changed

9 files changed

+102
-14
lines changed

.github/workflows/ci-lite.yaml

+9-1
Original file line numberDiff line numberDiff line change
@@ -161,14 +161,22 @@ jobs:
161161
- meta
162162
- build_action
163163
steps:
164+
# To use .trivyignore file, you must check out the repository
165+
- name: Checkout
166+
uses: actions/checkout@v4
167+
with:
168+
submodules: false
169+
persist-credentials: false
164170
- name: Run docker vulnerability scanner
165171
uses: aquasecurity/trivy-action@master
166172
with:
167173
image-ref: ${{ needs.meta.outputs.container_base }}
168174
format: 'table'
169175
exit-code: '1'
170176
severity: 'CRITICAL,HIGH,MEDIUM,LOW'
171-
177+
trivyignores: '.trivyignore'
178+
scanners: "vuln"
179+
172180
test-container:
173181
runs-on: ubuntu-latest
174182
needs:

.github/workflows/ci-main.yaml

+9-1
Original file line numberDiff line numberDiff line change
@@ -161,14 +161,22 @@ jobs:
161161
- meta
162162
- build_action
163163
steps:
164+
# To use .trivyignore file, you must check out the repository
165+
- name: Checkout
166+
uses: actions/checkout@v4
167+
with:
168+
submodules: false
169+
persist-credentials: false
164170
- name: Run docker vulnerability scanner
165171
uses: aquasecurity/trivy-action@master
166172
with:
167173
image-ref: ${{ needs.meta.outputs.container_base }}
168174
format: 'table'
169175
exit-code: '1'
170176
severity: 'CRITICAL,HIGH,MEDIUM,LOW'
171-
177+
trivyignores: '.trivyignore'
178+
scanners: "vuln"
179+
172180
test-container:
173181
runs-on: ubuntu-latest
174182
needs:

.trivyignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# This has been safeguarded directly in the code
2+
CVE-2024-35515

package/Dockerfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ RUN apk add -U --upgrade --no-cache \
2828
less \
2929
net-tools \
3030
netcat-openbsd \
31-
openssl \
31+
"openssl>=3.3.2-r1" \
3232
procps \
3333
py3-pip \
3434
python3 \

package/Dockerfile.lite

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ RUN apk add -U --upgrade --no-cache \
2828
less \
2929
net-tools \
3030
netcat-openbsd \
31-
openssl \
31+
"openssl>=3.3.2-r1" \
3232
procps \
3333
py3-pip \
3434
python3 \

package/etc/pylib/parser_source_cache.py

+9-5
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import traceback
33
import socket
44
import struct
5-
from sqlitedict import SqliteDict
65

76
import time
87

@@ -17,7 +16,6 @@ class LogParser:
1716
class LogDestination:
1817
pass
1918

20-
2119
def ip2int(addr):
2220
ip4_to_int = lambda addr: struct.unpack("!I", socket.inet_aton(addr))[0]
2321

@@ -53,8 +51,10 @@ def int_to_ip6(num):
5351

5452
class psc_parse(LogParser):
5553
def init(self, options):
54+
from sqlite_utils import RestrictedSqliteDict
55+
5656
self.logger = syslogng.Logger()
57-
self.db = SqliteDict(f"{hostdict}.sqlite")
57+
self.db = RestrictedSqliteDict(f"{hostdict}.sqlite")
5858
return True
5959

6060
def deinit(self):
@@ -80,9 +80,11 @@ def parse(self, log_message):
8080

8181
class psc_dest(LogDestination):
8282
def init(self, options):
83+
from sqlite_utils import RestrictedSqliteDict
84+
8385
self.logger = syslogng.Logger()
8486
try:
85-
self.db = SqliteDict(f"{hostdict}.sqlite", autocommit=True)
87+
self.db = RestrictedSqliteDict(f"{hostdict}.sqlite", autocommit=True)
8688
except Exception:
8789
exc_type, exc_value, exc_traceback = sys.exc_info()
8890
lines = traceback.format_exception(exc_type, exc_value, exc_traceback)
@@ -123,7 +125,9 @@ def flush(self):
123125

124126

125127
if __name__ == "__main__":
126-
db = SqliteDict(f"{hostdict}.sqlite", autocommit=True)
128+
from sqlite_utils import RestrictedSqliteDict
129+
130+
db = RestrictedSqliteDict(f"{hostdict}.sqlite", autocommit=True)
127131
db[0] = "seed"
128132
db.commit()
129133
db.close()

package/etc/pylib/parser_vps_cache.py

+6-4
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import traceback
33
import socket
44
import struct
5-
from sqlitedict import SqliteDict
65

76
import time
87

@@ -17,14 +16,15 @@ class LogParser:
1716
class LogDestination:
1817
pass
1918

20-
2119
hostdict = str("/var/lib/syslog-ng/vps")
2220

2321

2422
class vpsc_parse(LogParser):
2523
def init(self, options):
24+
from sqlite_utils import RestrictedSqliteDict
25+
2626
self.logger = syslogng.Logger()
27-
self.db = SqliteDict(f"{hostdict}.sqlite")
27+
self.db = RestrictedSqliteDict(f"{hostdict}.sqlite")
2828
return True
2929

3030
def deinit(self):
@@ -50,9 +50,11 @@ def parse(self, log_message):
5050

5151
class vpsc_dest(LogDestination):
5252
def init(self, options):
53+
from sqlite_utils import RestrictedSqliteDict
54+
5355
self.logger = syslogng.Logger()
5456
try:
55-
self.db = SqliteDict(f"{hostdict}.sqlite", autocommit=True)
57+
self.db = RestrictedSqliteDict(f"{hostdict}.sqlite", autocommit=True)
5658
except Exception:
5759
exc_type, exc_value, exc_traceback = sys.exc_info()
5860
lines = traceback.format_exception(exc_type, exc_value, exc_traceback)

package/etc/pylib/sqlite_utils.py

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import io
2+
import pickle
3+
from base64 import b64decode
4+
from sqlitedict import SqliteDict
5+
6+
7+
class RestrictedUnpickler(pickle.Unpickler):
8+
def find_class(self, module, name):
9+
"""Override pickle.Unpickler.find_class() to prevent deserialization of class instances."""
10+
raise pickle.UnpicklingError("Class deserialization is disabled")
11+
12+
13+
def restricted_loads(s):
14+
"""Helper function analogous to pickle.loads()."""
15+
return RestrictedUnpickler(io.BytesIO(s)).load()
16+
17+
def restricted_decode(obj):
18+
"""Overwrite sqlitedict.decode() to prevent code injection."""
19+
return restricted_loads(bytes(obj))
20+
21+
def restricted_decode_key(key):
22+
"""Overwrite sqlitedict.decode_key() to prevent code injection."""
23+
return restricted_loads(b64decode(key.encode("ascii")))
24+
25+
26+
class RestrictedSqliteDict(SqliteDict):
27+
def __init__(self, *args, **kwargs):
28+
super(RestrictedSqliteDict, self).__init__(*args, decode=restricted_decode, decode_key=restricted_decode_key, **kwargs)

tests/test_name_cache.py

+37-1
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55
# https://opensource.org/licenses/BSD-2-Clause
66

77
import datetime
8+
import pickle
89
import random
910
import re
11+
import tempfile
1012
import time
1113

1214
from jinja2 import Environment
@@ -16,6 +18,7 @@
1618
from .sendmessage import sendsingle
1719
from .splunkutils import splunk_single
1820
from package.etc.pylib.parser_source_cache import ip2int, int2ip
21+
from package.etc.pylib.sqlite_utils import RestrictedSqliteDict
1922

2023
env = Environment()
2124

@@ -73,4 +76,37 @@ def test_ipv4_utils():
7376
@pytest.mark.name_cache
7477
def test_ipv6_utils():
7578
ip = generate_random_ipv6()
76-
assert ip == int2ip(ip2int(ip))
79+
assert ip == int2ip(ip2int(ip))
80+
81+
@pytest.mark.name_cache
82+
def test_RestrictedSqliteDict_stores_and_retrieves_string():
83+
with tempfile.NamedTemporaryFile(delete=True) as temp_db_file:
84+
cache = RestrictedSqliteDict(f"{temp_db_file.name}.db")
85+
cache["key"] = "value"
86+
cache.commit()
87+
cache.close()
88+
89+
cache = RestrictedSqliteDict(f"{temp_db_file.name}.db")
90+
assert cache["key"] == "value"
91+
cache.close()
92+
93+
@pytest.mark.name_cache
94+
def test_RestrictedSqliteDict_prevents_code_injection():
95+
class InjectionTestClass:
96+
def __reduce__(self):
97+
import os
98+
return os.system, ('touch pwned.txt',)
99+
100+
with tempfile.NamedTemporaryFile(delete=True) as temp_db_file:
101+
# Initialize the RestrictedSqliteDict and insert an 'injected' object
102+
cache = RestrictedSqliteDict(f"{temp_db_file.name}.db")
103+
cache["key"] = InjectionTestClass()
104+
cache.commit()
105+
cache.close()
106+
107+
# Re-open cache and attempt to deserialize 'injected' object
108+
# Expecting UnpicklingError due to RestrictedSqliteDict restrictions
109+
cache = RestrictedSqliteDict(f"{temp_db_file.name}.db")
110+
with pytest.raises(pickle.UnpicklingError):
111+
_ = cache["key"]
112+
cache.close()

0 commit comments

Comments
 (0)