İçeriğe atla
Noroxi
Araştırma9 dk

BestWebSoft Google Captcha: kimlik doğrulamasız CAPTCHA atlatma

100 bin kurulumlu reCAPTCHA eklentisinde, istek URI'sindeki gevşek bir alt-dize denetimi doğrulamayı dışarıdan kapatıyor. Kök neden ve çözüm.

Noroxi EkibiSızma testi ve güvenlik danışmanlığı

Özet. Bir CAPTCHA eklentisinin her istekteki tek işi, burada insan doğrulaması gerekip gerekmediğine karar vermektir. BestWebSoft'un Google Captcha / reCAPTCHA eklentisi (100.000'den fazla kurulum) bu kararı, tüm istek URI'sine uygulanan ham bir strstr() denetimiyle veriyor. Eşleşme çapasız olduğu için, kimliği doğrulanmamış herhangi bir ziyaretçi sorguya zararsız görünen bir dize yerleştirip korunan her formda doğrulamayı kapatabiliyor. Ortaya çıkan şey, kimlik doğrulaması ve kullanıcı etkileşimi gerektirmeyen bir güvenlik özelliği atlatması (CWE-287). Yazıda kök nedendeki kalıbı, neden işe yaramadığını, kusura kavramsal olarak nasıl erişildiğini ve site sahiplerinin bunu nasıl tespit edip sınırlayabileceğini ele alıyoruz. Bulgu üreticiye eşgüdümlü açıklama kapsamında bildirildi; çalışan tam kavram kanıtı, düzeltme yayımlanana kadar saklı tutuluyor.

Anahtar kelimeler: WordPress güvenliği, CAPTCHA atlatma, yetersiz kimlik doğrulama, CWE-287, güvenlik özelliği atlatma, güvenli kodlama.

1. Giriş

BestWebSoft'un Google Captcha / reCAPTCHA eklentisi (slug google-captcha), 100.000'den fazla WordPress sitesini koruyor. wp-login.php'ye, kayıt ve parola sıfırlama formlarına, yorumlara ve diğer giriş noktalarına bir reCAPTCHA doğrulaması ekliyor; böylece oturum açma kaba kuvvetle denenemiyor, kayıtlar betikle otomatikleştirilemiyor, yorum spamı daha kapıda kesiliyor.

Var oluş nedeni baştan sona bir güvenlik denetimi olan bir eklentide asıl soru, doğrulamanın ekranda görünüp görünmediği değil; saldırganın, o doğrulamanın gösterilmesi kararına en baştan müdahale edip edemeyeceğidir. Buradaki kusur tam da bu — kimlik doğrulamasız bir güvenlik özelliği atlatması (CWE-287) — ve sessizce başarısız oluyor. Ekranda hiçbir şey bozulmuyor. Eklenti hâlâ etkin görünüyor, yönetici kendi ziyaretlerinde reCAPTCHA'yı görmeye devam ediyor, oysa koruma bunu isteyen her istek için ortadan kalkmış oluyor. Böyle bir atlatma denetimi kaldırmaz, etkisiz bırakır; gözden kaçması da bu yüzden kolaydır.

2. Etkilenen bileşen

EklentiGoogle Captcha / reCAPTCHA by BestWebSoft — slug google-captcha
Test edilen sürüm1.87 (test sırasında güncel/en son)
Etkin kurulum100,000+
Zafiyet türüGüvenlik özelliği atlatma / yetersiz kimlik doğrulama korumasının atlatılması
CWECWE-287 Improper Authentication (CWE-290 Authentication Bypass by Spoofing)
Gerekli yetkiYok — kimlik doğrulamasız, uzaktan
Kullanıcı etkileşimiYok
CVSS 3.1~5.3 (Orta) AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N
DurumTaze; kodda doğrulandı ve localhost laboratuvarında yeniden üretildi; yazım anında yama yok

3. Teknik analiz

Doğrulamanın bir isteğe uygulanıp uygulanmayacağına tek bir fonksiyon, gglcptch_is_recaptcha_required(), merkezî olarak karar veriyor. Fonksiyon erken bir çıkışla açılıyor:

