From 5c0cfade1237fa7f590dfbd40a4260a8011358eb Mon Sep 17 00:00:00 2001 From: "Roger A. Light" Date: Sat, 3 Dec 2022 22:13:40 +0000 Subject: [PATCH] Add http origin checking to built in websockets. --- include/mosquitto.h | 1 + src/http_serv.c | 13 ++++ src/loop.c | 3 + test/broker/13-websocket-bad-origin.py | 90 ++++++++++++++++++++++++++ test/broker/Makefile | 1 + test/broker/test.py | 2 + 6 files changed, 110 insertions(+) create mode 100755 test/broker/13-websocket-bad-origin.py diff --git a/include/mosquitto.h b/include/mosquitto.h index 5aac8c1a..4de6ec4a 100644 --- a/include/mosquitto.h +++ b/include/mosquitto.h @@ -125,6 +125,7 @@ enum mosq_err_t { /* 28, 29, 30 - was internal only, moved to MQTT v5 section. */ MOSQ_ERR_ALREADY_EXISTS = 31, MOSQ_ERR_PLUGIN_IGNORE = 32, + MOSQ_ERR_HTTP_BAD_ORIGIN = 33, /* MQTT v5 direct equivalents 128-255 */ MOSQ_ERR_UNSPECIFIED = 128, diff --git a/src/http_serv.c b/src/http_serv.c index 42a7a903..95d0db29 100644 --- a/src/http_serv.c +++ b/src/http_serv.c @@ -213,6 +213,19 @@ int http__read(struct mosquitto *mosq) } strncpy(mosq->address, forwarded_for, (size_t)forwarded_for_len); mosq->address[forwarded_for_len] = '\0'; + }else if(!strncasecmp(http_headers[i].name, "Origin", http_headers[i].name_len)){ + if(mosq->listener){ + bool have_match = false; + for(int j=0; jlistener->ws_origin_count; j++){ + if(!strncmp(mosq->listener->ws_origins[j], http_headers[i].value, http_headers[i].value_len)){ + have_match = true; + break; + } + } + if(!have_match){ + return MOSQ_ERR_HTTP_BAD_ORIGIN; + } + } }else{ /* Unknown header */ } diff --git a/src/loop.c b/src/loop.c index 525e4f52..f6133d1a 100644 --- a/src/loop.c +++ b/src/loop.c @@ -368,6 +368,9 @@ void do_disconnect(struct mosquitto *context, int reason) case MOSQ_ERR_SESSION_TAKEN_OVER: log__printf(NULL, MOSQ_LOG_NOTICE, "Client %s disconnected, session taken over.", id); break; + case MOSQ_ERR_HTTP_BAD_ORIGIN: + log__printf(NULL, MOSQ_LOG_NOTICE, "Client %s disconnected, non-matching http origin.", id); + break; default: log__printf(NULL, MOSQ_LOG_NOTICE, "Bad socket read/write on client %s: %s", id, mosquitto_strerror(reason)); break; diff --git a/test/broker/13-websocket-bad-origin.py b/test/broker/13-websocket-bad-origin.py new file mode 100755 index 00000000..b9afe51a --- /dev/null +++ b/test/broker/13-websocket-bad-origin.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 + +from mosq_test_helper import * + +rc = 1 + +def write_config(filename, port): + with open(filename, 'w') as f: + f.write("listener %d\n" % (port)) + f.write("allow_anonymous true\n") + f.write("protocol websockets\n") + f.write("websockets_origin example.org\n") + +def do_test(publish_packet, reason_code, error_string): + global rc + + rc = 1 + + connect_packet = mosq_test.gen_connect("13-malformed-publish-v5", proto_ver=5) + + connack_props = mqtt5_props.gen_uint16_prop(mqtt5_props.PROP_TOPIC_ALIAS_MAXIMUM, 10) + connack_props += mqtt5_props.gen_byte_prop(mqtt5_props.PROP_RETAIN_AVAILABLE, 0) + connack_props += mqtt5_props.gen_uint16_prop(mqtt5_props.PROP_RECEIVE_MAXIMUM, 20) + connack_props += mqtt5_props.gen_byte_prop(mqtt5_props.PROP_MAXIMUM_QOS, 1) + + connack_packet = mosq_test.gen_connack(rc=0, proto_ver=5, properties=connack_props, property_helper=False) + + mid = 0 + disconnect_packet = mosq_test.gen_disconnect(proto_ver=5, reason_code=reason_code) + + sock = mosq_test.do_client_connect(connect_packet, connack_packet, port=port) + mosq_test.do_send_receive(sock, publish_packet, disconnect_packet, error_string=error_string) + rc = 0 + + +port = mosq_test.get_port() +conf_file = os.path.basename(__file__).replace('.py', '.conf') +write_config(conf_file, port) +broker = mosq_test.start_broker(filename=os.path.basename(__file__), use_conf=True, port=port) + +try: + websocket_req_bad = b"GET /mqtt HTTP/1.1\r\n" \ + + b"Host: localhost\r\n" \ + + b"Upgrade: websocket\r\n" \ + + b"Connection: Upgrade\r\n" \ + + B"Sec-WebSocket-Key: 1JaITHdgDZVd/4OE2AzTTA==\r\n" \ + + b"Sec-WebSocket-Protocol: mqtt\r\n" \ + + b"Sec-WebSocket-Version: 13\r\n" \ + + b"Origin: localhost\r\n" \ + + b"\r\n" + + sock = mosq_test.do_client_connect(websocket_req_bad, b"", port=port) + sock.close() +except BrokenPipeError: + pass + +try: + websocket_req_good = b"GET /mqtt HTTP/1.1\r\n" \ + + b"Host: localhost\r\n" \ + + b"Upgrade: websocket\r\n" \ + + b"Connection: Upgrade\r\n" \ + + B"Sec-WebSocket-Key: 1JaITHdgDZVd/4OE2AzTTA==\r\n" \ + + b"Sec-WebSocket-Protocol: mqtt\r\n" \ + + b"Sec-WebSocket-Version: 13\r\n" \ + + b"Origin: example.org\r\n" \ + + b"\r\n" + + websocket_resp_good = b"HTTP/1.1 101 Switching Protocols\r\n" \ + + b"Upgrade: WebSocket\r\n" \ + + b"Connection: Upgrade\r\n" \ + + b"Sec-WebSocket-Accept: Ako91O0lxiq8gN0+b9YCijMx8lk=\r\n" \ + + b"Sec-WebSocket-Protocol: mqtt\r\n" \ + + b"\r\n" + + sock = mosq_test.do_client_connect(websocket_req_good, websocket_resp_good, port=port) + sock.close() + + rc = 0 +except Exception as err: + print(err) +finally: + broker.terminate() + if mosq_test.wait_for_subprocess(broker): + print("broker not terminated") + if rc == 0: rc=1 + (stdo, stde) = broker.communicate() + os.remove(conf_file) + if rc: + print(stde.decode('utf-8')) + exit(rc) diff --git a/test/broker/Makefile b/test/broker/Makefile index 7c1879da..37f0985d 100644 --- a/test/broker/Makefile +++ b/test/broker/Makefile @@ -236,6 +236,7 @@ endif ./12-prop-subpub-payload-format.py 13 : + ./13-websocket-bad-origin.py 14 : ifeq ($(WITH_TLS),yes) diff --git a/test/broker/test.py b/test/broker/test.py index 9c351003..8108cb0a 100755 --- a/test/broker/test.py +++ b/test/broker/test.py @@ -201,6 +201,8 @@ tests = [ (1, './12-prop-subpub-content-type.py'), (1, './12-prop-subpub-payload-format.py'), + (1, './13-websocket-bad-origin.py'), + (1, './14-dynsec-acl.py'), (1, './14-dynsec-allow-wildcard.py'), (1, './14-dynsec-anon-group.py'),