Fixed possible security issues in HTTP server + minor optimizations

parent bea52af8
...@@ -19,6 +19,7 @@ CONFIG_HTTP = { ...@@ -19,6 +19,7 @@ CONFIG_HTTP = {
"API_ROOT": "/restconf", "API_ROOT": "/restconf",
"API_ROOT_STAGING": "/restconf_staging", "API_ROOT_STAGING": "/restconf_staging",
"SERVER_NAME": "hyper-h2", "SERVER_NAME": "hyper-h2",
"UPLOAD_SIZE_LIMIT": 1,
"PORT": 8443, "PORT": 8443,
"SERVER_SSL_CERT": "server.crt", "SERVER_SSL_CERT": "server.crt",
......
...@@ -169,17 +169,14 @@ def create_get_staging_api(ds: BaseDatastore): ...@@ -169,17 +169,14 @@ def create_get_staging_api(ds: BaseDatastore):
def get_file(prot: "H2Protocol", stream_id: int, headers: OrderedDict): def get_file(prot: "H2Protocol", stream_id: int, headers: OrderedDict):
# Ordinary file on filesystem # Ordinary file on filesystem
username = CertHelpers.get_field(prot.client_cert, "emailAddress") username = CertHelpers.get_field(prot.client_cert, "emailAddress")
url_split = headers[":path"].split("?") url_path = headers[":path"].split("?")[0]
url_path = url_split[0] url_path_safe = "".join(filter(lambda c: c.isalpha() or c in "/-_.", url_path)).replace("..", "").strip("/")
file_path = os.path.join(CONFIG_HTTP["DOC_ROOT"], url_path_safe)
file_path = os.path.join(CONFIG_HTTP["DOC_ROOT"], url_path[1:].replace("..", "").replace("&", ""))
if os.path.isdir(file_path): if os.path.isdir(file_path):
file_path = os.path.join(file_path, CONFIG_HTTP["DOC_DEFAULT_NAME"]) file_path = os.path.join(file_path, CONFIG_HTTP["DOC_DEFAULT_NAME"])
(ctype, encoding) = mimetypes.guess_type(file_path) ctype = mimetypes.guess_type(file_path)[0] or "application/octet-stream"
if ctype is None:
ctype = "application/octet-stream"
try: try:
fd = open(file_path, 'rb') fd = open(file_path, 'rb')
......
...@@ -6,7 +6,7 @@ from colorlog import error, warning as warn, info, debug ...@@ -6,7 +6,7 @@ from colorlog import error, warning as warn, info, debug
from typing import List, Tuple, Dict, Any, Callable from typing import List, Tuple, Dict, Any, Callable
from h2.connection import H2Connection from h2.connection import H2Connection
from h2.errors import PROTOCOL_ERROR from h2.errors import PROTOCOL_ERROR, ENHANCE_YOUR_CALM
from h2.events import DataReceived, RequestReceived, RemoteSettingsChanged, StreamEnded from h2.events import DataReceived, RequestReceived, RemoteSettingsChanged, StreamEnded
import jetconf.http_handlers as handlers import jetconf.http_handlers as handlers
...@@ -21,9 +21,10 @@ h2_handlers = None # type: HandlerList ...@@ -21,9 +21,10 @@ h2_handlers = None # type: HandlerList
class RequestData: class RequestData:
def __init__(self, headers=None, data=None): def __init__(self, headers: OrderedDict, data: BytesIO):
self.headers = headers self.headers = headers
self.data = data self.data = data
self.data_overflow = False
class HandlerList: class HandlerList:
...@@ -49,7 +50,7 @@ class H2Protocol(asyncio.Protocol): ...@@ -49,7 +50,7 @@ class H2Protocol(asyncio.Protocol):
def __init__(self): def __init__(self):
self.conn = H2Connection(client_side=False) self.conn = H2Connection(client_side=False)
self.transport = None self.transport = None
self.stream_data = {} self.stream_data = {} # type: Dict[int, RequestData]
self.client_cert = None # type: Dict[str, Any] self.client_cert = None # type: Dict[str, Any]
def connection_made(self, transport: asyncio.Transport): def connection_made(self, transport: asyncio.Transport):
...@@ -86,7 +87,12 @@ class H2Protocol(asyncio.Protocol): ...@@ -86,7 +87,12 @@ class H2Protocol(asyncio.Protocol):
except KeyError: except KeyError:
self.conn.reset_stream(event.stream_id, error_code=PROTOCOL_ERROR) self.conn.reset_stream(event.stream_id, error_code=PROTOCOL_ERROR)
else: else:
stream_data.data.write(event.data) # Check if incoming data are not excessively large
if (stream_data.data.tell() + len(event.data)) < (CONFIG_HTTP["UPLOAD_SIZE_LIMIT"] * 1048576):
stream_data.data.write(event.data)
else:
stream_data.data_overflow = True
self.conn.reset_stream(event.stream_id, error_code=ENHANCE_YOUR_CALM)
elif isinstance(event, StreamEnded): elif isinstance(event, StreamEnded):
# Process request # Process request
try: try:
...@@ -94,17 +100,20 @@ class H2Protocol(asyncio.Protocol): ...@@ -94,17 +100,20 @@ class H2Protocol(asyncio.Protocol):
except KeyError: except KeyError:
self.send_empty(event.stream_id, "400", "Bad Request") self.send_empty(event.stream_id, "400", "Bad Request")
else: else:
headers = request_data.headers if request_data.data_overflow:
body = request_data.data.getvalue().decode('utf-8') self.send_empty(event.stream_id, "406", "Not Acceptable")
http_method = headers[":method"]
if http_method in ("GET", "DELETE"):
self.handle_get_delete(headers, event.stream_id)
elif http_method in ("PUT", "POST"):
self.handle_put_post(headers, event.stream_id, body)
else: else:
warn("Unknown http method \"{}\"".format(headers[":method"])) headers = request_data.headers
self.send_empty(event.stream_id, "405", "Method Not Allowed") body = request_data.data.getvalue().decode('utf-8')
http_method = headers[":method"]
if http_method in ("GET", "DELETE"):
self.handle_get_delete(headers, event.stream_id)
elif http_method in ("PUT", "POST"):
self.handle_put_post(headers, event.stream_id, body)
else:
warn("Unknown http method \"{}\"".format(headers[":method"]))
self.send_empty(event.stream_id, "405", "Method Not Allowed")
# elif isinstance(event, RemoteSettingsChanged): # elif isinstance(event, RemoteSettingsChanged):
# changed_settings = {} # changed_settings = {}
# for s in event.changed_settings.items(): # for s in event.changed_settings.items():
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment