Switch from pickle->json for config encoding

`pickle.loads` is insecure (and unnecessary in this context). The
simpler data types of the config model can easily be serialized to json
instead.
This commit is contained in:
Ben Busby 2024-10-31 16:41:46 -06:00
parent 466cb63d0e
commit 223f00c3c0
2 changed files with 28 additions and 40 deletions

View File

@ -3,13 +3,12 @@ from typing import Optional
from app.utils.misc import read_config_bool from app.utils.misc import read_config_bool
from flask import current_app from flask import current_app
import os import os
import re
from base64 import urlsafe_b64encode, urlsafe_b64decode from base64 import urlsafe_b64encode, urlsafe_b64decode
import pickle
from cryptography.fernet import Fernet from cryptography.fernet import Fernet
import hashlib import hashlib
import brotli import brotli
import logging import logging
import json
import cssutils import cssutils
from cssutils.css.cssstylesheet import CSSStyleSheet from cssutils.css.cssstylesheet import CSSStyleSheet
@ -62,7 +61,7 @@ class Config:
self.anon_view = read_config_bool('WHOOGLE_CONFIG_ANON_VIEW') self.anon_view = read_config_bool('WHOOGLE_CONFIG_ANON_VIEW')
self.preferences_encrypted = read_config_bool('WHOOGLE_CONFIG_PREFERENCES_ENCRYPTED') self.preferences_encrypted = read_config_bool('WHOOGLE_CONFIG_PREFERENCES_ENCRYPTED')
self.preferences_key = os.getenv('WHOOGLE_CONFIG_PREFERENCES_KEY', '') self.preferences_key = os.getenv('WHOOGLE_CONFIG_PREFERENCES_KEY', '')
self.accept_language = False self.accept_language = False
self.safe_keys = [ self.safe_keys = [
@ -144,7 +143,7 @@ class Config:
# if encryption key is not set will uncheck preferences encryption # if encryption key is not set will uncheck preferences encryption
if self.preferences_encrypted: if self.preferences_encrypted:
self.preferences_encrypted = bool(self.preferences_key) self.preferences_encrypted = bool(self.preferences_key)
# add a tag for visibility if preferences token startswith 'e' it means # add a tag for visibility if preferences token startswith 'e' it means
# the token is encrypted, 'u' means the token is unencrypted and can be # the token is encrypted, 'u' means the token is unencrypted and can be
# used by other whoogle instances # used by other whoogle instances
@ -237,35 +236,32 @@ class Config:
return key return key
def _encode_preferences(self) -> str: def _encode_preferences(self) -> str:
encoded_preferences = brotli.compress(pickle.dumps(self.get_attrs())) preferences_json = json.dumps(self.get_attrs()).encode()
if self.preferences_encrypted: compressed_preferences = brotli.compress(preferences_json)
if self.preferences_key != '':
key = self._get_fernet_key(self.preferences_key)
encoded_preferences = Fernet(key).encrypt(encoded_preferences)
encoded_preferences = brotli.compress(encoded_preferences)
return urlsafe_b64encode(encoded_preferences).decode() if self.preferences_encrypted and self.preferences_key:
key = self._get_fernet_key(self.preferences_key)
encrypted_preferences = Fernet(key).encrypt(compressed_preferences)
compressed_preferences = brotli.compress(encrypted_preferences)
return urlsafe_b64encode(compressed_preferences).decode()
def _decode_preferences(self, preferences: str) -> dict: def _decode_preferences(self, preferences: str) -> dict:
mode = preferences[0] mode = preferences[0]
preferences = preferences[1:] preferences = preferences[1:]
if mode == 'e': # preferences are encrypted
try: try:
decoded_data = brotli.decompress(urlsafe_b64decode(preferences.encode() + b'=='))
if mode == 'e' and self.preferences_key:
# preferences are encrypted
key = self._get_fernet_key(self.preferences_key) key = self._get_fernet_key(self.preferences_key)
decrypted_data = Fernet(key).decrypt(decoded_data)
decoded_data = brotli.decompress(decrypted_data)
config = Fernet(key).decrypt( config = json.loads(decoded_data)
brotli.decompress(urlsafe_b64decode( except Exception:
preferences.encode() + b'=='))
)
config = pickle.loads(brotli.decompress(config))
except Exception:
config = {}
elif mode == 'u': # preferences are not encrypted
config = pickle.loads(
brotli.decompress(urlsafe_b64decode(
preferences.encode() + b'=='))
)
else: # preferences are incorrectly formatted
config = {} config = {}
return config return config

View File

@ -4,19 +4,11 @@ from app import app
from app.models.endpoint import Endpoint from app.models.endpoint import Endpoint
from app.utils.session import generate_key, valid_user_session from app.utils.session import generate_key, valid_user_session
JAPAN_PREFS = 'uG7IBICwK7FgMJNpUawp2tKDb1Omuv_euy-cJHVZ' \
JAPAN_PREFS = 'uG-gGIJwHdqxl6DrS3mnu_511HlQcRpxYlG03Xs-' \ + 'BSydthgwxRFIHxiVA8qUGavKaDXyiM5uNuPIjKbEAW-zB_vzNXWVaafFhW7k2' \
+ '_znXNiJWI9nLOkRLkiiFwIpeUYMTGfUF5-t9fP5DGmzDLEt04DCx703j3nPf' \ + 'fO2_mS5e5eK41XXWwiViTz2VVmGWje0UgQwwVPe1A7aH0s10FgARsd2xl5nlg' \
+ '29v_RWkU7gXw_44m2oAFIaKGmYlu4Z0bKyu9k5WXfL9Dy6YKKnpcR5CiaFsG' \ + 'RLHT2krPUw-iLQ5uHZSnYXFuF4caYemWcj4vqB2ocHkt-aqn04jgnnlWWME_K' \
+ 'rccNRkAPYm-eYGAFUV8M59f8StsGd_M-gHKGS9fLok7EhwBWjHxBJ2Kv8hsT' \ + '9ySWdWmPyS66HtLt1tCwc_-xGZklvbHw=='
+ '87zftP2gMJOevTdNnezw2Y5WOx-ZotgeheCW1BYCFcRqatlov21PHp22NGVG' \
+ '8ZuBNAFW0bE99WSdyT7dUIvzeWCLJpbdSsq-3FUUZkxbRdFYlGd8vY1UgVAp' \
+ 'OSie2uAmpgLFXygO-VfNBBZ68Q7gAap2QtzHCiKD5cFYwH3LPgVJ-DoZvJ6k' \
+ 'alt34TaYiJphgiqFKV4SCeVmLWTkr0SF3xakSR78yYJU_d41D2ng-TojA9XZ' \
+ 'uR2ZqjSvPKOWvjimu89YhFOgJxG1Po8Henj5h9OL9VXXvdvlJwBSAKw1E3FV' \
+ '7UHWiglMxPblfxqou1cYckMYkFeIMCD2SBtju68mBiQh2k328XRPTsQ_ocby' \
+ 'cgVKnleGperqbD6crRk3Z9xE5sVCjujn9JNVI-7mqOITMZ0kntq9uJ3R5n25' \
+ 'Vec0TJ0P19nEtvjY0nJIrIjtnBg=='
def test_generate_user_keys(): def test_generate_user_keys():