Validate client_secret parameter (#6767)

This commit is contained in:
Andrew Morgan 2020-01-24 14:28:40 +00:00 committed by GitHub
parent fa4d609e20
commit 9f7aaf90b5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 93 additions and 6 deletions

1
changelog.d/6767.bugfix Normal file
View File

@ -0,0 +1 @@
Validate `client_secret` parameter using the regex provided by the Client-Server API, temporarily allowing `:` characters for older clients. The `:` character will be removed in a future release.

View File

@ -38,7 +38,7 @@ from synapse.api.errors import (
from synapse.config.emailconfig import ThreepidBehaviour from synapse.config.emailconfig import ThreepidBehaviour
from synapse.http.client import SimpleHttpClient from synapse.http.client import SimpleHttpClient
from synapse.util.hash import sha256_and_url_safe_base64 from synapse.util.hash import sha256_and_url_safe_base64
from synapse.util.stringutils import random_string from synapse.util.stringutils import assert_valid_client_secret, random_string
from ._base import BaseHandler from ._base import BaseHandler
@ -84,6 +84,8 @@ class IdentityHandler(BaseHandler):
raise SynapseError( raise SynapseError(
400, "Missing param client_secret in creds", errcode=Codes.MISSING_PARAM 400, "Missing param client_secret in creds", errcode=Codes.MISSING_PARAM
) )
assert_valid_client_secret(client_secret)
session_id = creds.get("sid") session_id = creds.get("sid")
if not session_id: if not session_id:
raise SynapseError( raise SynapseError(

View File

@ -30,6 +30,7 @@ from synapse.http.servlet import (
) )
from synapse.push.mailer import Mailer, load_jinja2_templates from synapse.push.mailer import Mailer, load_jinja2_templates
from synapse.util.msisdn import phone_number_to_msisdn from synapse.util.msisdn import phone_number_to_msisdn
from synapse.util.stringutils import assert_valid_client_secret
from synapse.util.threepids import check_3pid_allowed from synapse.util.threepids import check_3pid_allowed
from ._base import client_patterns, interactive_auth_handler from ._base import client_patterns, interactive_auth_handler
@ -81,6 +82,8 @@ class EmailPasswordRequestTokenRestServlet(RestServlet):
# Extract params from body # Extract params from body
client_secret = body["client_secret"] client_secret = body["client_secret"]
assert_valid_client_secret(client_secret)
email = body["email"] email = body["email"]
send_attempt = body["send_attempt"] send_attempt = body["send_attempt"]
next_link = body.get("next_link") # Optional param next_link = body.get("next_link") # Optional param
@ -166,8 +169,9 @@ class PasswordResetSubmitTokenServlet(RestServlet):
) )
sid = parse_string(request, "sid", required=True) sid = parse_string(request, "sid", required=True)
client_secret = parse_string(request, "client_secret", required=True)
token = parse_string(request, "token", required=True) token = parse_string(request, "token", required=True)
client_secret = parse_string(request, "client_secret", required=True)
assert_valid_client_secret(client_secret)
# Attempt to validate a 3PID session # Attempt to validate a 3PID session
try: try:
@ -353,6 +357,8 @@ class EmailThreepidRequestTokenRestServlet(RestServlet):
body = parse_json_object_from_request(request) body = parse_json_object_from_request(request)
assert_params_in_dict(body, ["client_secret", "email", "send_attempt"]) assert_params_in_dict(body, ["client_secret", "email", "send_attempt"])
client_secret = body["client_secret"] client_secret = body["client_secret"]
assert_valid_client_secret(client_secret)
email = body["email"] email = body["email"]
send_attempt = body["send_attempt"] send_attempt = body["send_attempt"]
next_link = body.get("next_link") # Optional param next_link = body.get("next_link") # Optional param
@ -413,6 +419,8 @@ class MsisdnThreepidRequestTokenRestServlet(RestServlet):
body, ["client_secret", "country", "phone_number", "send_attempt"] body, ["client_secret", "country", "phone_number", "send_attempt"]
) )
client_secret = body["client_secret"] client_secret = body["client_secret"]
assert_valid_client_secret(client_secret)
country = body["country"] country = body["country"]
phone_number = body["phone_number"] phone_number = body["phone_number"]
send_attempt = body["send_attempt"] send_attempt = body["send_attempt"]
@ -493,8 +501,9 @@ class AddThreepidEmailSubmitTokenServlet(RestServlet):
) )
sid = parse_string(request, "sid", required=True) sid = parse_string(request, "sid", required=True)
client_secret = parse_string(request, "client_secret", required=True)
token = parse_string(request, "token", required=True) token = parse_string(request, "token", required=True)
client_secret = parse_string(request, "client_secret", required=True)
assert_valid_client_secret(client_secret)
# Attempt to validate a 3PID session # Attempt to validate a 3PID session
try: try:
@ -559,6 +568,7 @@ class AddThreepidMsisdnSubmitTokenServlet(RestServlet):
body = parse_json_object_from_request(request) body = parse_json_object_from_request(request)
assert_params_in_dict(body, ["client_secret", "sid", "token"]) assert_params_in_dict(body, ["client_secret", "sid", "token"])
assert_valid_client_secret(body["client_secret"])
# Proxy submit_token request to msisdn threepid delegate # Proxy submit_token request to msisdn threepid delegate
response = await self.identity_handler.proxy_msisdn_submit_token( response = await self.identity_handler.proxy_msisdn_submit_token(
@ -600,8 +610,9 @@ class ThreepidRestServlet(RestServlet):
) )
assert_params_in_dict(threepid_creds, ["client_secret", "sid"]) assert_params_in_dict(threepid_creds, ["client_secret", "sid"])
client_secret = threepid_creds["client_secret"]
sid = threepid_creds["sid"] sid = threepid_creds["sid"]
client_secret = threepid_creds["client_secret"]
assert_valid_client_secret(client_secret)
validation_session = await self.identity_handler.validate_threepid_session( validation_session = await self.identity_handler.validate_threepid_session(
client_secret, sid client_secret, sid
@ -637,8 +648,9 @@ class ThreepidAddRestServlet(RestServlet):
body = parse_json_object_from_request(request) body = parse_json_object_from_request(request)
assert_params_in_dict(body, ["client_secret", "sid"]) assert_params_in_dict(body, ["client_secret", "sid"])
client_secret = body["client_secret"]
sid = body["sid"] sid = body["sid"]
client_secret = body["client_secret"]
assert_valid_client_secret(client_secret)
await self.auth_handler.validate_user_via_ui_auth( await self.auth_handler.validate_user_via_ui_auth(
requester, body, self.hs.get_ip_from_request(request) requester, body, self.hs.get_ip_from_request(request)
@ -676,8 +688,9 @@ class ThreepidBindRestServlet(RestServlet):
assert_params_in_dict(body, ["id_server", "sid", "client_secret"]) assert_params_in_dict(body, ["id_server", "sid", "client_secret"])
id_server = body["id_server"] id_server = body["id_server"]
sid = body["sid"] sid = body["sid"]
client_secret = body["client_secret"]
id_access_token = body.get("id_access_token") # optional id_access_token = body.get("id_access_token") # optional
client_secret = body["client_secret"]
assert_valid_client_secret(client_secret)
requester = await self.auth.get_user_by_req(request) requester = await self.auth.get_user_by_req(request)
user_id = requester.user.to_string() user_id = requester.user.to_string()

View File

@ -49,6 +49,7 @@ from synapse.http.servlet import (
from synapse.push.mailer import load_jinja2_templates from synapse.push.mailer import load_jinja2_templates
from synapse.util.msisdn import phone_number_to_msisdn from synapse.util.msisdn import phone_number_to_msisdn
from synapse.util.ratelimitutils import FederationRateLimiter from synapse.util.ratelimitutils import FederationRateLimiter
from synapse.util.stringutils import assert_valid_client_secret
from synapse.util.threepids import check_3pid_allowed from synapse.util.threepids import check_3pid_allowed
from ._base import client_patterns, interactive_auth_handler from ._base import client_patterns, interactive_auth_handler
@ -116,6 +117,8 @@ class EmailRegisterRequestTokenRestServlet(RestServlet):
# Extract params from body # Extract params from body
client_secret = body["client_secret"] client_secret = body["client_secret"]
assert_valid_client_secret(client_secret)
email = body["email"] email = body["email"]
send_attempt = body["send_attempt"] send_attempt = body["send_attempt"]
next_link = body.get("next_link") # Optional param next_link = body.get("next_link") # Optional param

View File

@ -1,5 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright 2014-2016 OpenMarket Ltd # Copyright 2014-2016 OpenMarket Ltd
# Copyright 2020 The Matrix.org Foundation C.I.C.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@ -14,14 +15,22 @@
# limitations under the License. # limitations under the License.
import random import random
import re
import string import string
import six import six
from six import PY2, PY3 from six import PY2, PY3
from six.moves import range from six.moves import range
from synapse.api.errors import Codes, SynapseError
_string_with_symbols = string.digits + string.ascii_letters + ".,;:^&*-_+=#~@" _string_with_symbols = string.digits + string.ascii_letters + ".,;:^&*-_+=#~@"
# https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-register-email-requesttoken
# Note: The : character is allowed here for older clients, but will be removed in a
# future release. Context: https://github.com/matrix-org/synapse/issues/6766
client_secret_regex = re.compile(r"^[0-9a-zA-Z\.\=\_\-\:]+$")
# random_string and random_string_with_symbols are used for a range of things, # random_string and random_string_with_symbols are used for a range of things,
# some cryptographically important, some less so. We use SystemRandom to make sure # some cryptographically important, some less so. We use SystemRandom to make sure
# we get cryptographically-secure randoms. # we get cryptographically-secure randoms.
@ -109,3 +118,11 @@ def exception_to_unicode(e):
return msg.decode("utf-8", errors="replace") return msg.decode("utf-8", errors="replace")
else: else:
return msg return msg
def assert_valid_client_secret(client_secret):
"""Validate that a given string matches the client_secret regex defined by the spec"""
if client_secret_regex.match(client_secret) is None:
raise SynapseError(
400, "Invalid client_secret parameter", errcode=Codes.INVALID_PARAM
)

View File

@ -0,0 +1,51 @@
# -*- coding: utf-8 -*-
# Copyright 2020 The Matrix.org Foundation C.I.C.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from synapse.api.errors import SynapseError
from synapse.util.stringutils import assert_valid_client_secret
from .. import unittest
class StringUtilsTestCase(unittest.TestCase):
def test_client_secret_regex(self):
"""Ensure that client_secret does not contain illegal characters"""
good = [
"abcde12345",
"ABCabc123",
"_--something==_",
"...--==-18913",
"8Dj2odd-e9asd.cd==_--ddas-secret-",
# We temporarily allow : characters: https://github.com/matrix-org/synapse/issues/6766
# To be removed in a future release
"SECRET:1234567890",
]
bad = [
"--+-/secret",
"\\dx--dsa288",
"",
"AAS//",
"asdj**",
">X><Z<!!-)))",
"a@b.com",
]
for client_secret in good:
assert_valid_client_secret(client_secret)
for client_secret in bad:
with self.assertRaises(SynapseError):
assert_valid_client_secret(client_secret)