Skip to content

Commit

Permalink
Fix potential leak of sensitive data via HTTPServer logging #1016
Browse files Browse the repository at this point in the history
  • Loading branch information
MoojMidge committed Dec 7, 2024
1 parent f74cf3d commit f5578a7
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 26 deletions.
83 changes: 58 additions & 25 deletions resources/lib/youtube_plugin/kodion/network/http_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
BaseHTTPRequestHandler,
TCPServer,
parse_qsl,
urlencode,
urlsplit,
urlunsplit,
xbmc,
Expand Down Expand Up @@ -92,25 +93,58 @@ def connection_allowed(self, method):
else:
in_local_range = 'Undetermined'

if self.path != PATHS.PING:
path_parts = urlsplit(self.path)
if path_parts.query:
params = dict(parse_qsl(path_parts.query))
log_params = params.copy()
for param, value in params.items():
if param in {'key', 'api_key', 'api_secret', 'client_secret'}:
log_params[param] = '...'.join((value[:3], value[-3:]))
elif param in {'api_id', 'client_id'}:
log_params[param] = '...'.join((value[:3], value[-5:]))
elif param in {'access_token', 'refresh_token', 'token'}:
log_params[param] = '<redacted>'
elif param == 'url':
log_params[param] = redact_ip(value)
elif param == 'location':
log_params[param] = '|xx.xxxx,xx.xxxx|'
log_path = urlunsplit((
'', '', path_parts.path, urlencode(log_params), '',
))
else:
params = log_params = None
log_path = path_parts.path
path = {
'full': self.path,
'path': path_parts.path,
'query': path_parts.query,
'params': params,
'log_params': log_params,
'log_path': log_path,
}

if not path['path'].startswith(PATHS.PING):
msg = ('HTTPServer - {method}'
'\n\tPath: |{path}|'
'\n\tParams: |{params}|'
'\n\tAddress: |{client_ip}|'
'\n\tWhitelisted: {is_whitelisted}'
'\n\tLocal range: {in_local_range}'
'\n\tStatus: {status}'
.format(method=method,
path=redact_ip(self.path),
path=path['path'],
params=path['log_params'],
client_ip=client_ip,
is_whitelisted=is_whitelisted,
in_local_range=in_local_range,
status='Allowed' if conn_allowed else 'Blocked'))
self._context.log_debug(msg)
return conn_allowed
return conn_allowed, path

# noinspection PyPep8Naming
def do_GET(self):
if not self.connection_allowed('GET'):
allowed, path = self.connection_allowed('GET')
if not allowed:
self.send_error(403)
return

Expand All @@ -120,20 +154,17 @@ def do_GET(self):
settings = context.get_settings()
api_config_enabled = settings.api_config_page()

# Strip trailing slash if present
stripped_path = self.path.rstrip('/')

if stripped_path == PATHS.IP:
if path['path'] == PATHS.IP:
client_json = json.dumps({'ip': self.client_address[0]})
self.send_response(200)
self.send_header('Content-Type', 'application/json; charset=utf-8')
self.send_header('Content-Length', str(len(client_json)))
self.end_headers()
self.wfile.write(client_json.encode('utf-8'))

elif stripped_path.startswith(PATHS.MPD):
elif path['path'].startswith(PATHS.MPD):
try:
file = dict(parse_qsl(urlsplit(self.path).query)).get('file')
file = path['params'].get('file')
if file:
filepath = os.path.join(self.BASE_PATH, file)
else:
Expand All @@ -154,10 +185,10 @@ def do_GET(self):
self.wfile.write(file_chunk)
except IOError:
response = ('File Not Found: |{path}| -> |{filepath}|'
.format(path=self.path, filepath=filepath))
.format(path=path['log_path'], filepath=filepath))
self.send_error(404, response)

elif api_config_enabled and stripped_path == PATHS.API:
elif api_config_enabled and path['path'] == PATHS.API:
html = self.api_config_page()
html = html.encode('utf-8')

Expand All @@ -169,11 +200,11 @@ def do_GET(self):
for chunk in self.get_chunks(html):
self.wfile.write(chunk)

elif api_config_enabled and stripped_path.startswith(PATHS.API_SUBMIT):
elif api_config_enabled and path['path'].startswith(PATHS.API_SUBMIT):
xbmc.executebuiltin('Dialog.Close(addonsettings,true)')

query = urlsplit(self.path).query
params = dict(parse_qsl(query))
query = path['query']
params = path['params']
updated = []

api_key = params.get('api_key')
Expand Down Expand Up @@ -227,11 +258,11 @@ def do_GET(self):
for chunk in self.get_chunks(html):
self.wfile.write(chunk)

elif stripped_path == PATHS.PING:
elif path['path'] == PATHS.PING:
self.send_error(204)

elif stripped_path.startswith(PATHS.REDIRECT):
url = dict(parse_qsl(urlsplit(self.path).query)).get('url')
elif path['path'].startswith(PATHS.REDIRECT):
url = path['params'].get('url')
if url:
wait(1)
self.send_response(301)
Expand All @@ -246,13 +277,14 @@ def do_GET(self):

# noinspection PyPep8Naming
def do_HEAD(self):
if not self.connection_allowed('HEAD'):
allowed, path = self.connection_allowed('HEAD')
if not allowed:
self.send_error(403)
return

if self.path.startswith(PATHS.MPD):
if path['path'].startswith(PATHS.MPD):
try:
file = dict(parse_qsl(urlsplit(self.path).query)).get('file')
file = path['params'].get('file')
if file:
file_path = os.path.join(self.BASE_PATH, file)
else:
Expand All @@ -266,22 +298,23 @@ def do_HEAD(self):
self.end_headers()
except IOError:
response = ('File Not Found: |{path}| -> |{file_path}|'
.format(path=self.path, file_path=file_path))
.format(path=path['log_path'], file_path=file_path))
self.send_error(404, response)

elif self.path.startswith(PATHS.REDIRECT):
elif path['path'].startswith(PATHS.REDIRECT):
self.send_error(404)

else:
self.send_error(501)

# noinspection PyPep8Naming
def do_POST(self):
if not self.connection_allowed('POST'):
allowed, path = self.connection_allowed('POST')
if not allowed:
self.send_error(403)
return

if self.path.startswith(PATHS.DRM):
if path['path'].startswith(PATHS.DRM):
home = xbmcgui.Window(10000)

lic_url = home.getProperty('-'.join((ADDON_ID, LICENSE_URL)))
Expand Down
3 changes: 2 additions & 1 deletion resources/lib/youtube_plugin/kodion/utils/methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,5 +326,6 @@ def wait(timeout=None):
return xbmc.Monitor().waitForAbort(timeout)


def redact_ip(url, ip_re=re_compile(r'([?&/])ip([=/])[^?&/]+')):
def redact_ip(url,
ip_re=re_compile(r'([?&/]|%3F|%26|%2F)ip([=/]|%3D|%2F)[^?&/%]+')):
return ip_re.sub(r'\g<1>ip\g<2><redacted>', url)

0 comments on commit f5578a7

Please sign in to comment.