Özet. WPS Hide Login (2.000.000+ etkin kurulum), kimliği doğrulanmış bir AJAX işleyicisinde CSRF nonce'unu doğrular ama çağrı sahibinin yetkisini hiç denetlemez. Bu yüzden oturum açmış herkes, Abone düzeyinde biri bile, rastgele WordPress site transient'leri yazabilir. Ders kitabı niteliğinde bir eksik yetkilendirme kusuru (CWE-862). Bu yazı savunma odaklı bir inceleme: kök nedeni, düşük yetkili bir hesabın yazıma neden ulaşabildiğini kavramsal olarak ve site sahipleriyle geliştiriciler için önleme ile tespit yöntemlerini ele alıyor. Kusur yazım anında yamasız olduğundan, koordineli açıklama ilkesi gereği çalışır kavram kanıtı düzeltme çıkana dek paylaşılmıyor.
Anahtar kelimeler: WordPress, eksik yetkilendirme, bozuk erişim denetimi, CWE-862, yetki denetimi, nonce, site transient, en az yetki.
1. Giriş
WPS Hide Login, varsayılan wp-login.php giriş adresini özel bir slug'a taşıyan ve bunu 2.000.000'den fazla sitede yapan bir eklentidir. Kusuru ilginç kılan da bu erişim alanı. Bu kurulumların çoğu üyelik portalları, WooCommerce mağazaları ya da açık kayıtlı siteler; yani düşük yetkili hesapların bol ve kolay elde edildiği ortamlar. Yalnızca oturum açmış bir hesap gerektiren bir kusur, tam da buralara düşer. Üstelik giriş sertleştirmesini vaat eden bir eklentinin içinde bozuk erişim denetimi bulmanın kendine özgü bir ironisi var.
Eksik yetkilendirme, yani bozuk erişim denetimi, WordPress eklenti ekosistemindeki en yaygın ve en zarar verici kusur sınıflarından biri — aynı zamanda en gösterişsizlerinden. Bellek bozulması yok, SQL enjeksiyonu yok, uzaktan kod yürütme yok. Yetki isteyen bir durum değişikliği basitçe denetimsiz bırakılıyor ve bunu asla tetikleyememesi gereken bir hesap tetikleyebiliyor. WPS Hide Login bu örüntünün kod üzerinde incelenecek temiz, somut bir örneğini sunuyor.
2. Etkilenen bileşen
| Eklenti | WPS Hide Login — slug wps-hide-login |
| Test edilen sürüm | 1.9.19 (test sırasındaki güncel/son sürüm) |
| Etkin kurulum | 2.000.000+ |
| Zafiyet türü | Eksik Yetkilendirme / Bozuk Erişim Denetimi |
| CWE | CWE-862 Missing Authorization |
| Gereken yetki | Kimliği doğrulanmış herhangi bir kullanıcı — Abone (read yetkisi) yeterli |
| Kullanıcı etkileşimi | Yok |
| CVSS 3.1 | 4.3 (Düşük) – 5.4 (Orta) — AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:L (bir triyajcı A:N → 4.3 seçebilir; etki, kısıtlı saklanan değerle sınırlanır) |
| Durum | Taze (0-gün); kodda doğrulandı ve güncel sürümde yalıtılmış localhost laboratuvarında yeniden üretildi; yazım anında yamasız |
3. Kök neden
WPS Hide Login, kendi yönetici bildiriminin kapatılmasını kaydetmek için wp_ajax_dismiss_admin_notice adlı kimliği doğrulanmış bir AJAX eylemi tanımlar. İşleyici yalnızca CSRF nonce'unu doğrular, başka bir şey yapmaz. Oysa nonce ile yetki denetimi iki ayrı soruyu yanıtlar. Nonce, isteğin oturum açmış kullanıcının kendi oturumundan geldiğini doğrular; siteler arası istek sahteciliğine (CSRF) karşı bir savunmadır. Kullanıcının o işlemi yapmaya izni olduğunu ise hiçbir zaman göstermez.
Eylem, araya hiçbir yetki sarmalayıcısı girmeden doğrudan işleyiciye bağlanır:
add_action( 'wp_ajax_dismiss_admin_notice', array( $this, 'dismiss_admin_notice' ) );
// (wp_ajax_nopriv_ varyantı yok — yani bu yalnızca kimlik doğrulanmışlara açık, ki TEK kapı budur)İşleyici nonce'u doğrular, sonra saldırgan kontrolündeki seçenek adını doğrudan bir site transient yazımına besler:
public function dismiss_admin_notice() {
check_ajax_referer( 'wps-hide-login-dismissible-notice' ); // yalnızca CSRF nonce — yetki denetimi DEĞİL
$option_name = sanitize_text_field( $_POST['option_name'] ); // tamamen çağrı sahibi kontrolünde, izin listesi yok
$dismissible_length = sanitize_text_field( $_POST['dismissible_length'] );
$transient = 0;
if ( 'forever' != $dismissible_length ) {
$dismissible_length = ( 0 == absint( $dismissible_length ) ) ? 1 : $dismissible_length;
$transient = absint( $dismissible_length ) * DAY_IN_SECONDS;
$dismissible_length = strtotime( absint( $dismissible_length ) . ' days' );
}
set_site_transient( $option_name, $dismissible_length, $transient ); // HEDEF: rastgele site-transient yazımı
wp_die();
}İki kusur birbirini besliyor. Birincisi, current_user_can() kapısı yok. Site transient yazmak yönetici düzeyinde bir işlem olmasına karşın işleyici, çağrı sahibinin manage_options gibi bir yetkisi olup olmadığını hiç sormuyor. Nonce denetimi her oturum açmış kullanıcı için geçtiğinden hiçbir yetki ayrımı sağlamıyor. İkincisi, option_name için izin listesi yok. Yalnızca yöneticilere açık bir işleyici bile hangi transient'e dokunduğunu sınırlamalıdır; burada ad istekten aynen alındığı için yazım eklentinin kendi bildirim anahtarlarıyla sınırlı kalmıyor.
Doğru örüntüyü eklentinin kendi ayar kayıtları gösteriyor. Bunlar WordPress çekirdeğinin options.php'si üzerinden geçer; options.php hem check_admin_referer('siteoptions')'u hem de manage_options yetkisini zorunlu kılar. Kusur tam olarak bu ikinci denetimin AJAX işleyicisinde eksik oluşudur.
4. Yazıma ulaşmak (kavramsal)
Yeniden üretilebilir bir istek dizisi vermeden, öğretici düzeyde kısa bir iz sürüş, düşük yetkili bir hesabın savunmasız yazıma neden ulaştığını gösteriyor.
Önce kayda bakalım. Eylem yalnızca kimliği doğrulanmış biçimde (wp_ajax_…) var; karşılığı olan wp_ajax_nopriv_… yok. Dolayısıyla etkin kapı "oturum açmış olmak"tan ibaret — ve üyelik sitelerinde, WooCommerce mağazalarında, açık kayıtlı her yerde Abone hesapları serbestçe dağıtılır. Bir Abone zaten rolüne açık wp-admin sayfalarına, örneğin kendi profiline, ulaşabilir.
Bir de nonce var. Bildirim kapatma nonce'u, normalde yalnızca bildirimi gösteren yönetici ekranında basılmalıdır. Bu eklentide ise kapatma betiğini yükleyen işlev koşulsuz olarak yönetici betik yükleme olayına bağlanmış, onu eklentinin kendi ayarlar ekranıyla sınırlayacak tek satır da yorum satırına alınmış. Sonuçta işleyicinin kilidini açan nonce, herhangi bir yönetici ekranını yükleyebilen her kullanıcı — Aboneler dahil — için sayfaya gömülüyor.
Her iki ön koşul da zahmetsizce yerine oturunca, Abonenin elinde işleyicinin istediği her şey oluyor: kimlik doğrulanmışlara açık kayıt için geçerli bir oturum ve CSRF denetimi için geçerli bir nonce. İşleyici de yetki isteyen yazımı onun adına yapıyor. Savunucular için asıl nokta prosedürel değil kavramsal: güvenilmeyen bir hesapla bu durum değişikliği arasında duran tek şey, hiç yazılmamış bir denetim. İşin içinde özel araç, zamanlama ya da yük hazırlığı yok; yetki sadece denetimsiz bırakılmış.
5. Etki
Saklanan değer kısıtlı: ya bir tamsayı zaman damgası ya da tam olarak forever dizesi. Bu sınırı açıkça söylemek gerekiyor, çünkü bulguyu dürüst tutuyor — bu bir nesne enjeksiyonu, SQL enjeksiyonu ya da kod yürütme değil. Düşük yetkili kullanıcının kazandığı şey, yöneticilere ait, site genelinde bir durum değişikliği yapabilmek. Uygulamada kimliği doğrulanmış bir Abone şunları yapabilir:
- Bekleyen güncelleme bildirimlerini bastırmak. Çekirdeğin
update_plugins,update_themesya daupdate_coresite transient'lerinin üzerine skaler bir değer yazıldığında, WordPress'in güncelleme mekanizması beklediği yerde->responseyükünü bulamaz. Böylece yöneticilere bir sonraki zamanlanmış yenilemeye dek hiçbir bekleyen eklenti, tema ya da çekirdek güncellemesi — güvenlik güncellemeleri dahil — gösterilmez; yazım yinelenebildiği için bu kör nokta süresiz açık tutulabilir. Mevcut düzeltmelerin varlığını saklamak, ardından gelecek bir saldırı için anlamlı bir kolaylaştırıcıdır. - Rastgele site transient'lerini bozmak ya da silmek. WordPress çekirdeğine ya da başka eklentilere ait önbellekler, hız sınırı sayaçları, özellik bayrakları, lisans ve geçerlilik önbellekleri — işlevsel kesintilere ya da önbellek yığılmalarına yol açar.
Her iki durumda da tutması gereken bir yetki sınırı aşılıyor: bir Abone normalde site transient yazamaz bile. AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:L vektörü; ağdan erişilebilir, düşük karmaşıklıklı, düşük yetki isteyen ve etkileşimsiz bir saldırıyı, gizlilik kaybı olmadan, bütünlük ve erişilebilirliğe sınırlı etkiyle tanımlar. Puanı 4.3 (Düşük) ile 5.4 (Orta) arasında oynatan şey, triyajcının erişilebilirlik etkisini sayıp saymamasıdır.
6. Düzeltme
Kök düzeltme, zaten eksik olan denetimdir — işleyiciyi, yazıma yalnızca yeterli yetkiye sahip bir kullanıcı ulaşacak biçimde kapılayın:
public function dismiss_admin_notice() {
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( -1, 403 );
}
check_ajax_referer( 'wps-hide-login-dismissible-notice' );
// ... mevcut mantık ...
}Derinlemesine savunma için bir de izin listesi ekleyin. option_name'i eklentinin kendi bildirim anahtarlarıyla — wps-hide-login-* gibi sabit bir önek yeterli — kısıtlayın ki yetkili bir çağrı sahibi bile ilgisiz çekirdek ya da üçüncü taraf transient'lerine uzanamasın.
Satıcının sürüm çıkarmasını bekleyemeyen site sahipleri için:
- Eklentinin değişiklik günlüğünü (changelog) izleyin ve düzeltilmiş sürüm yayımlanır yayımlanmaz uygulayın.
- Düşük yetkili hesap sayısını küçük tutun, bunların kimlerde olduğunu denetleyin.
- Aşağıdaki derinlemesine savunma sertleştirmesine yaslanın; herhangi bir denetimsiz işleyicinin etki yarıçapını daraltır.
7. Site sahipleri için tespit ve sertleştirme
Tek gerçek ön koşul çalışan bir giriş olduğundan, pratik sorular şunlar: kim oturum açabiliyor ve güncelleme durumu nasıl izleniyor.
- En az yetki. Abone ve diğer düşük yetkili hesapları, sitenin gerçekten ihtiyaç duyduğu asgari seviyede tutun. Kayıt açıksa ya da self-servisse (üyelik, e-ticaret) her böyle hesabı, bunun gibi kimlik doğrulamalı kusurlar için olası bir aktör sayın.
- Tutarsız güncelleme durumunu kollayın. Buradaki en değerli kötüye kullanım, güncellemeleri gizlemek. Yönetici "Güncellemeler" ekranı her şeyin güncel olduğunu söylerken bağımsız bir kaynak — harici bir envanter ya da bağlam dışı çalıştırılan bir
wp-clidenetimi — daha yeni bir eklenti, tema ya da çekirdek sürümü bildiriyorsa, bu farkı araştırma gerekçesi sayın. Nesne yapısı yerine çıplak bir skaler tutan bir çekirdek güncelleme transient'i zaten başlı başına anormaldir. - Yetki isteyen durum değişikliklerini panelden bağımsız izleyin. Bileşenlerin güncel olup olmadığını yalnızca pano içindeki güncelleme rozetinden öğrenmeyin; ikinci, bağlam dışı bir doğruluk kaynağı bu gizleme biçimini tümüyle etkisiz kılar.
- Özelliğin maruziyetini azaltın. Bir sertleştirme katmanı ya da WAF sanal yama yapmanıza olanak tanıyorsa — bu AJAX eylemi için yönetici yetkisi şart koşarak ya da beklenmedik
option_nametaşıyan istekleri reddederek — kod düzeltmesi gelene kadar bu açığı kapatır. Böyle bir kuralı yalnızca burada anlatılan mekanikten türetin. - Her şeyi güncel tutun. Güncelleme bastıran bir kusurun ironisi, çarenin güncellemenin kendisi olması; düzeltilmiş sürüm çıkar çıkmaz eklentinin yükseltildiğinden emin olun.
8. Daha geniş ders
Bunun altındaki ilke eski ve sürekli unutuluyor, bu yüzden yinelemeye değer: nonce bir yetki denetimi değildir. İkisi ayrı soruları yanıtlar. Nonce "bu istek gerçekten bu kullanıcının oturumundan mı geldi?" sorusunu yanıtlar ve CSRF'ye karşı savunur. Yetkilendirme "bu kullanıcının bunu yapmaya izni var mı?" sorusunu yanıtlar ve yetkiye dair bir karardır. İlkini doğrulayıp ikincisini atlayan bir işleyici, geçerli bir nonce edinebilen her hesaba ardına kadar açıktır; WordPress'te bu, oturum açmış her kullanıcı demektir.
Durum değiştiren her uç nokta için bunu bir kural belleyin — ister AJAX eylemi, ister REST rotası, ister admin-post işleyicisi olsun: işleme uygun, kendi başına duran ve hiçbir nonce'a yaslanmayan açık bir yetki denetimi (current_user_can( … )). Sonra, çağrı sahibinden gelen bir tanımlayıcıyı tüketen her hedefte — seçenek adı, transient anahtarı, dosya yolu, yazı kimliği — istekte ne gelirse ona güvenmek yerine bir izin listesiyle sınırlayın. Kimlik doğrulama, CSRF koruması ve yetkilendirme üç ayrı denetimdir; birini ya da ikisini uygulamak diğerlerini uygulamış olmaz.
9. Sorumlu açıklama
Bu araştırma koordineli açıklama ilkesiyle yürütüldü ve yayımlanıyor. Kusur yamasız. Eklenti satıcısı özel olarak bilgilendirildi; çalışır kavram kanıtı — canlı bir sitede yazımı yeniden üreten tam istek dizisi ve yük değerleri — bir düzeltme çıkana dek bu herkese açık yazıdan bilerek saklanıyor. Burada kalan şey kök neden kodu, önerilen düzeltme ve mekanizmanın kavramsal anlatımı; geliştiriciler ve site sahiplerinin kusur sınıfını anlamasına, önlemesine ve tespit etmesine yetecek kadar, ama kullanıma hazır bir silah sunmadan.
Tüm doğrulama, araştırmacının kendi yalıtılmış, localhost'a bağlı laboratuvarında, herkese açık GPL eklentisine karşı yapıldı. Hiçbir üçüncü taraf ya da üretim sistemine dokunulmadı. Test eklentisi ve test hesabı sonradan kaldırıldı, test sırasında değiştirilen her transient eski haline getirildi.
Kaynaklar
- CWE-862: Missing Authorization — https://cwe.mitre.org/data/definitions/862.html
- WordPress Plugin Handbook — Checking User Capabilities — https://developer.wordpress.org/plugins/security/checking-user-capabilities/
- WordPress Plugin Handbook — Nonces — https://developer.wordpress.org/plugins/security/nonces/
- WordPress Developer Reference —
current_user_can()— https://developer.wordpress.org/reference/functions/current_user_can/ - WordPress Developer Reference —
set_site_transient()— https://developer.wordpress.org/reference/functions/set_site_transient/ - OWASP — Broken Access Control — https://owasp.org/Top10/A01_2021-Broken_Access_Control/
- FIRST — CVSS v3.1 Hesaplayıcı — https://www.first.org/cvss/calculator/3.1
Savunma ve eğitim amacıyla yayımlanmıştır. Noroxi LLC yalnızca yetkili güvenlik araştırması yürütür; işlevsel istismar ayrıntıları bir düzeltme yayımlanana kadar saklanır.
Bu hafta sömürülenler · her pazartesi
KEV'e girenler, yeni PoC'ler, WordPress triyajı ve bu hafta ne yapmalı. Hesap gerekmez.