Kısaca. BookIt'in 2.6.0.5 ve önceki sürümlerinde, oturum açmamış bir ziyaretçi randevu isteğine tek bir alan ekleyerek ödeme yapmadan randevusunu “ödendi” olarak kaydettirebiliyordu.
- Açık CVE-2026-110900 numarasıyla yayımlandı (CVSS 5.3, yetki kontrolü eksikliği). 15 Ekim 2026'da çıkan 2.6.0.6 sürümünde kapatıldı.
- Sorun ödeme sağlayıcısında değil, eklentinin kendi kodundaydı: ödeme durumunu sunucu belirlemesi gerekirken istekte gelen değer olduğu gibi veritabanına yazılıyordu.
- BookIt kullanıyorsanız 2.6.0.6 ya da sonraki bir sürüme güncelleyin ve “ödendi” görünen randevuları ödeme sağlayıcınızın kayıtlarıyla karşılaştırın.
BookIt, kuaför, klinik, danışmanlık bürosu gibi randevuyla çalışan işletmelerin WordPress sitelerine kurduğu bir randevu ve takvim eklentisi; 4.000'den fazla sitede etkindir. Ziyaretçi takvimden bir saat seçer, bilgilerini girer ve hizmet ücretliyse Stripe, PayPal ya da yerinde ödeme gibi bir yöntemi seçer. Ekim 2026'nın ilk haftasında bu eklentinin herkese açık randevu ucunu kendi bilgisayarımdaki deneme kurulumunda incelerken, ödeme durumunun kimin elinde olduğuna dair bir boşluk buldum ve Patchstack'in açık bildirim programı üzerinden üreticiye bildirdim. Bu yazıda açığın kod düzeyinde ne olduğunu, nasıl kapatıldığını ve site sahiplerinin ne yapması gerektiğini anlatıyorum.
Açık tam olarak neydi?
Randevu formu gönderildiğinde tarayıcı, WordPress'in admin-ajax.php adresine bookit_book_appointment adlı bir istek yollar. Bu uç, randevu almak için hesap gerekmediğinden oturum açmamış ziyaretçilere bilerek açık bırakılmıştır. İstekte hizmet, personel, saat, ad, e-posta, telefon ve ödeme yöntemi gibi alanlar bulunur. Formun kendisi ödeme durumu diye bir alan göndermez; ama sunucu, istekte payment_status adlı bir alan gelirse onu reddetmiyor, randevuya bağlı ödeme kaydına doğrudan yazıyordu.
Sonuç olarak ziyaretçi, normal randevu isteğinin sonuna payment_status=complete eklediğinde ödeme kaydı “tamamlandı” olarak oluşuyordu. Bu sırada hiçbir ödeme sağlayıcısına istek gitmiyordu; kart çekilmiyor, para el değiştirmiyordu. Site sahibi yönetim panelindeki randevu listesinde ise bu randevuyu ödenmiş olarak görüyordu.
Kod nerede hata yapıyordu?
Hata, birbirini tamamlayan üç küçük kararın birleşmesinden doğuyordu. Birincisi, uç oturum açmamış kullanıcılara açık kaydediliyor (son parametre true):
// includes/classes/AjaxActions.php
self::addAction( 'bookit_book_appointment', array( AppointmentController::class, 'save' ), true );
İkincisi, gelen veriyi temizleyen yardımcı fonksiyon, kural listesinde bulunmayan alanları atmak yerine yalnız metin temizliğinden geçirip olduğu gibi bırakıyor:
// includes/helpers/CleanHelper.php
foreach ( $data as $key => $value ) {
$value = sanitize_text_field( $value );
if ( ! array_key_exists( $key, $rules ) ) {
$data[ $key ] = $value; // kuralı olmayan alan da korunuyor
continue;
}
...
payment_status bu kural listesinde yoktu. Denetleyici randevunun kendi durumunu “beklemede” olarak zorluyordu ama ödeme durumuna hiç dokunmuyordu. Üçüncüsü, ödeme kaydı oluşturulurken bu değer veritabanına aktarılıyordu:
// includes/classes/database/Appointments.php (2.6.0.5)
'status' => ( ! empty( $data['payment_status'] ) ) ? $data['payment_status'] : Payments::$defaultStatus,
Kodun niyeti anlaşılır: ücretsiz hizmetlerde eklenti ödeme durumunu kendisi “tamamlandı” yapıyor ve bu satır o değeri taşımak için yazılmış. Sorun, aynı yolun istekten gelen değere de açık olmasıydı. Güvenlik açısından karar şu soruya bağlanıyor: “Bu randevu ödendi mi?” sorusunun cevabını kim veriyor? 2.6.0.5'te cevabı, kimliği belirsiz ziyaretçinin gönderdiği istek veriyordu.
Bunu nasıl doğruladım?
Deneme kurulumunda tek bir ücretli hizmet ve randevu sayfası oluşturdum, sonra hiçbir çerez ya da hesap kullanmadan iki istek gönderdim. İlki normal bir randevu isteğiydi ve ödeme kaydı “beklemede” olarak oluştu. İkincisi aynı istekti, yalnız sonuna ödeme durumu alanı eklenmişti ve ödeme kaydı “tamamlandı” olarak oluştu. Kurulumun dışarıya giden bağlantı kaydı boştu, yani iki istekte de hiçbir ödeme sağlayıcısıyla bağlantı kurulmadı. Test yalnız kendi bilgisayarımdaki kurulumda yapıldı; başkasına ait hiçbir siteye istek gönderilmedi.
Etkiyi abartmamak için sınırlarını da rapora yazdım: sahte kayıt “ödeme tamamlandı” bildirimini tetiklemiyor, kayıtta işlem numarası ve ödeme tarihi boş kalıyor, randevunun kendi durumu da “beklemede” görünüyor. Ödeme sağlayıcısının kayıtlarıyla karşılaştırma yapan bir yönetici farkı görebilir. Ama randevu saatini dolduruyor ve listede ödenmiş görünüyor; personel ödemeye bakarak hizmet veriyorsa doğrudan kayıp doğuyor.
Satıcı açığı nasıl kapattı?
Düzeltme 2.6.0.6 sürümünde tek satırdır: denetleyici, temizlenmiş veriden ödeme durumunu siliyor.
// includes/classes/AppointmentController.php (2.6.0.6)
$data = CleanHelper::cleanData( $_POST, self::getCleanRules() );
// Payment status is server-owned; drop any client value so a public booking can't be stored as paid.
unset( $data['payment_status'] );
Yorum satırı ilkeyi doğru koyuyor: ödeme durumu sunucuya aittir. Ücretsiz hizmetlerde durumu eklentinin kendisi belirlemeye devam ediyor, ücretli hizmetlerde ise artık yalnız ödeme sağlayıcısından gelen doğrulama değiştirebiliyor. Daha genel bir ders de var: kural listesinde olmayan alanı atmak yerine taşıyan bir temizlik fonksiyonu, ileride eklenecek her yeni alan için aynı riski taşır. Güvenli varsayılan, tanınmayan alanı reddetmektir.
Site sahibi ne yapmalı?
- Eklentiyi 2.6.0.6 ya da sonraki bir sürüme güncelleyin. WordPress yönetim panelindeki eklentiler sayfasında sürüm numarası görünür.
- Güncellemeden önceki dönemde “ödendi” görünen randevuları ödeme sağlayıcınızın panelindeki işlemlerle karşılaştırın. İşlem numarası boş olan ama tamamlanmış görünen kayıtlar bu açıkla oluşmuş olabilir.
- Personelin hizmet verirken eklentideki ödeme durumuna değil, ödeme sağlayıcısının onayına bakmasını bir iş kuralı haline getirin.
Bunun hukuki bir boyutu var mı?
Bu açık kişisel veri sızdırmıyordu; bozulan şey işletmenin ödeme kaydıydı. Bu yüzden KVKK m. 12/5'teki, kişisel verilerin kanuni olmayan yollarla başkalarınca elde edilmesi halinde ilgiliye ve Kurula bildirim yükümlülüğü bu açık için kendiliğinden doğmaz. Ama randevu sistemleri ad, telefon, e-posta ve kimi zaman sağlık bilgisi işler ve veri sorumlusu, verilerin hukuka aykırı işlenmesini ve erişilmesini önlemek için gerekli her türlü teknik ve idari tedbiri almak zorundadır (KVKK m. 12/1). Güvenlik güncellemelerini zamanında uygulamak bu tedbirlerin en temel olanıdır; aynı eklentide daha önce randevu kişisel verilerini açığa çıkaran ayrı açıklar da yayımlanmıştı.
İşletme ile müşteri arasında da sonuçları olabilir. “Ödendi” görünen ama ödenmemiş bir randevuda alacak ortadan kalkmaz; ancak uyuşmazlıkta eklenti kaydının tek başına ispat aracı sayılması zorlaşır. Bu yüzden ödeme sağlayıcısının kayıtları asıl kayıt olarak saklanmalıdır.
Zaman çizelgesi
- 7 Ekim 2026: Açık bulundu, deneme kurulumunda doğrulandı ve Patchstack üzerinden bildirildi.
- 8 Ekim 2026: Patchstack bulguyu doğruladı ve üreticiye iletti.
- 15 Ekim 2026: BookIt 2.6.0.6 yayımlandı. Sürüm notunda düzeltme “Props to Alpaslan Fatih Sözer for reporting” ifadesiyle yer alıyor.
- 16 Ekim 2026: CVE-2026-110900 yayımlandı.
Değerlendirmem
Bu açığı ilginç bulmamın nedeni, karmaşık bir saldırı gerektirmemesidir. Ne şifre kırılıyor ne de bir sistemin sınırı aşılıyor; eklenti, ödenip ödenmediğine kendisinin karar vermesi gereken bir bilgiyi ziyaretçiye soruyordu. Önceki sürümlerde aynı eklentide ödeme sağlayıcısıyla ilgili iki ayrı açık kapatılmıştı ve 2.6.0.5'teki yol, o düzeltmelerin hiçbirine uğramadan, ödeme kodu çalışmadan önce kaydı oluşturuyordu. Üreticinin bildirimden bir hafta sonra yama çıkarması ve bildirimi sürüm notuna yazması da iyi bir açık bildirim örneği.
Kaynaklar
Bu yazı 23 Ekim 2026 itibarıyla durumu anlatıyor.
Süreç, süreler ve sık sorulanlar: Bilişim hukuku, siber güvenlik ve KVKK sayfası