
وصفة OpenRewrite تكتشف وتصلح قمع ترويسات Spring Security (CVE-2026-22732) من خلال تحديد سوء استخدام ترويسة Content-Length وتوليد إعداد كتابة الترويسات المبكرة.
وصفة OpenRewrite تكتشف الكود المعرّض لـ CVE-2026-22732، وهو خلل في Spring Security حيث يؤدي تعيين Content-Length عبر إحدى طرق الاستجابة الثلاث إلى تجاوز OnCommittedResponseWrapper الخاص بـ Spring Security. نظرًا لأن الغلاف لا يرى الترويسة أبدًا، فإن onResponseCommitted() لا يتم استدعاؤه أبدًا، ويتم إسقاط ترويسات الأمان المضافة بتكاسل (X-Frame-Options, X-Content-Type-Options, Cache-Control, إلخ) بصمت.
المحفزات الفعلية، المؤكدة ضد Spring Security 6.4.12 الضعيف مع Spring Boot 3.4.3 / Tomcat المدمج:
Content-Length الخاص بـ Servlet عبر التحميلات الزائدة المتجاوزة للغلاف
response.setHeader("Content-Length", "42");
response.setIntHeader("Content-Length", 42);
response.addIntHeader("Content-Length", 42);
هذه التحميلات الزائدة الثلاثة غير مُعاد تعريفها في OnCommittedResponseWrapper. تكمل عمليات كتابة النص اللاحقة الطول المُعلن ويقوم الحاوية بالالتزام دون تشغيل كاتب الترويسات الكسول.
Content-Length الخاص بـ WebFlux عبر HttpHeaders
serverHttpResponse.getHeaders().set("Content-Length", "42");
serverHttpResponse.getHeaders().add("Content-Length", "42");
serverHttpResponse.getHeaders().setContentLength(42L);
التزامات استجابة WebFlux غير المشروطة
serverHttpResponse.writeWith(Mono.just(dataBuffer));
serverHttpResponse.writeAndFlushWith(publisher);
serverHttpResponse.setComplete();
الوصفة مشروطة بوجود Spring Security — لا تُصدر أي شيء في الملفات التي لا تشير إلى أي نوع org.springframework.security.* — وبإصدارات Spring Security المتأثرة. وفقًا لنشرة Spring الأمنية المنشورة في 2026-03-19، فإن النطاقات المتأثرة وإصدارات الإصلاح هي:
| السلسلة | المتأثر | المُصلح |
|---|---|---|
| 5.7.x | 5.7.0 – 5.7.21 | 5.7.22 (Enterprise) |
| 5.8.x | 5.8.0 – 5.8.23 | 5.8.24 (Enterprise) |
| 6.3.x | 6.3.0 – 6.3.14 | 6.3.15 (Enterprise) |
| 6.4.x | 6.4.0 – 6.4.14 | 6.4.15 (Enterprise) |
| 6.5.x | 6.5.0 – 6.5.8 | 6.5.9 (OSS) |
| 7.0.x | 7.0.0 – 7.0.3 | 7.0.4 (OSS) |
المشاريع التي تحل إصدار Spring Security عند أو فوق الإصلاح في سلسلتها (أو على أي سلسلة مستقبلية بعد 7.0 / 6.5) تُعتبر غير متأثرة ولا تتلقى أي علامات لكل نقطة تسريب أو لكل ملف. المشاريع التي لا يمكن حل إصدارها تنتقل إلى الكشف المعتاد القائم على الأنماط بحيث يميل الماسح إلى الإبلاغ عن نتيجة لا يمكنه دحضها. لا يزال جدول بيانات SpringSecurityVersionByProject يسجل الإصدار الذي تم حله ويضع علامة على كل مشروع كمتأثر أو غير متأثر، حتى تتمكن من تدقيق ما تمت تصفيته.
تبدو هذه خطيرة ولكن يتم تتبعها بواسطة الغلاف، لذلك تتم كتابة ترويسات الأمان قبل التزام الاستجابة:
| الكود | لماذا هو آمن |
|---|---|
response.setContentLength(int) / setContentLengthLong(long) | مُعاد تعريفه — يسجل الغلاف الطول المُعلن ويشغل onResponseCommitted() عند اكتمال النص. |
response.flushBuffer() | مُعاد تعريفه — يستدعي doOnResponseCommitted() قبل super.flushBuffer(). |
response.getOutputStream().write(..) / flush() / close() | يُرجع SaveContextServletOutputStream؛ كل كتابة/تفريغ/إغلاق يشغل doOnResponseCommitted() قبل التفويض. |
response.getWriter().write(..) / print(..) / println(..) / flush() / close() | يُرجع SaveContextPrintWriter؛ نفس النمط. |
response.addHeader("Content-Length", v) | حالة خاصة في الغلاف — يتم توجيهها عبر setContentLength(long). |
يدّعي نقطة نهاية /vuln/flush في عرض Semgrep أن flushBuffer() هو المحفز، ولكن على Spring Security 6.4.12 الضعيف، تُرجع الاستجابة فعليًا جميع ترويسات الأمان الستة. المحفزات الحقيقية في العرض هي استدعاءات setIntHeader("Content-Length", ...) في /vuln/stream و /vuln/content-length.
شغّل هذه الوصفة:
| الوصفة | الغرض |
|---|---|
io.moderne.recipe.cve202622732.FindSpringSecurityHeaderSuppression | تشغيل كل اكتشاف وإصدار جدول تقرير الإصدار |
المُجمِّع أعلاه يتكون من وصفتين أصغر. يمكنك استدعاؤهما بشكل فردي إذا كنت تريد اكتشافًا واحدًا فقط.
| الوصفة | الغرض |
|---|---|
io.moderne.recipe.cve202622732.FindHttpResponseContentLengthHeader | تدفق التلوث للحرف "Content-Length" الذي يصل إلى setHeader / setIntHeader / addIntHeader (servlet) أو HttpHeaders.set / add (WebFlux) |
io.moderne.recipe.cve202622732.FindHttpResponseContentLengthOrFlushBuffer | التزامات WebFlux غير المشروطة: writeWith, writeAndFlushWith, setComplete, HttpHeaders.setContentLength |
شغّل هذه الوصفة:
| الوصفة | الغرض |
|---|---|
io.moderne.recipe.cve202622732.FixSpringSecurityHeaderSuppression | اختيار أرخص معالجة يمكن لكل مشروع تنفيذها فعليًا |
تشغّل خطوتين بالترتيب.
1. الترقية إلى الإصلاح على سلسلة المشروع الخاصة. نشر Spring Security الإصلاح كإصدار 6.5.9
و 7.0.4 على Maven Central. كل ترقية مشروطة بشرط مسبق FindAffectedSpringSecuritySeries،
لأن UpgradeDependencyVersion يتحقق فقط من أن هدفه أحدث — إذا طُلب منه
الانتقال إلى 7.0.4 فسيسحب مشروع 5.8 عبر إصدارين رئيسيين عن طيب خاطر.
2. إضافة تكوين كتابة ترويسات مبكر لأي شيء لم تستطع الخطوة 1 إصلاحه. يولّد هذا
فئة @Configuration واحدة لكل مشروع:
@Bean
public static BeanPostProcessor eagerHeaderWriterFilterBeanPostProcessor() {
return new BeanPostProcessor() {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof HeaderWriterFilter) {
((HeaderWriterFilter) bean).setShouldWriteHeadersEagerly(true);
}
return bean;
}
};
}
كتابة الترويسات مسبقًا تجعل من غير المهم ما إذا كان الغلاف يلاحظ الالتزام أم لا، لذا
يغلق هذا كل نقطة تسريب في المشروع دفعة واحدة — بما في ذلك تلك التي لا يمكن لتحليل التلوث الوصول إليها،
مثل تدفق Map تحت "القيود المعروفة". يرى BeanPostProcessor عوامل التصفية المبنية بواسطة
DSL HttpSecurity لأن AutowireBeanFactoryObjectPostProcessor يهيئها عبر
مصنع الفول. إصلاحان منشوران بشكل مستقل لهذا CVE يستخدمان هذا الشكل بالضبط
(hmcts/idam-web-public، و
فروع armory-io الخاصة بـ Spinnaker عبر ObjectPostProcessor المكافئ).
الخطوة 2 هي ما يغطي المشاريع التي لا يمكن للخطوة 1 مساعدتها:
| الموقف | لماذا لا تعمل الترقية |
|---|---|
| 5.7, 5.8, 6.3, 6.4 | الإصلاح يُشحن لمشتركي Spring Enterprise فقط — وليس على Maven Central |
| 6.0 - 6.2 | لم يُصدر أي إصلاح على تلك السلاسل |
| الإصدار المُدار بواسطة BOM مستورد | لا يوجد شيء مُعلن محليًا لتقوم الترقية بتعديله |
الصف الأخير ليس حالة هامشية. nla/bamboo يحل 7.0.3 — سلسلة لديها إصلاح
مفتوح المصدر — بالكامل من Spring Boot BOM، لذا فإن فحص الإصدار وحده سيتخطاه تحت كلتا الخطوتين
ويتركه عرضة للخطر. لذلك AddEagerHeaderWriterConfiguration يؤجل فقط إلى الترقية
عندما يكون لدى المشروع إصلاح مفتوح المصدر متاح و يعلن إصدارًا خاصًا به.
تُوضع الفئة المُولَّدة بجوار فئة @EnableWebSecurity حيثما توجد، مع الرجوع
إلى @SpringBootApplication ثم أي @Configuration، لذا تهبط دائمًا في مكان يصل إليه
فحص المكونات. المشاريع التي تكتب الترويسات مبكرًا بالفعل، أو التي تورد
OnCommittedResponseWrapper مُصححًا (كما يفعل jogetworkflow/jw-community)،
تُترك دون تغيير.
| الوصفة | الغرض |
|---|---|
io.moderne.recipe.cve202622732.UpgradeSpringSecurityToPatchedVersion | ترقية مشروطة بالسلسلة إلى 6.5.9 / 7.0.4 |
io.moderne.recipe.cve202622732.AddEagerHeaderWriterConfiguration | التكوين المُولَّد، بمفرده |
io.moderne.recipe.cve202622732.FindAffectedSpringSecuritySeries | وضع علامة على المشاريع على سلسلة متأثرة واحدة؛ الشرط المسبق للترقية |
طُبِّق على semgrep/cve-2026-22732-demo على
Spring Security 6.4.12 الضعيف، ضد Tomcat المدمج. يؤكد HeaderVerificationTest الخاص به
أن ترويسات الأمان مفقودة، لذا فإن الإصلاح الناجح يجعله يفشل: