From 879bff19cef71eab3c10988c11540016911fd443 Mon Sep 17 00:00:00 2001 From: tuanaiseo Date: Fri, 3 Apr 2026 19:49:03 +0700 Subject: [PATCH] fix(security): ssrf risk in openid discovery fetch `fetch_openid_config()` performs `requests.get()` on a URL derived from untrusted `issuer` input without host allowlisting, scheme enforcement, or private-address blocking. An attacker could force server-side requests to internal services/metadata endpoints (SSRF). Affected files: utils.py Signed-off-by: tuanaiseo <221258316+tuanaiseo@users.noreply.github.com> --- kinto/plugins/openid/utils.py | 40 +++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/kinto/plugins/openid/utils.py b/kinto/plugins/openid/utils.py index 60e05e6d8..6940ec64f 100644 --- a/kinto/plugins/openid/utils.py +++ b/kinto/plugins/openid/utils.py @@ -1,14 +1,46 @@ +import ipaddress +import socket +from urllib.parse import urlparse, urlunparse + import requests _configs = {} +def _normalize_issuer(issuer): + parsed = urlparse(issuer) + if parsed.scheme != "https": + raise ValueError("Invalid issuer") + + if not parsed.netloc or parsed.params or parsed.query or parsed.fragment: + raise ValueError("Invalid issuer") + + hostname = parsed.hostname + if not hostname or hostname.rstrip(".").lower() == "localhost": + raise ValueError("Invalid issuer") + + try: + addrinfo = socket.getaddrinfo(hostname, None) + except socket.gaierror: + raise ValueError("Invalid issuer") + + for _, _, _, _, sockaddr in addrinfo: + address = ipaddress.ip_address(sockaddr[0]) + if not address.is_global: + raise ValueError("Invalid issuer") + + path = parsed.path.rstrip("/") + return urlunparse((parsed.scheme, parsed.netloc, path, "", "", "")) + + def fetch_openid_config(issuer): global _configs - if issuer not in _configs: - resp = requests.get(issuer.rstrip("/") + "/.well-known/openid-configuration") - _configs[issuer] = resp.json() + normalized_issuer = _normalize_issuer(issuer) + + if normalized_issuer not in _configs: + resp = requests.get(normalized_issuer + "/.well-known/openid-configuration") + _configs[normalized_issuer] = resp.json() - return _configs[issuer] + return _configs[normalized_issuer]