php
// google-captcha.php (gglcptch_is_recaptcha_required)function gglcptch_is_recaptcha_required( $form_slug = '', $is_user_logged_in = null ) {    global $gglcptch_options;    if ( strstr( $_SERVER['REQUEST_URI'], '/jwt-auth' ) ) {   // KUSUR: sorgu dizesi dâhil ham REQUEST_URI; çapa yok, çözme yok, rota denetimi yok        return false;                                          // -> captcha gerekli değil    }    ...}

Çıkışın ardındaki mantık makul. JWT Authentication for WP REST API eklentisi, görsel bir captcha çizemeyen /wp-json/jwt-auth/* rotalarını sunar; bu rotaların muaf tutulması gerekir. Sorun, muafiyetin nasıl saptandığında. strstr(), baştan sona saldırganın denetimindeki REQUEST_URI boyunca — yol ve sorgu dizesi birlikte — çalışıyor ve üç boşluk bırakıyor:

  • değer hiçbir zaman URL olarak çözülmüyor;
  • eşleşme çapasız; yani /jwt-auth dizesi yalnızca rotanın başında değil, dizenin herhangi bir yerinde durabiliyor;
  • isteğin gerçekten bir JWT-Auth REST çağrısı olduğu hiçbir yerde doğrulanmıyor — REST_REQUEST denetimi yok, tam rest_route ya da yol eşleşmesi yok.

Yani bir güven kararı, doğrudan istemcinin baştan sona sahibi olduğu girdi üzerindeki serbest metin aramasından çıkıyor. Sorgu dizesini yazan saldırgandır; içine /jwt-auth dizesini koymak testi geçiriyor, altındaki istek ise hâlâ sıradan bir oturum açma, kayıt ya da yorum gönderimi olarak kalıyor.

Tek bir karar, gerisini sürüklüyor. gglcptch_is_recaptcha_required() aynı zamanda uygulama kancalarının hiç kaydedilip kaydedilmeyeceğini de belirliyor — includes/forms.php içinde (gglcptch_add_actions, init kancasında):

php
if ( gglcptch_is_recaptcha_required( 'login_form', $is_user_logged_in ) ) {    add_action( 'login_form', 'gglcptch_login_display' );    add_action( 'authenticate', 'gglcptch_login_check', 21, 1 );   // uygulama kancası — erken çıkış tetiklenince hiç eklenmez}if ( gglcptch_is_recaptcha_required( 'registration_form', $is_user_logged_in ) ) { ... }if ( gglcptch_is_recaptcha_required( 'reset_pwd_form', $is_user_logged_in ) ) { ... }

Erken çıkış false döndürdüğünde o istek için kancaların hiçbiri eklenmiyor; gglcptch_login_check(), gglcptch_register_check() ve yorum denetimleri hiç çalışmıyor. Doğrulama yalnızca gözden gizlenmiyor — sunucu tarafı denetim baştan hiç bağlanmamış oluyor.

4. Kusura nasıl erişiliyor (kavramsal)

Özel bir konum da gerekmiyor, kimlik bilgisi de. Eklenti her istekte, uygula-ya da-atla kararını kendisine verilen bir dizeden yeniden hesaplıyor; bu dize tüm URI boyunca (sorgu dâhil) çapasız bir alt-dize olarak eşleştirildiği için, isteği yapan kişi "bu bir JWT-Auth rotası, captcha'yı atla" dalını karşılayıp yine de gayet normal, korunan bir form gönderebiliyor.

Kimliği doğrulanmamış birinin korumasız yola girebilmesinin nedeni rastlantı değil, yapısal. Muafiyet, URL'nin asla doğrulamadığı bir özelliğine güveniyor; onu gerçek bir REST rotasına bağlayan hiçbir şey yok, dolayısıyla hiçbir zaman REST isteği olmamış bir istekle de karşılanabiliyor. Bir kez tetiklendiğinde oturum açma, kayıt, parola sıfırlama ve yorumlar için uygulama o istek boyunca atlanıyor, form ise eklentinin koruması devre dışıyken işleniyor.

İsteğin tam biçimini, yük değerlerini ve adım adım yeniden üretim sırasını bilerek dışarıda bırakıyoruz. Bir savunmacı için ders tümüyle mekanizmada: kendi kaderine, doğrulanmamış ve saldırganca biçimlendirilmiş girdiye bakarak karar veren bir denetim, istendiği an kapatılabilir. Operasyonel tarif, düzeltme çıkana kadar üreticide kalıyor.

5. Etki değerlendirmesi

Kimlik doğrulaması ve etkileşim olmadan reCAPTCHA; oturum açma, kayıt, parola sıfırlama ve yorum formlarından düşürülebiliyor — ki eklentinin tüm görevi bu. Bu da sitenin eklentiyi engellemek için kurduğu şeyi geri veriyor: wp-login.php'ye karşı kimlik-bilgisi doldurma ve kaba kuvvet, betikle spam hesap kaydı, yorum spamı.

Hata tek başına kod çalıştırmıyor ve veri sızdırmıyor; zarar, sitenin gizliliğine ya da kullanılabilirliğine değil, denetimin bütünlüğüne düşüyor. Bu da dürüst bir Orta taban puanını destekliyor — AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N, yaklaşık 5.3. Bir triyajcı daha yükseğini haklı olarak savunabilir: kusur, bir güvenlik ürününü site genelinde, talep üzerine, isteyen herkes için devre dışı bırakıyor. Gerçekteki riskin — hız sınırsız kaba kuvvetle hesap ele geçirme — nereye kadar gideceği, parolaların gücüne ve yığının başka yerindeki hız sınırlamalarına bağlı.

6. Düzeltme

Güveni, saldırgan girdisinin çapasız bir alt-dizesinden türetmeyi bırakın ve asıl kastettiğiniz şeyi denetleyin: isteğin gerçekten JWT-Auth REST rotası olup olmadığını.

php
// Yalnızca gerçek JWT-Auth REST isteklerini muaf tut.if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {    $path = parse_url( $_SERVER['REQUEST_URI'], PHP_URL_PATH ); // yalnızca çözülmüş yol, sorgu dizesi yok    if ( preg_match( '#/wp-json/jwt-auth/#', (string) $path ) ) {        return false;    }}

Düzeltilen denetimi sağlam kılan üç özellik var: REST_REQUEST üzerinde denetim yapıyor, yalnızca yola karşı eşleşiyor ve sorgu dizesine asla bakmıyor, serbest bir alt-dize yerine gerçek rota önekine çapalıyor. İkincil IP izin-listesi zaafı da aynı disiplini istiyor: istemci adresini REMOTE_ADDR'den (ya da yapılandırılmış, güvenilir bir vekil çözümünden) çözün; istemcinin gönderdiği X-Forwarded-For, X-Real-IP ya da Client-IP başlıklarından asla.

Operasyonel olarak: üretici düzeltilmiş sürümü yayımlar yayımlamaz güncelleyin. O zamana kadar aşağıdaki sertleştirme zaman kazandırır.

7. Site sahipleri için tespit ve sertleştirme

Hepsi yukarıdaki mekanizmadan çıkıyor — uydurma ürün özelliği yok:

  • İstek günlüklerinde işareti arayın. wp-login.php'ye, kayıt uç noktasına ya da wp-comments-post.php'ye yapılan ve URL'sinde yersiz bir /jwt-auth taşıyan — özellikle sorgu dizesinde — başarılı bir gönderim, birinin muafiyeti tetiklediğine dair güçlü bir işarettir. Gerçek JWT-Auth trafiği /wp-json/jwt-auth/… altında, bir oturum açma sayfasına iliştirilmiş sorgu parametresi olarak değil, gerçek bir REST rotası olarak yaşar.
  • Eklentinin atlanamayacağı bir katmanda hız sınırlayın. Kusur eklentinin denetimini ortadan kaldırdığı için, kaba kuvvet korumasını ona bağlı olmayan bir yere koyun: wp-login.php üzerinde sunucu ya da WAF düzeyinde hız sınırlama, tekrarlı başarısızlıklardan sonra oturum kilidi ve kimlik-bilgisi doldurma örüntülerine uyarı.
  • Güçlü kimlik doğrulamaya yaslanın. Her yerde güçlü ve benzersiz parolalar ile çok faktörlü kimlik doğrulama, hız sınırsız bir oturum açma uç noktasını saldırı için çok daha az kârlı kılar.
  • Saldırı yüzeyini kısın. Kullanmadığınız kayıt ve yorum formlarını kapatın. JWT-Auth sitenizde hiçbir rol oynamıyorsa, üzerinde düşünülecek bir hareketli parça daha az olur.
  • Sanal yama. REST olmayan uç noktalarda — wp-login.php ve yorum gönderimleri — /jwt-auth alt-dizesini çıkaran ya da reddeden bir WAF kuralı makul bir geçici çözümdür; yeter ki gerçek /wp-json/jwt-auth/ REST trafiğini rahat bırakacak kadar dar kapsamlansın.
  • Değişiklik günlüğünü izleyin. Her şeyi güncel tutun ve üreticinin sürüm notlarını takip edin ki düzeltme çıktığı gün uygulanabilsin.

8. Daha geniş ders

İlke WordPress'ten onlarca yıl eski: bir güvenlik kararını asla doğrulanmamış, saldırgan denetimindeki girdiden vermeyin ve kesin bir koşulun yerine asla gevşek bir dize eşleşmesi koymayın. Muafiyet bir güvenlik kararıdır. Anlamı "bu JWT-Auth REST rotasıdır" ise, kod tam olarak bunu doğrulamalı — istek bağlamını (REST_REQUEST) ve kanonik, çözülmüş rota yolunu — gerçek koşul doğruyken rastgele ortaya çıkıveren bir alt-dizeyi değil.

Bu tek satırda üç kötü kalıp buluşuyor ve her biri ezberlenmeye değer:

  1. Sınır yerine alt-dize. strstr() ve strpos(), "bu metin bir yerlerde geçiyor mu?" sorusunu yanıtlar — bir yönlendiricinin ya da yetkilendiricinin gerçekten sorduğu soru neredeyse hiç bu değildir. Bunun yerine çapalayın ve ayrıştırın.
  2. Yalnızca bir kısmı önemliyken tüm girdiye güvenmek. Sorgu dizesi rota değildir. Eşleştirmeden önce kastettiğiniz bileşeni — yolu — çözün ve ayırın.
  3. Varsayılanda izin veren muafiyetler. Bir denetimi kapatan muafiyet, bir özelliği açan muafiyetten çok daha tehlikelidir. Fonksiyondaki en dar, en kesin denetlenen dal olmalı ve kapalıya düşmelidir.

Aynı mantık ikincil varyantı da bitirir: bir IP izin-listesi, istemcinin ayarlayabildiği bir başlığa değil, sunucunun güvenebileceği bir adrese — REMOTE_ADDR — karşı eşleştirilmelidir.

9. Sorumlu açıklama

Bu çalışma eşgüdümlü açıklama kapsamında yürütüldü ve yayımlanıyor. Üretici, sorunu yeniden üretip düzeltmeye yetecek tam teknik ayrıntıyı ve çalışan bir kavram kanıtını özel olarak elinde bulunduruyor. Operasyonel istismar ayrıntısı, düzeltilmiş sürüm çıkana (ya da üzerinde anlaşılan açıklama penceresi kapanana) kadar bu kamuya açık yazıdan bilerek çıkarıldı; böylece kimse yamasız bir denetime karşı kullanıma hazır bir silahla ayrılmıyor.

Tüm testler, kamuya açık GPL eklentinin yalıtılmış, localhost'a bağlı bir laboratuvar örneğinde, iş için oluşturulmuş bir test kullanıcısıyla yürütüldü. Hiçbir üçüncü taraf sisteme dokunulmadı; laboratuvar ve test hesabı sonrasında söküldü. Noroxi LLC yalnızca yetkili güvenlik araştırması yapar ve test etme izni olmayan sistemleri test etmez.

Kaynaklar


Savunma ve eğitim amacıyla yayımlanmıştır. Noroxi LLC yalnızca yetkili güvenlik araştırması yürütür; operasyonel istismar ayrıntısı, bir düzeltme hazır olana kadar yayımlanmaz.

Bu hafta sömürülenler · her pazartesi

KEV'e girenler, yeni PoC'ler, WordPress triyajı ve bu hafta ne yapmalı. Hesap gerekmez.

Adresiniz yalnızca bu e-posta için kullanılır; üçüncü tarafla paylaşılmaz.

Sıradaki

Happy Addons for Elementor SSRF: API anahtarı temizleme hatası (CWE-918)