
تدقيق ثابت للشيفرة البرمجية للثغرة CVE-2024-57521، وهي حقن SQL بعد المصادقة في وحدة المولّد في RuoYi-Vue، مع تحليل من المصدر إلى النقطة الحرجة وإثبات مفهوم لتجاوز المرشّح %0b.
الشكل 1: مخطط تدقيق تدفق بيانات ثغرة حقن SQL CVE-2024-57521
هذا المستودع مخصص لأغراض البحث الأمني والتعليم فقط. يُرجى عدم استخدام التقنيات الواردة في هذا المستند لتنفيذ هجمات غير مصرح بها. تم إجراء جميع الاختبارات في بيئة تدقيق شيفرة ثابتة محلية.
ruoyi-generator)ruoyi-generator/src/main/java/com/ruoyi/generator/controller/GenController.javaالشيفرة الرئيسية:
@PostMapping("/createTable")
public AjaxResult createTableSave(@RequestParam("sql") String sql, @RequestParam("tplWebType") String tplWebType)
{
try
{
SqlUtil.filterKeyword(sql); // 在进入业务前调用过滤器
التحليل: يمكن لمسؤول الخلفية تمرير أي عبارة إنشاء جدول لقاعدة البيانات عبر معامل sql في طلب HTTP POST. نقطة الدخول لا تفرض قيودًا كافية على إدخال المستخدم، بل تُمرّره مباشرة إلى الفلتر اللاحق.
دليل لقطة الشاشة:

· موقع الملف: ruoyi-common/src/main/java/com/ruoyi/common/utils/sql/SqlUtil.java
كود العيب الجوهري:
// 第16行:黑名单定义,注意关键词带有【尾随空格】
public static String SQL_REGEX = "\u0008|%0A|and |extractvalue|updatexml|sleep|information_schema|exec...";
// 第61-66行:过滤逻辑
String normalizedValue = value.replaceAll("\\p{Z}|\\s", ""); // 先清空了输入里的所有空格
String[] sqlKeywords = StringUtils.split(SQL_REGEX, "\\|");
for (String sqlKeyword : sqlKeywords)
{
if (StringUtils.indexOfIgnoreCase(normalizedValue, sqlKeyword) > -1)
{
throw new UtilException("请求参数包含敏感关键词" + sqlKeyword + ",可能存在安全风险");
}
}
تحليل الثغرة: ينفّذ الفلتر أولاً replaceAll("\\p{Z}|\\s", "") لحذف جميع أحرف المسافات البيضاء من المدخل. غير أن الكلمات المفتاحية في القائمة السوداء SQL_REGEX (مثل "and " و"select ") تتضمن مسافة لاحقة. وهذا يجعل المطابقة تفشل حتماً، إذ يكفي أن يستخدم المهاجم %0b (علامة التبويب العمودية) بدلاً من المسافة بعد الكلمة المفتاحية ليتجاوز فحص القائمة السوداء تماماً.
أدلة لقطات الشاشة:
· موقع الملف: ruoyi-generator/src/main/resources/mapper/generator/GenTableMapper.xml
الكود الأساسي:
<update id="createTable">
${sql}
</update>
تحليل: في MyBatis، يقوم ${} بدمج السلاسل النصية مباشرةً، بدلاً من استخدام الاستعلام المُعد الآمن #{}. مما يؤدي إلى إرسال SQL خبيث تجاوز الفلاتر إلى قاعدة البيانات لتنفيذه. هذه ثغرة نموذجية من نوع "تصفية بالقائمة السوداء + دمج غير آمن".
أدلة لقطة الشاشة:
· الطريقة: يستخدم المهاجم %0b (حرف الجدولة العمودية في MySQL، والذي يقع ضمن نطاق مطابقة \s) بدلاً من المسافات في جملة SQL.
· مبدأ الاستكشاف: استخدام IF(<الشرط>, 1, 1/0) كأداة كشف للحقن الأعمى المنطقي.
· إذا كان الشرط خاطئاً: يتم إطلاق خطأ القسمة على صفر، ويعيد الخادم HTTP 500.
· إذا كان الشرط صحيحاً: لا يحدث خطأ ويعود الوضع طبيعياً.
· طريقة الاستخراج: يمكن استخدام طريقة التنصيف لتخمين بيانات قاعدة البيانات بتاً بتاً.
هذه الثغرة هي تجاوز نموذجي ناتج عن "إصلاح غير كامل". أدخل المطورون آلية القائمة السوداء عند إصلاح حقن SQL مماثل سابقاً، لكنهم أغفلوا أن القائمة السوداء تعتمد على المسافات، وأن المنطق المسبق لإزالة المسافات يُفسد شروط مطابقة القائمة السوداء، مما أدى في النهاية إلى تجاوز جديد (CVE-2024-57521).
· إلغاء آلية القائمة السوداء: لا تعتمد على القائمة السوداء، فآلية القائمة البيضاء هي الأساس الآمن.
· استخدام الاستعلام المُعد: غيّر ${sql} في MyBatis إلى أسلوب الاستعلام المُعد #{}. إذا كان لا بد من تمرير SQL ديناميكياً (مثل إنشاء الجداول أو Order By)، فيجب استخدام تحليل صارم لشجرة التركيب المجردة (AST) أو تحقق صارم من المعاملات.
· تحسين الفلتر: إذا كان لا بد من استخدام القائمة السوداء، فيجب إزالة المسافات اللاحقة من كلمات القائمة السوداء المفتاحية، وإجراء توحيد قياسي قبل المقارنة (مثل التحويل الموحد إلى أحرف صغيرة، واستبدال %0b وغيرها بمسافات).
· NVD - CVE-2024-57521 · مستودع RuoYi-Vue على Gitee
poc/poc_boolean.py (ملاحظة: يُقترح لاحقاً تغيير اسم الملف إلى poc_error_based.py ليتوافق مع المنطق الفعلي)extractvalue() لإطلاق خطأ XPath، واستخراج البيانات عبر مطابقة النمط XPATH syntax error: '~...~' بتعبير نمطي. يُستخدم /**/ في الـ Payload بدلاً من المسافات لتجاوز تصفية القائمة السوداء.
CREATE//table//{随机表名}//as//SELECT/**/extractvalue(1,concat(0x7e,({查询语句}),0x7e))
ملاحظات الاختبار: في بيئة اختبار RuoYi المُعدّة محليًا، تم التحقق بنجاح من إمكانية تجاوز WAF باستخدام %0b ومن جدوى حقن الأخطاء عبر extractvalue، وقد استخرج السكربت بثبات إصدار MySQL (5.7.26)، واسم قاعدة البيانات الحالية (ry)، ومستخدم اتصال قاعدة البيانات (root@localhost).