
HTTP3ONSTEROIDS - بحث حول CVE-2023-25950 حيث يفشل تنفيذ HAProxy الخاص ببروتوكول HTTP/3 في حظر اسم حقل ترويسة HTTP مشوّه.

إن تنفيذ HAProxy لبروتوكول HTTP/3 لا ينجح في حظر اسم حقل ترويسة HTTP غير صالح، وعند نشره أمام خادم يعالج هذه الترويسة غير الصالحة بشكل غير صحيح، قد يُستخدم لشن هجوم تهريب طلب/استجابة HTTP (HTTP request/response smuggling). قد يتمكن مهاجم عن بُعد من تغيير طلب مستخدم شرعي. ونتيجة لذلك، قد يحصل المهاجم على معلومات حساسة أو يتسبب في حالة حرمان من الخدمة (DoS).
https://jvn.jp/en/jp/JVN38170084/
من أفضل الممارسات قبل البدء في أي بحث حول ثغرات CVE هي قراءة وصف الثغرة أولاً ثم التحقق من توفر الالتزامات (commits) الخاصة بالتصحيح.
في حالتي، كان الالتزام متاحًا، ومن الواضح أن مطوري HAProxy نسوا تضمين فحوصات RFC 9114 4.1.2. الطلبات والاستجابات غير الصالحة أثناء تحليل الترويسات القياسية في تنفيذ HTTP/3.
ارجع إلى التزام التصحيح بالأسفل.
--- a/src/h3.c
+++ b/src/h3.c
@@ -352,7 +352,27 @@ static ssize_t h3_headers_to_htx(struct qcs *qcs, const struct buffer *buf,
//struct ist scheme = IST_NULL, authority = IST_NULL;
struct ist authority = IST_NULL;
int hdr_idx, ret;
- int cookie = -1, last_cookie = -1;
+ int cookie = -1, last_cookie = -1, i;
+
+ /* RFC 9114 4.1.2. Malformed Requests and Responses
+ *
+ * A malformed request or response is one that is an otherwise valid
+ * sequence of frames but is invalid due to:
+ * - the presence of prohibited fields or pseudo-header fields,
+ * - the absence of mandatory pseudo-header fields,
+ * - invalid values for pseudo-header fields,
+ * - pseudo-header fields after fields,
+ * - an invalid sequence of HTTP messages,
+ * - the inclusion of uppercase field names, or
+ * - the inclusion of invalid characters in field names or values.
+ *
+ * [...]
+ *
+ * Intermediaries that process HTTP requests or responses (i.e., any
+ * intermediary not acting as a tunnel) MUST NOT forward a malformed
+ * request or response. Malformed requests or responses that are
+ * detected MUST be treated as a stream error of type H3_MESSAGE_ERROR.
+ */
TRACE_ENTER(H3_EV_RX_FRAME|H3_EV_RX_HDR, qcs->qcc->conn, qcs);
@@ -416,6 +436,14 @@ static ssize_t h3_headers_to_htx(struct qcs *qcs, const struct buffer *buf,
if (isteq(list[hdr_idx].n, ist("")))
break;
+ for (i = 0; i < list[hdr_idx].n.len; ++i) {
+ const char c = list[hdr_idx].n.ptr[i];
+ if ((uint8_t)(c - 'A') < 'Z' - 'A' || !HTTP_IS_TOKEN(c)) {
+ TRACE_ERROR("invalid characters in field name", H3_EV_RX_FRAME|H3_EV_RX_HDR, qcs->qcc->conn, qcs);
+ return -1;
+ }
+ }
+
if (isteq(list[hdr_idx].n, ist("cookie"))) {
http_cookie_register(list, hdr_idx, &cookie, &last_cookie);
continue;
المستودعات - haproxy-2.7.git/commit
بالأسفل، يمكنك العثور على الكود الذي يوضح غياب تنقية أسماء الترويسات في تنفيذ معالجة الترويسات القياسية على HAProxy 2.7.0.
/*
src/h3.c
lines: 413 - 428
*/
/* now treat standard headers */
hdr_idx = 0;
while (1) {
if (isteq(list[hdr_idx].n, ist("")))
break;
if (isteq(list[hdr_idx].n, ist("cookie"))) {
http_cookie_register(list, hdr_idx, & cookie, & last_cookie);
continue;
}
if (!istmatch(list[hdr_idx].n, ist(":")))
htx_add_header(htx, list[hdr_idx].n, list[hdr_idx].v);
++hdr_idx;
}
بالأسفل، يمكنك العثور على الكود المستخدم للتحقق من صلاحية اسم الترويسة.
يبدأ الكود بحلقة for تتكرر عبر كل حرف من اسم حقل الترويسة. تعمل الحلقة من i = 0 إلى i < list[hdr_idx0.n.len]، حيث list هي مصفوفة أو بنية تحتوي على معلومات الترويسة، وhdr_idx هو فهرس يمثل الترويسة المحددة التي يتم فحصها.
داخل الحلقة، يستخرج الكود الحرف الحالي c من اسم حقل الترويسة.
list[hdr_idx].n.ptr[i] يصل إلى الحرف في الموضع i من اسم حقل الترويسة.
يحتوي الجزء التالي من الكود على عبارة if. تتحقق مما إذا كان الحرف الحالي c يحقق أحد الشرطين:
(uint8_t)(c - 'A') < 'Z' - 'A': يتحقق هذا الشرط مما إذا كان الحرف حرفًا كبيرًا (من A إلى Z) عن طريق طرح 'A' من c وتحويل النتيجة إلى uint8_t. إذا كانت النتيجة أقل من الفرق بين 'Z' و'A'، فإن الحرف هو حرف كبير.!HTTP_IS_TOKEN(c): يتحقق هذا الشرط مما إذا كان الحرف حرف رمز HTTP صالحًا. يعمل HTTP_IS_TOKEN أولاً عن طريق التحقق مما إذا كان اسم الترويسة يحتوي على أي رموز. الرمز (token) هو سلسلة من الأحرف التي ليست أحرفًا محجوزة في بروتوكول HTTP. الأحرف المحجوزة هي أحرف لها معنى خاص في بروتوكول HTTP، مثل : و/ و? و#./*
src/h3.c
lines: 439 - 445
*/
for (i = 0; i < list[hdr_idx].n.len; ++i) {
const char c = list[hdr_idx].n.ptr[i];
if ((uint8_t)(c - 'A') < 'Z' - 'A' || !HTTP_IS_TOKEN(c)) {
TRACE_ERROR("invalid characters in field name", H3_EV_RX_FRAME | H3_EV_RX_HDR, qcs -> qcc -> conn, qcs);
return -1;
}
}
يعمل المختبر بالكامل على Docker. يمكنك تشغيل المختبر باستخدام الأوامر التالية:
cd /labdocker-compose up --buildيُرجى ملاحظة أن بناء Docker سيستغرق 15-20 دقيقة لإكماله.
ومع ذلك، قبل تشغيل المختبر، يجب إجراء بعض التغييرات في الإعدادات:
/lab/haproxy/conf/haproxy.cfg
...
default_backend api_server
backend api_server
balance roundrobin
server api_server [YOUR-LOCAL-IPv4]:8080 # replace with local IPv4
/etc/hosts
[YOUR-LOCAL-IPv4] foo.com
/lab/docker-compose.yml
يمكنك الاختيار بين النسخة غير المصححة والنسخة المصححة عن طريق تغيير قيمة الوسيط إلى 'vuln' أو 'patched' لإجراء اختبارك عليها.
...
args:
- haproxy_version=patched || vuln
...
وأخيرًا، استورد شهادة minica.crt في متصفحك.
⚠️ يُرجى تشغيل المختبر في بيئة لينكس.
إرسال طلب curl التالي:
curl --http3 -H "foooooo\r\n: barr" -iL -k https://192.168.1.104/استجابة النسخة غير المصححة:
HTTP/3 200
server: Werkzeug/2.3.6 Python/3.8.17
date: Sat, 12 Aug 2023 13:10:52 GMT
content-type: text/html; charset=utf-8
content-length: 76
alt-svc: h3=":443";ma=900;
Host: 192.168.1.104
User-Agent: curl/8.1.2-DEV
Accept: */*
Foooooo\R\N: barr <-- Malformed header
استجابة النسخة المصححة:
curl: (56) HTTP/3 stream 0 reset by server
بناءً على النتائج أعلاه، تشير الاستجابات المقابلة إلى أن النسخة غير المصححة من HAProxy سمحت بتمرير بادئة \r\n إلى الخادم الخلفي، بينما أسقطت النسخة المصححة الاتصال بين العميل وHAProxy.
يمكن للمهاجم شن هجوم تهريب طلب HTTP (HTTP Request Smuggling) بناءً على سلوك الخادم الخلفي وكيفية تعامل الخادم الخلفي مع الترويسة غير الصالحة. من وجهة نظري، أكثر ما يثير القلق هو أن المهاجم قد يستغل الثغرة المذكورة أعلاه لتنفيذ هجوم حرمان من الخدمة (DoS).
https://jvn.jp/en/jp/JVN38170084/
https://github.com/haproxytechblog/haproxy-2.6-http3
https://www.haproxy.com/blog/how-to-enable-quic-load-balancing-on-haproxy
https://git.haproxy.org/
https://github.com/jsha/minica
https://curl.se/docs/http3.html