طلب POST واحد إلى نموذج، فيشغّل الخادم شيفرتك
طلب POST واحد إلى نموذج. هذه هي كامل المسافة بين زائر مجهول وشيفرة تعمل على خادمك. الطلب أدناه هو الثغرة كلها، وهو يُطلَق فعلاً على مواقع حية. الثغرة CVE-2026-85097 رفع ملفات عشوائي دون مصادقة في Bricksforge، وهو إضافة شائعة لمنشئ المواقع Bricks. تتأثر كل الإصدارات حتى 3.1.8.9 ضمناً، وأُصلحت ابتداءً من 3.1.8.10. [1][2] تمنحها Wordfence، التي أسندت المعرّف، تقييم 9.8. وتسميها Patchstack، التي رصدت الاستغلال، 10.0. [1][2]
أُبلغت Bricksforge في 3 سبتمبر، وصارت التفاصيل علنية في 7 أكتوبر. [2] ورصدت Patchstack أول محاولة استغلال في اليوم نفسه، الساعة 21:47 بالتوقيت العالمي، من بنية آلية كانت تنتظر بوضوح. [1] لم أُعد إنتاج هذا على نظام حيّ. وما يلي مبني على تحليل Patchstack والشيفرة التي نشرتها، وتنبيه Wordfence، وسجل إصدارات المطوّر، ووثائق ووردبريس، قرأتها في 12 أكتوبر 2026. [1][2][3][4] المثير ليس أن رفع ملف أدى إلى تنفيذ شيفرة، بل أن الإضافة فحصت الملف بعناية واختُرقت رغم ذلك.
POST /wp-json/bricksforge/v1/form_submit HTTP/1.1
Host: victim.example
Content-Type: application/json
{
"temporaryFileUploads": [
{
"field": "form-field-file",
"file": {
"file": "/var/www/.../uploads/bricksforge/tmp/img.gif", // real GIF, passed the MIME check
"url": "https://victim.example/wp-content/uploads/shell.php", // attacker names the destination
"type": "image/gif"
}
}
]
}الخادم فحص البايتات، والمهاجم سمّى الملف
هذا هو التصميم الذي وثقت به Bricksforge. حين يرفق زائر ملفاً، ترفعه الإضافة إلى مجلد مؤقت وتفحص هناك نوع MIME، كما يجب، قبل أي شيء آخر. على GIF أن يكون GIF حقيقياً، وهذا الفحص يعمل. ثم فعل المطورون ما يبدو غير مؤذٍ: عند إرسال النموذج أخيراً، تركوا المتصفح يخبر الخادم أي ملف يقصد وأين ينبغي أن ينتهي.
هذا هو الخطأ كله، و«لا يتحقق من حقل url» يبخسه حقه. فحصت الإضافة خاصية واحدة للملف، محتواه، على حقل واحد. ثم اختارت اسم الملف النهائي وامتداده من حقل آخر يتحكم به المهاجم ولا يُعاد فحصه. فما فحصته وما تصرفت بناءً عليه ليسا الشيء نفسه. بايتات GIF لا تقول شيئاً عمّا إذا كان الملف الذي ستكتبه سيُسمّى shell.php، وعلى بيئة Apache مع mod_php فإن الـ.php في النهاية، لا ترويسة الصورة، هو ما يقرّر هل يشغّل الخادم الملف.
صورة صحيحة، منسوخة إلى ملف قابل للتنفيذ
لنمشِ في المرحلتين. أولاً الرفع. يطلب المهاجم nonce من نقطة دون مصادقة، ثم يرفع ملفاً هجيناً: ملف هو GIF صحيح ويحوي أيضاً سطر PHP. ولأنه صورة حقيقية، يُبلّغ mime_content_type بأنه image/gif، فيحطّ في المجلد المؤقت دون مساس. [1] حتى الآن فعلت الإضافة كل شيء بشكل صحيح. الملف على القرص صورة بالفعل.
ثم يُرسَل النموذج، وتقوم البيانات الوصفية بالعمل. يحمل JSON المُرسَل كائن file بحقلين مهمّين: file، المسار الخادمي لما جرى فحصه، وurl، إلى أين يذهب. الشيفرة أدناه هي المسار المصاب، مختصرة مما نشرته Patchstack. [1] يُؤخذ اسم الوجهة من حقل url، ويُمرَّر عبر basename، ويُستخدم اسماً تُنسَخ إليه البايتات التي جرى فحصها.
انظر ماذا يفعل هذا copy(). يأخذ الـGIF الحقيقي من المجلد المؤقت ويكتب بايتاته إلى مسار سمّاه المهاجم، ينتهي بـ.php، داخل جذر الويب. ترويسة الصورة تُبقي الملف GIF صحيحاً؛ وأما PHP بداخله فهو ما يشغّله الخادم أول مرة يُطلَب فيها ذلك الرابط. [1] لم يكن لفحص MIME فرصة، لأنه كان يجيب سؤالاً لا يطرحه أحد. أثبت أن الملف صورة. ولم يثبت أن الملف سيُخزَّن أو يُقدَّم كصورة.
// Bricksforge Pro Forms (condensed from the vulnerable path)
$file_path = $file_data['file']; // server path of the file that was MIME-checked
$file_url = $file_data['url']; // client-supplied, never re-validated
$attachment['guid'] = $file_url; // destination comes from the URL
$desired_filename = basename($attachment['guid']); // ...so from the attacker
$destination_path = $upload_path . '/' . $desired_filename;
copy($file_path, $destination_path); // GIF bytes written to an attacker-named .phpلماذا «نماذجنا بلا حقل رفع» لا ينقذك
الآن الجزء الذي ينبغي أن يقلقك حتى لو لم تحسب نفسك مكشوفاً. قد تظن أن هذا يخص فقط النماذج التي تقبل الرفع. سجل إصدارات Bricksforge نفسه يقول غير ذلك: العيب يطال كل موقع يعمل فيه Pro Forms، بما في ذلك النماذج التي لا تحوي أي حقول رفع إطلاقاً. [3] والسبب في الشيفرة. قائمة الملفات التي تُعالَج لا تُقرأ من رفع multipart حقيقي، بل من قيمة temporaryFileUploads في جسم JSON، الذي يكتبه المهاجم من الصفر. [1] نموذج تواصل نصي بسيط يكفي.
والـnonce ليس البوابة التي يبدو عليها. تكشف Bricksforge نقطة bricksforge_regenerate_nonce تمنح nonce جديداً لأي زائر مجهول، وهذا وحده يُبطل الغرض. وحتى بدونها، يولّد ووردبريس nonce نفسه لكل مستخدم غير مسجّل، لأنهم جميعاً يتشاركون user ID 0. [4] الـnonce يثبت أن الطلب جاء من صفحة، لا من شخص موثوق. تقول وثائق ووردبريس ذلك في سطر: لا ينبغي الاعتماد على الـnonce للمصادقة أو التفويض أو التحكم بالوصول. [4] وهذا المعالِج اتّكأ عليه لذلك بالضبط.
// The file list is read from the JSON body, not from a real multipart upload:
$file_uploads = isset($form_data['temporaryFileUploads'])
? json_decode($form_data['temporaryFileUploads'], true) // attacker-supplied
: [];
$merged_files = array_merge($file_uploads, $request->get_file_params());
// The gate in front of it:
// GET bricksforge_regenerate_nonce -> hands any anonymous visitor a nonce
// WordPress gives every logged-out user the SAME nonce (user ID 0)
// A nonce is CSRF hygiene. It was never authentication.الإصلاح يعيد اسم الملف إلى الخادم
يعيد الإصلاح القاعدة البديهية: الخادم، لا العميل، يقرّر أين يذهب الملف المرفوع وبأي اسم. تسمّي Patchstack وتنبيه Wordfence الإصدار 3.1.8.10 بناءً مصحَّحاً. [1][2] ويُدرج سجل إصدارات Bricksforge إصلاح رفع الملفات تحت 4.0.1 الصادر في 8 أكتوبر، ويقول إن التفاصيل الكاملة تتبع بعد الإفصاح. [3] حدّث إلى ما تمنحك رخصتك، لكن اعتبر 3.1.8.10 الحد الأدنى.
المبدأ تحت ذلك يعيش أطول من الإضافة. اسم الملف المخزَّن قرار أمني، فعلى الخادم أن يملكه: ولّد اسماً عشوائياً، ثبّت الامتداد وفق قائمة سماح، وخزّن خارج جذر الويب أو حيث لا يشغّل الخادم PHP. الكتلة أدناه هي هذا الشكل، لا فرق شيفرة المطوّر. وهناك أيضاً فخّ توزيع خاص بـBricksforge. فهو إضافة تجارية، ليست في دليل WordPress.org، تُحدَّث عبر رخصة. [3] ورخصة منتهية قد تُبقيك بصمت على بناء مصاب بينما تبدو لوحتك سليمة. «اشتريناه مرة» ليس «نحن مُرقَّعون».
// The fix, as a principle: the SERVER owns the name and extension, never the client.
$ext = 'bin'; // or an allowlisted image extension
$stored = wp_generate_uuid4() . '.' . $ext; // random name the attacker can't choose
$destination = $upload_path . '/' . $stored;
copy($validated_tmp_file, $destination);
// And decide execution by WRITE location, not by MIME: store outside the web root,
// or in a directory where the server is configured never to run PHP.كيف أبحث عن هذا في شيفرة لا علاقة لها بـBricksforge
هذا الخلل صنف، والصنف أقدم من ووردبريس: تحقّق من خاصية، وتصرّف وفق أخرى. متى فحص معالِج الرفع محتوى الملف لكنه ترك مدخل الطلب يختار اسم الملف أو امتداده أو مجلده، ظهرت الثغرة نفسها، سواء أكانت البيئة PHP أم Node أم Java. حين أراجع مسار رفع، لا أبدأ من فحص MIME الذي يفخر به الجميع، بل أبدأ من الكتابة.
أجد كل مصبّ يضع بايتات على القرص، ثم أتتبّع رجوعاً إلى مصدر اسم الوجهة. الـgrep أدناه يعطيني المرشحين؛ أما العمل الحقيقي فقراءة كل واحد. ولكل كتابة أسأل سؤالاً واحداً، وهو نفسه الذي كان سيمسك CVE-2026-85097 في المراجعة: هل الخاصية التي فحصتها تقرّر الخاصية التي تُنفَّذ؟ إن كانت البايتات مفحوصة بينما يسمّي الملفَ حقلٌ منفصل يتحكم به العميل، فالفحص زينة.
# Smell to hunt in any upload handler:
# the stored filename, extension or directory derived from request input.
rg -n "basename\(|get_param|\$_POST|\$_REQUEST|json_decode" --glob '*.php'
# Then read each write sink by hand: move_uploaded_file, copy, file_put_contents, rename.
# The one question that confirms the bug:
# does the property you VALIDATED (the bytes, the MIME) decide the property
# that gets EXECUTED (the extension, the directory)?
# If a separate, client-controlled field names the sink, the check is decoration.هل هذا حريقك؟ فقط إن كنت تشغّل Bricksforge، وحينها نعم
لنكن صريحين بشأن الحجم. لدى Bricksforge نحو 6000 تثبيت نشط بحسب Patchstack، فهذا ليس حدثاً بحجم الإنترنت. [1] إن لم تشغّل Bricksforge، أغلق هذه الصفحة. وإن شغّلته، فالنافذة ليست نظرية. سجّلت Patchstack استغلالاً من 63 عنوان IP فريداً بدأ خلال ساعات من الإفصاح، مع تنكّر 95% من الطلبات الخبيثة لحمولة PHP في صورة GIF أو PNG، وعناقيد صغيرة تجرّب عشرات صيغ الامتداد لتتجاوز المرشحات الساذجة. [1]
يستحق التملّص النظر، لأنه يدلّك على ما تبحث عنه بـgrep. جرّب المهاجمون امتدادات PHP بديلة مثل .php7 و.phtml، وألاعيب حالة الأحرف مثل .PHp، وصيغاً مُرمَّزة في الرابط مثل %2ephp، وكلها تستهدف الـcopy() نفسه. [1] والملفات التي حاولوا إسقاطها اتّبعت أنماطاً مثل login_admin_*.php في دليل الرفع، بما فيه مجلد Bricksforge المؤقت. [1] وحتى 12 أكتوبر ليست الثغرة بعد في فهرس CISA للثغرات المستغلّة المعروفة، ودرجة EPSS لها دون واحد بالمئة، لكن أياً منهما لا يعني الهدوء. [2] بل يعني الوقت المبكر. فالـKEV مؤشر متأخر؛ والمصائد مؤشر مبكر، والمصائد تضجّ فعلاً.
إذاً الشروط الثلاثة لأن يصير هذا طوارئك: تشغّل Bricksforge، بإصدار دون 3.1.8.10، قابل للوصول من الإنترنت. فإن صحّت الثلاثة، فالترقيع خطوة أولى لا خط النهاية، لأن ملف PHP كان يمكن كتابته ربما كُتب ونُفِّذ بالفعل. ولصاحب الموقع، السؤال ليس فقط «هل حدّثنا» بل «هل وصل أحد إلى نقطة form_submit بحمولة ملف قبلنا». وهذا الجواب يسكن في سجلاتك ودليل الرفع، لا في شاشة الإضافات.
ما الذي تفعله قبل أن تغلق هذه الصفحة
هذا هو الترتيب الذي كنت سأعمل به. رقّع أولاً، لأن نافذة الاستغلال تقاس بالساعات لا بالأسابيع. [1] لكن إن كان إصدار مصاب مواجهاً للإنترنت، فافترض أن أحداً حاول، وابحث قبل أن تعلن النصر. الرقعة توقف الرفع التالي؛ ولا تفعل شيئاً حيال شِل زُرع الأسبوع الماضي.
القائمة أدناه ملموسة. توقيع السجل الذي تبحث عنه هو ما لا يستطيع الهجوم تفاديه: طلب إلى نقطة إرسال النموذج يحمل جسم temporaryFileUploads حيث يشير مسار الملف إلى صورة لكن حقل url ينتهي بامتداد PHP. هذا التنافر هو بصمة الثغرة، ويصعب تزييفه ببراءة.
Patch and verify (in this order):
[ ] Find every site running Bricksforge; confirm the version actually LOADED.
[ ] Update to 3.1.8.10+ (3.x branch) or 4.0.1+. Bricksforge is license-gated and
not on WordPress.org, so a lapsed license can silently block the update.
[ ] Cannot update now? Block at the WAF / server: POST to
/wp-json/bricksforge/v1/form_submit and the admin-ajax action
bricksforge_form_submit carrying a temporaryFileUploads body.
If a vulnerable version faced the internet, assume compromise and hunt:
[ ] Grep uploads for unexpected .php, especially
/wp-content/uploads/bricksforge/tmp/ and names like login_admin_*.php.
[ ] Audit for hidden admin accounts by querying the database directly,
and review the must-use (mu-plugins) directory.
[ ] In access logs, look for form_submit requests whose file path is an image
but whose url field ends in a PHP extension.
[ ] Rotate secrets reachable from PHP: database, API keys, SMTP.الجزء الذي لا تغطّيه «نحن نفحص عمليات الرفع» أبداً
تمثّل CVE-2026-85097 درساً أنيقاً في سبب أن «نحن نفحص عمليات الرفع» ليس جواباً على «هل الرفع آمن». فحصت الإضافة الشيء الذي كان فحصه سهلاً، ووثقت بالشيء الذي كان مهماً. تلك الفجوة، بين الخاصية التي تفحصها والخاصية التي تُنفَّذ، هي حيث يسكن عدد مفاجئ من ثغرات الرفع، ولا تراها من رقم الإصدار ولا من علامة الماسح الخضراء. عليك أن تقرأ المسار من الطلب إلى الكتابة.
تتبّع ذلك المسار، من طلب مجهول عبر إطار العمل إلى السطر الدقيق الذي يسمّي ملفاً على القرص، هو جوهر تقييم تطبيق الويب، ورائحة «افحص شيئاً وتصرّف وفق آخر» في هذه الثغرة أبحث عنها في شيفرة لا علاقة لها بووردبريس. فإن كان لديك نماذج أو عمليات رفع أو أي مسار يحوّل مدخلاً خارجياً إلى ملفات على خادمك، فهذا هو الحد الذي سأفحصه لك. أخبرني بما تصدره ونحدّد النطاق.
المصادر
هل تحتاج إلى هذا الفحص في منتجك؟
اختبار اختراق الويب وواجهات API