Skip to main content

Kolibri CVE-2026-48053

MEDIUM
Server-Side Request Forgery (SSRF) (CWE-918)
2026-06-11 https://github.com/learningequality/kolibri GHSA-4mj9-pf4r-cqrc
5.8
CVSS 3.1 · GitHub Advisory
Share

Severity by source

GitHub Advisory PRIMARY
5.8 MEDIUM
AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:N/A:N
vuln.today AI
5.8 MEDIUM

GET endpoint requires no authentication (PR:N); attacker reaches systems beyond Kolibri itself (S:C); confidentiality scored Low as general-case JSON reflection, though IMDSv1 deployments face higher practical impact.

3.1 AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:N/A:N
4.0 AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:N/SC:L/SI:N/SA:N

Primary rating from GitHub Advisory.

CVSS VectorGitHub Advisory

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:N/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
Changed
Confidentiality
Low
Integrity
None
Availability
None

Lifecycle Timeline

3
Source Code Evidence Fetched
Jun 11, 2026 - 17:32 vuln.today
Analysis Generated
Jun 11, 2026 - 17:32 vuln.today
CVE Published
Jun 11, 2026 - 17:10 github-advisory
MEDIUM 5.8

Blast Radius

ecosystem impact
† from your stack dependencies † transitive graph · vuln.today resolves 4-path depth
  • 3 pypi packages depend on kolibri (3 direct, 0 indirect)

Ecosystem-wide dependent count for version 0.19.4.

DescriptionGitHub Advisory

Summary

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.

Affected endpoints

Reported:

  • GET /api/auth/remotefacilityuserRemoteFacilityUserViewset (kolibri/core/auth/api.py:1570). No authentication required.
  • POST /api/auth/remotefacilityauthenticateduserinfoRemoteFacilityUserAuthenticatedViewset (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.

Root cause

Two compounding issues:

  1. Response reflection - these endpoints returned the remote server's JSON body more or less verbatim to the caller (Response(response.json()), Response(facility_info["users"]), etc.).
  2. No restriction on the remote target - 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.

Two reflection vectors

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.

Reproduction

The vulnerability can be reproduced by pointing baseurl at an attacker-controlled HTTP server that:

  1. Responds to GET /api/public/info/ with a valid Kolibri info payload (so NetworkClient.build_for_address() succeeds).
  2. GET vector: responds to GET /api/public/facilitysearchuser/ with a 302 redirect to the target URL. The redirected response body is reflected via Response(response.json()).
  3. POST vector: responds to the relevant remote URL with crafted JSON containing additional fields. The full JSON is reflected.

A working PoC has been retained internally and is not published with this advisory.

Demonstrated impact (pre-fix)

  • Unauthenticated outbound requests from the Kolibri server to any HTTP(S) URL the attacker chose (GET endpoint only; the others required auth or an unprovisioned device).
  • Reflected data exfiltration for any HTTP endpoint that responded to a plain GET with JSON and no special request headers.
  • Cloud metadata reachability was realistic but service-specific:
  • AWS IMDSv1 - reachable
  • DigitalOcean (/metadata/v1.json) - reachable
  • GCP, Azure, AWS IMDSv2 - *not* reachable via this vector (require Metadata-Flavor / Metadata / token headers that the attacker could not inject)
  • Reachability of internal HTTP services on the same network as the Kolibri server, with their JSON responses returned to the attacker.

Not demonstrated

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.

Mitigation

Four layers of defence:

  1. Response sanitisation. Each affected endpoint now coerces the remote response to a documented shape before returning it. Smuggled fields are dropped.
  2. Authentication. The previously-open RemoteFacilityUser* endpoints now require an authenticated caller (or an unprovisioned device, for setup-wizard flows).
  3. Cross-host redirect blocking. Remote-fetch HTTP sessions refuse 30x responses that point to a different hostname. Same-host redirects still work.
  4. Peer allowlist. Endpoints that accept a caller-supplied 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.

Credit

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>

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.

Details

This is mainly due to the following issues:

1. Missing authentication on the API endpoint

File: kolibri/core/auth/api.py, line ~1553

python
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

python
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:

  • RFC 1918 private IPs (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)
  • Loopback addresses (127.0.0.0/8, ::1)
  • Link-local addresses (169.254.0.0/16, including AWS/GCP/Azure metadata endpoints)
  • IPv6 equivalents of any of the above

PoC

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):

bash
# 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-alive

Testers have also confirmed the issue against live deployments of Kolibri.

Impact

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>

AnalysisAI

Server-side request forgery in Kolibri (pip/kolibri <= 0.19.3) allows network-reachable attackers to force the Kolibri server to issue outbound HTTP requests to arbitrary internal hosts, cloud metadata endpoints, and internal services, with JSON response bodies reflected directly to the caller. The primary GET endpoint at /api/auth/remotefacilityuser required no authentication whatsoever, making this exploitable by any party with network access to the Kolibri server. A working proof-of-concept was retained internally by the reporting researcher but was not published; no public exploit code exists and CISA KEV listing has not been identified at time of analysis.

