CVE-2021-21273 (Python, published 2021-02-26T18:15Z)
Description: value: Synapse is a Matrix reference homeserver written in python (pypi package matrix-synapse). Matrix is an ecosystem for open federated Instant Messaging and VoIP. In Synapse before version 1.25.0, requests to user provided domains were not restricted to external IP addresses when calculating the key validity for third-party invite events and sending push notifications. This could cause Synapse to make requests to internal infrastructure. The type of request was not controlled by the user, although limited modification of request bodies was possible. For the most thorough protection server administrators should remove the deprecated `federation_ip_range_blacklist` from their settings after upgrading to Synapse v1.25.0 which will result in Synapse using the improved default IP address restrictions. See the new `ip_range_blacklist` and `ip_range_whitelist` settings if more specific control is necessary.
Severity: MEDIUM (CVSS v3 base score: 6.1)
Weakness: CWE-601 \u2014 URL Redirection to Untrusted Site ('Open Redirect')
A web application accepts a user-controlled input that specifies a link to an external site, and uses that link in a Redirect. This simplifies phishing attacks.
Repository: https://github.com/matrix-org/synapse
Files changed:
changelog.d/8821.bugfix
docs/sample_config.yaml
synapse/app/generic_worker.py
synapse/config/federation.py
synapse/crypto/keyring.py
synapse/federation/federation_server.py
synapse/federation/transport/client.py
synapse/handlers/federation.py
synapse/handlers/identity.py
synapse/http/client.py
synapse/http/federation/matrix_federation_agent.py
synapse/http/matrixfederationclient.py
synapse/push/httppusher.py
synapse/rest/media/v1/media_repository.py
synapse/server.py
tests/api/test_filtering.py
tests/app/test_frontend_proxy.py
tests/app/test_openid_listener.py
tests/crypto/test_keyring.py
tests/handlers/test_device.py
tests/handlers/test_directory.py
tests/handlers/test_federation.py
tests/handlers/test_presence.py
tests/handlers/test_profile.py
tests/handlers/test_typing.py
tests/http/federation/test_matrix_federation_agent.py
tests/push/test_http.py
tests/replication/_base.py
tests/replication/test_federation_sender_shard.py
tests/replication/test_pusher_shard.py
tests/rest/admin/test_admin.py
tests/rest/client/v1/test_presence.py
tests/rest/client/v1/test_profile.py
tests/rest/client/v1/test_rooms.py
tests/rest/client/v1/test_typing.py
tests/rest/key/v2/test_remote_key_resource.py
tests/rest/media/v1/test_media_storage.py
tests/storage/test_e2e_room_keys.py
tests/storage/test_purge.py
tests/storage/test_redaction.py
tests/storage/test_roommember.py
tests/test_federation.py
tests/test_server.py
Commit message: Apply an IP range blacklist to push and key revocation requests. (#8821)

Replaces the `federation_ip_range_blacklist` configuration setting with an
`ip_range_blacklist` setting with wider scope. It now applies to:

* Federation
* Identity servers
* Push notifications
* Checking key validitity for third-party invite events

The old `federation_ip_range_blacklist` setting is still honored if present, but
with reduced scope (it only applies to federation and identity servers).

Vulnerable code:
# Prevent federation requests from being sent to the following
# blacklist IP address CIDR ranges. If this option is not specified, or
# specified with an empty list, no ip range blacklist will be enforced.
# As of Synapse v1.4.0 this option also affects any outbound requests to identity
# servers provided by user input.
federation_ip_range_blacklist:
        self.http_client = hs.get_simple_http_client()
        self.federation_ip_range_blacklist = config.get(
            "federation_ip_range_blacklist", []
        )
            self.federation_ip_range_blacklist = IPSet(
                self.federation_ip_range_blacklist
            )

            # Always blacklist 0.0.0.0, ::
            self.federation_ip_range_blacklist.update(["0.0.0.0", "::"])
        # Prevent federation requests from being sent to the following
        # blacklist IP address CIDR ranges. If this option is not specified, or
        # specified with an empty list, no ip range blacklist will be enforced.
        # As of Synapse v1.4.0 this option also affects any outbound requests to identity
        # servers provided by user input.
        federation_ip_range_blacklist:
        self.client = hs.get_http_client()
        self.client = hs.get_http_client()
        self.http_client = hs.get_simple_http_client()
        self.client = hs.get_http_client()
        self.http_client = hs.get_simple_http_client()
        # We create a blacklisting instance of SimpleHttpClient for contacting identity
        # servers specified by clients
        self.federation_http_client = hs.get_http_client()
class IPBlacklistingResolver:
            real_reactor = hs.get_reactor()
            nameResolver = IPBlacklistingResolver(
                real_reactor, self._ip_whitelist, self._ip_blacklist

            @implementer(IReactorPluggableNameResolver)
            class Reactor:
                def __getattr__(_self, attr):
                    if attr == "nameResolver":
                        return nameResolver
                    else:
                        return getattr(real_reactor, attr)

            self.reactor = Reactor()
from netaddr import AddrFormatError, IPAddress
                agent=Agent(
                    pool=self._pool,
                    contextFactory=tls_client_options_factory,
from zope.interface import implementer
from twisted.internet.interfaces import IReactorPluggableNameResolver, IReactorTime
    IPBlacklistingResolver,
        real_reactor = hs.get_reactor()

        nameResolver = IPBlacklistingResolver(
            real_reactor, None, hs.config.federation_ip_range_blacklist
        @implementer(IReactorPluggableNameResolver)
        class Reactor:
            def __getattr__(_self, attr):
                if attr == "nameResolver":
                    return nameResolver
                else:
                    return getattr(real_reactor, attr)

        self.reactor = Reactor()

            self.reactor, tls_client_options_factory, user_agent
        self.http_client = hs.get_proxied_http_client()
        self.client = hs.get_http_client()
    @cache_in_self
    def get_http_client(self) -> MatrixFederationHttpClient:
        tls_client_options_factory = context_factory.FederationPolicyForHTTPS(
            self.config
        )
        return MatrixFederationHttpClient(self, tls_client_options_factory)

            self.addCleanup, http_client=self.mock_http_client, keyring=Mock(),
            http_client=None, homeserver_to_use=GenericWorkerServer
            http_client=None, homeserver_to_use=GenericWorkerServer
            http_client=None, homeserver_to_use=SynapseHomeServer
        hs = self.setup_test_homeserver(http_client=self.http_client)
        return self.setup_test_homeserver(http_client=self.http_client, config=config)
        hs = self.setup_test_homeserver("server", http_client=None)
        hs = self.setup_test_homeserver("server", http_client=None)
            http_client=None,
        hs = self.setup_test_homeserver(http_client=None)
            "server", http_client=None, federation_sender=Mock()
            http_client=None,
            http_client=mock_federation_client,
        put_json = self.hs.get_http_client().put_json
        put_json = self.hs.get_http_client().put_json
        hs = self.setup_test_homeserver(config=config, proxied_http_client=m)
            http_client=None,
                useful to e.g. pass some mocks for things like `http_client`
            http_client=mock_client,
            http_client=mock_client1,
            http_client=mock_client2,
            http_client=mock_client1,
            http_client=mock_client2,
            proxied_http_client=http_client_mock,
            proxied_http_client=http_client_mock1,
            proxied_http_client=http_client_mock2,
        hs = self.setup_test_homeserver(config=config, http_client=client)
            http_client=None,
            http_client=None,
            "red", http_client=None, federation_client=Mock(),
            "red", http_client=None, federation_client=Mock(),
        return self.setup_test_homeserver(http_client=self.http_client)
            http_client=self.http_client2, config=config
        hs = self.setup_test_homeserver(config=config, http_client=client)
        hs = self.setup_test_homeserver("server", http_client=None)
        hs = self.setup_test_homeserver("server", http_client=None)
            resource_for_federation=Mock(), http_client=None, config=config
            resource_for_federation=Mock(), http_client=None
            http_client=self.http_client,
            self.addCleanup, http_client=None, clock=self.hs_clock, reactor=self.reactor

Fixed code:
Apply the `federation_ip_range_blacklist` to push and key revocation requests.
# Prevent outgoing requests from being sent to the following blacklisted IP address
# CIDR ranges. If this option is not specified, or specified with an empty list,
# no IP range blacklist will be enforced.
# The blacklist applies to the outbound requests for federation, identity servers,
# push servers, and for checking key validitity for third-party invite events.
# This option replaces federation_ip_range_blacklist in Synapse v1.24.0.
#
ip_range_blacklist:
        ip_range_blacklist = config.get("ip_range_blacklist", [])
            self.ip_range_blacklist = IPSet(ip_range_blacklist)
        except Exception as e:
            raise ConfigError("Invalid range(s) provided in ip_range_blacklist: %s" % e)
        # Always blacklist 0.0.0.0, ::
        self.ip_range_blacklist.update(["0.0.0.0", "::"])

        # The federation_ip_range_blacklist is used for backwards-compatibility
        # and only applies to federation and identity servers. If it is not given,
        # default to ip_range_blacklist.
        federation_ip_range_blacklist = config.get(
            "federation_ip_range_blacklist", ip_range_blacklist
        )
        try:
            self.federation_ip_range_blacklist = IPSet(federation_ip_range_blacklist)
        # Always blacklist 0.0.0.0, ::
        self.federation_ip_range_blacklist.update(["0.0.0.0", "::"])
        # Prevent outgoing requests from being sent to the following blacklisted IP address
        # CIDR ranges. If this option is not specified, or specified with an empty list,
        # no IP range blacklist will be enforced.
        # The blacklist applies to the outbound requests for federation, identity servers,
        # push servers, and for checking key validitity for third-party invite events.
        # This option replaces federation_ip_range_blacklist in Synapse v1.24.0.
        #
        ip_range_blacklist:
        self.client = hs.get_federation_http_client()
        self.client = hs.get_federation_http_client()
        self.client = hs.get_federation_http_client()
        self.http_client = hs.get_proxied_blacklisted_http_client()
        # An HTTP client for contacting trusted URLs.
        # An HTTP client for contacting identity servers specified by clients.
        self.federation_http_client = hs.get_federation_http_client()
class _IPBlacklistingResolver:
@implementer(IReactorPluggableNameResolver)
class BlacklistingReactorWrapper:
    """
    A Reactor wrapper which will prevent DNS resolution to blacklisted IP
    addresses, to prevent DNS rebinding.
    """

    def __init__(
        self,
        reactor: IReactorPluggableNameResolver,
        ip_whitelist: Optional[IPSet],
        ip_blacklist: IPSet,
    ):
        self._reactor = reactor

        # We need to use a DNS resolver which filters out blacklisted IP
        # addresses, to prevent DNS rebinding.
        self._nameResolver = _IPBlacklistingResolver(
            self._reactor, ip_whitelist, ip_blacklist
        )

    def __getattr__(self, attr: str) -> Any:
        # Passthrough to the real reactor except for the DNS resolver.
        if attr == "nameResolver":
            return self._nameResolver
        else:
            return getattr(self._reactor, attr)


            self.reactor = BlacklistingReactorWrapper(
                hs.get_reactor(), self._ip_whitelist, self._ip_blacklist
from netaddr import AddrFormatError, IPAddress, IPSet
from synapse.http.client import BlacklistingAgentWrapper
        ip_blacklist: IPSet,
            # Note that the name resolver has already been wrapped in a
            # IPBlacklistingResolver by MatrixFederationHttpClient.
                agent=BlacklistingAgentWrapper(
                    Agent(
                        self._reactor,
                        pool=self._pool,
                        contextFactory=tls_client_options_factory,
                    ),
                    ip_blacklist=ip_blacklist,
from twisted.internet.interfaces import IReactorTime
    BlacklistingReactorWrapper,
        self.reactor = BlacklistingReactorWrapper(
            hs.get_reactor(), None, hs.config.federation_ip_range_blacklist
            self.reactor,
            tls_client_options_factory,
            user_agent,
            hs.config.federation_ip_range_blacklist,
        self.http_client = hs.get_proxied_blacklisted_http_client()
        self.client = hs.get_federation_http_client()
        """
        An HTTP client with no special configuration.
        """
        """
        An HTTP client that uses configured HTTP(S) proxies.
        """
        return SimpleHttpClient(
            self,
            http_proxy=os.getenvb(b"http_proxy"),
            https_proxy=os.getenvb(b"HTTPS_PROXY"),
        )

    @cache_in_self
    def get_proxied_blacklisted_http_client(self) -> SimpleHttpClient:
        """
        An HTTP client that uses configured HTTP(S) proxies and blacklists IPs
        based on the IP range blacklist.
        """
            ip_blacklist=self.config.ip_range_blacklist,
    @cache_in_self
    def get_federation_http_client(self) -> MatrixFederationHttpClient:
        """
        An HTTP client for federation.
        """
        tls_client_options_factory = context_factory.FederationPolicyForHTTPS(
            self.config
        )
        return MatrixFederationHttpClient(self, tls_client_options_factory)

            self.addCleanup,
            federation_http_client=self.mock_http_client,
            keyring=Mock(),
            federation_http_client=None, homeserver_to_use=GenericWorkerServer
            federation_http_client=None, homeserver_to_use=GenericWorkerServer
            federation_http_client=None, homeserver_to_use=SynapseHomeServer
        hs = self.setup_test_homeserver(federation_http_client=self.http_client)
        return self.setup_test_homeserver(
            federation_http_client=self.http_client, config=config
        )
        hs = self.setup_test_homeserver("server", federation_http_client=None)
        hs = self.setup_test_homeserver("server", federation_http_client=None)
            federation_http_client=None,
        hs = self.setup_test_homeserver(federation_http_client=None)
            "server", federation_http_client=None, federation_sender=Mock()
            federation_http_client=None,
            federation_http_client=mock_federation_client,
        put_json = self.hs.get_federation_http_client().put_json
        put_json = self.hs.get_federation_http_client().put_json
from netaddr import IPSet
            ip_blacklist=IPSet(),
            ip_blacklist=IPSet(),
        hs = self.setup_test_homeserver(
            config=config, proxied_blacklisted_http_client=m
        )
            federation_http_client=None,
                useful to e.g. pass some mocks for things like `federation_http_client`
            federation_http_client=mock_client,
            federation_http_client=mock_client1,
            federation_http_client=mock_client2,
            federation_http_client=mock_client1,
            federation_http_client=mock_client2,
            proxied_blacklisted_http_client=http_client_mock,
            proxied_blacklisted_http_client=http_client_mock1,
            proxied_blacklisted_http_client=http_client_mock2,
        hs = self.setup_test_homeserver(config=config, federation_http_client=client)
            federation_http_client=None,
            federation_http_client=None,
            "red", federation_http_client=None, federation_client=Mock(),
            "red", federation_http_client=None, federation_client=Mock(),
        return self.setup_test_homeserver(federation_http_client=self.http_client)
            federation_http_client=self.http_client2, config=config
        hs = self.setup_test_homeserver(config=config, federation_http_client=client)
        hs = self.setup_test_homeserver("server", federation_http_client=None)
        hs = self.setup_test_homeserver("server", federation_http_client=None)
            resource_for_federation=Mock(), federation_http_client=None, config=config
            resource_for_federation=Mock(), federation_http_client=None
            federation_http_client=self.http_client,
            self.addCleanup,
            federation_http_client=None,
            clock=self.hs_clock,
            reactor=self.reactor,