Technical ContextAI

Kolibri is a Django-based offline educational platform (CPE: pkg:pip/kolibri) built with Django REST Framework. The vulnerability (CWE-918: Server-Side Request Forgery) stems from two compounding design flaws in kolibri/core/auth/api.py and related viewsets: first, the RemoteFacilityUserViewset defined no permission_classes, defaulting DRF to AllowAny; second, the baseurl parameter was validated only by Django's URLValidator with schemes=['http','https'], which checks URL format but imposes no restrictions on RFC 1918 ranges, loopback, or link-local addresses (169.254.0.0/16). The NetworkClient.build_for_address() utility accepted any URL that returned a valid Kolibri-shaped /api/public/info/ probe response, and the underlying requests library followed HTTP 30x redirects by default - enabling a pivot from the attacker-controlled peer to any HTTP host reachable from the Kolibri server. Four endpoints shared this pattern: GET /api/auth/remotefacilityuser (unauthenticated), POST /api/auth/remotefacilityauthenticateduserinfo (auth checked against remote, not local, Kolibri), POST /api/public/setupwizard/loddata (reachable on unprovisioned devices), and GET /api/public/networklocation/<id>/facilities/ (locally authenticated). The reflection pattern - Response(response.json()) - returned the full remote JSON body verbatim, including smuggled fields.

RemediationAI

Vendor-released patch: kolibri 0.19.4. Operators should upgrade immediately via pip install --upgrade kolibri or the equivalent package manager command. The fix applies four layered controls: response sanitization (remote payloads are now coerced to a documented schema before being returned, dropping smuggled fields), mandatory authentication on the formerly open RemoteFacilityUser* endpoints, cross-host redirect blocking (30x responses that change hostname are now refused), and a peer allowlist restricting caller-supplied baseurl values to already-known peers. If immediate upgrade is not feasible, the most impactful compensating control is to restrict network access to the Kolibri HTTP port (default 8080) to trusted clients only using host-based firewall rules or a reverse proxy with IP allowlisting - this does not eliminate the SSRF but prevents unauthenticated external parties from triggering it. On cloud instances, enforcing IMDSv2 at the instance metadata service level (requiring a session token header) will block the highest-severity metadata exfiltration path independently of the Kolibri patch. Advisory: https://github.com/learningequality/kolibri/security/advisories/GHSA-4mj9-pf4r-cqrc.

More in Python

View all
CVE-2025-24016 CRITICAL POC
9.9 Feb 10

Wazuh SIEM platform versions 4.4.0 through 4.9.0 contain an unsafe deserialization vulnerability in the DistributedAPI t

CVE-2025-27520 CRITICAL POC
9.8 Apr 04

BentoML version 1.4.2 and earlier contains an unauthenticated remote code execution vulnerability through insecure deser

CVE-2025-2945 CRITICAL POC
9.9 Apr 03

pgAdmin 4 contains critical remote code execution vulnerabilities in the Query Tool download and Cloud Deployment endpoi

CVE-2013-5093 MEDIUM POC
6.8 Sep 27

The renderLocalView function in render/views.py in graphite-web in Graphite 0.9.5 through 0.9.10 uses the pickle Python

CVE-2025-32375 CRITICAL POC
9.8 Apr 09

BentoML is a Python library for building online serving systems optimized for AI apps and model inference. Rated critica

CVE-2014-0224 HIGH POC
7.4 Jun 05

OpenSSL before 0.9.8za, 1.0.0 before 1.0.0m, and 1.0.1 before 1.0.1h does not properly restrict processing of ChangeCiph

CVE-2024-21644 HIGH POC
7.5 Jan 08

pyLoad download manager version prior to 0.5.0b3.dev77 exposes the Flask SECRET_KEY through an unauthenticated endpoint.

CVE-2017-9462 HIGH POC
8.8 Jun 06

In Mercurial before 4.1.3, "hg serve --stdio" allows remote authenticated users to launch the Python debugger, and conse

CVE-2026-39987 CRITICAL POC
9.3 Apr 08

Unauthenticated remote code execution in Marimo ≤0.20.4 allows attackers to execute arbitrary system commands via the `/

CVE-2024-21645 MEDIUM POC
5.3 Jan 08

pyLoad is the free and open-source Download Manager written in pure Python. Rated medium severity (CVSS 5.3), this vulne

CVE-2026-33017 CRITICAL POC
9.3 Mar 17

Langflow (a visual LLM pipeline builder) contains a critical unauthenticated code execution vulnerability (CVE-2026-3301

CVE-2026-55255 HIGH POC
8.4 Jun 19

Cross-user flow execution in Langflow (< 1.9.1) lets any authenticated API-key holder run another user's flow by passing

Share

CVE-2026-48053 vulnerability details – vuln.today

This site uses cookies essential for authentication and security. No tracking or analytics cookies are used. Privacy Policy