HTTP Request Smuggling Nedir?

Sefa Yıldırım
11 min readAug 6, 2024

--

Bu bölümde, HTTP Request Smuggling zafiyetini detaylı bir şekilde ele alacağız. Zafiyetin ne olduğu ve nasıl çalıştığı, saldırı sürecinde neler yaşandığı, bu zafiyetin nasıl ortaya çıktığı gibi konuları inceleyeceğiz. Ayrıca, bu zafiyeti kullanarak nasıl saldırı gerçekleştirilebileceğini ve kaç farklı saldırı türü olduğunu hem teorik hem de pratik olarak öğreneceğiz.

HTTP Request Smuggling

HTTP request smuggling, bir saldırganın birden fazla HTTP request’ini manipüle ederek backend server’ların bu request’leri yanlış yorumlamasına neden olduğu bir saldırı tekniğidir. Bu durum, genellikle birden fazla server arasında yapılan iletişimde veya bir reverse proxy server’ı kullanıldığında ortaya çıkar.

Nasıl Çalışır?

Bu saldırı, HTTP request’inin header’larındaki küçük farklılıkları kullanarak gerçekleşir. HTTP request’i , server’lar arasında yönlendirilirken veya işlenirken, farklı server’ların bu istekleri farklı şekilde yorumlaması mümkündür. Saldırgan, bu farkı kullanarak iki server’ın aynı HTTP request’ini farklı şekillerde işlemesini sağlar.

Örnek

Bir server , bir HTTP request’inin sonunu Content-Length header’ına göre belirlerken, bir diğeri Transfer-Encoding header’ına göre belirleyebilir. Saldırgan, bu header’ları manipüle ederek, iki server’ın aynı request’i farklı şekillerde işlemesini sağlayabilir.

HTTP/2 ve HTTP/1

HTTP request smuggling, esas olarak HTTP/1 request’leri ile ilişkilidir. Ancak, backend mimarisine bağlı olarak, HTTP/2 destekleyen web siteleri de bu tür saldırılara karşı savunmasız olabilir.

HTTP request smuggling Saldırısında Ne Olur?

Günümüzün web uygulamaları, kullanıcılar ile son uygulama logic’i arasında HTTP server chain’leri kullanılır. Kullanıcılar, request’lerini bir frontend server’a gönderir ve bu server , request’leri bir veya daha fazla backend server’a iletir. Bu tür bir mimari, modern bulut tabanlı uygulamalarda giderek daha yaygın ve bazı durumlarda kaçınılmazdır.

Frontend server, HTTP request’leri backend server’a ilettiğinde, genellikle aynı backend ağ bağlantısı üzerinden birkaç request’i gönderir, çünkü bu çok daha verimli ve performanslıdır. Protokol çok basittir; HTTP request’leri art arda gönderilir ve alıcı server, bir request’in nerede bittiğini ve bir sonraki request’in nerede başladığını belirlemek zorundadır.

Bu durumda frontend ve backend sistemlerin request’ler arasındaki sınırlar konusunda(yani HTTP request’lerinin başlangıç ve bitiş noktaları) hemfikir olması çok önemlidir. Aksi takdirde saldırgan, frontend ve backend sistemler tarafından farklı şekilde yorumlanacak belirsiz bir request gönderebilir.

Bu durumda saldırgan, frontend request’inin bir kısmının backend server tarafından bir sonraki request’in başlangıcı olarak yorumlanmasına neden olur. Bir sonraki request’in başına eklenir. Bu nedenle uygulamanın bu request’i işleme biçimine müdahale edebilir. Bu durum http request smuggling saldırısıdır.

HTTP Smuggling Zafiyetleri Nasıl Oluşuyor?

Çoğu HTTP request smuggling zafiyeti, HTTP/1 spesifikasyonunun bir request’in nerede biteceğini belirlemek için iki farklı yol sağlaması nedeniyle ortaya çıkar. Content-Length ve Transfer-Encoding header’ıdır.

Content-Length header'ı nispeten daha basittir. body uzunluğunu byte cinsinden belirtir. Örneğin,

POST /search HTTP/1.1
Host: normal-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 11

q=smuggling

Transfer-Encoding header’ı, mesaj body’sinin chunked encoding kullandığını belirtmek için kullanılabilir. Bu durum mesaj body’sinin bir veya daha fazla veri parçası (chunk) içerdiği anlamına gelir. Her bir chunk, byte cinsinden chunk boyutunu (onaltılık olarak ifade edilir), ardından bir satır sonu ve chunk içeriğini içerir. Mesaj, boyutu sıfır olan bir chunk ile sonlandırılır. Örneğin,

POST /search HTTP/1.1
Host: normal-website.com
Content-Type: application/x-www-form-urlencoded
Transfer-Encoding: chunked

b
q=smuggling
0

Not: Birçok güvenlik testçisi, iki nedenle chunked encoding’in HTTP request’lerinde kullanılabileceğini bilmemektedir.

  • Burp Suite, chunked encoding’i otomatik olarak açar ve mesajların daha kolay görüntülenmesini ve düzenlenmesini sağlar.
  • Browser’lar genellikle request’lerde chunked encoding kullanmaz ve bu genellikle sadece server response’larında görülür.

HTTP/1 spesifikasyonu, HTTP mesajlarının uzunluğunu belirtmek için iki farklı yöntem sağladığından, tek bir mesajın her iki yöntemi aynı anda kullanması ve bunların birbiriyle çelişmesi mümkündür. Spesifikasyon, hem Content-Length hem de Transfer-Encoding header’ları varsa, Content-Length header’ının göz ardı edilmesi gerektiğini belirterek bu sorunu önlemeye çalışır. Bu durum yalnızca tek bir server söz konusu olduğunda belirsizliği önlemek için yeterli olabilir, ancak iki veya daha fazla server bir araya geldiğinde yeterli değildir. Bu durumda, iki nedenle sorunlar ortaya çıkabilir,

  1. Bazı server’lar, request’lerde Transfer-Encoding header’ını desteklemez.
  2. Transfer-Encoding header’ını destekleyen bazı server’lar, header bir şekilde gizlenmişse (obfuscated) bunu işlemeyebilir.

Frontend ve backend server’lar, (mümkünse gizlenmiş) Transfer-Encoding header’ına ilişkin farklı davrandıklarında, ardışık request’lerin sınırları konusunda anlaşmazlık yaşayabilirler ve bu da http request smuggling saldırılarına yol açabilir.

Not: HTTP/2'yi uçtan uca kullanan web siteleri, doğası gereği smuggling saldırılarına karşı dayanıklıdır. HTTP/2 spesifikasyonu, bir request’in uzunluğunu belirlemek için tek ve sağlam bir mekanizma sunduğundan, bir saldırganın gerekli belirsizliği ortaya çıkarması mümkün değildir.

Birçok web sitesi HTTP/2 konuşan frontend server’a sahiptir, ancak bu server’ın önünde yalnızca HTTP/1'i destekleyen backend altyapısı bulunur. Bu durum frontend’in aldığı request’leri HTTP/1'e çevirmek zorunda bırakır. Bu işleme HTTP düşürme (HTTP downgrading) denir.

HTTP Request Smuggling Saldırıları Nasıl Gerçekleştirilir?

Klasik request sumggling saldırıları, Content-Length header’ı ve Transfer-Encoding header’ını tek bir HTTP/1 request’inde birleştirerek ve bunları manipüle ederek frontend ve backend server’larının request’i farklı şekilde işlemesini sağlamayı içerir. Bu işlemin tam olarak nasıl yapıldığı, iki server’ın davranışına bağlıdır,

  • CL.TE: Frontend server Content-Length header’ını, backend server’i ise Transfer-Encoding header’ını kullanır.
  • TE.CL: Frontend server Transfer-Encoding header’ını , backend server ise Content-Length header’ını kullanır.
  • TE.TE: Frontend ve backend server’lar her ikisi de Transfer-Encoding header’ını destekler, ancak server’lardan biri header bir şekilde gizlenirse (obfuscation) bunu işlemeyebilir.

Not: Bu teknikler yalnızca HTTP/1 request’leri kullanılarak mümkündür. Browser’lar ve diğer client’lar, Burp dahil, TLS el sıkışması sırasında advertise support yoluyla server’larla iletişim kurmak için varsayılan olarak HTTP/2 kullanır.

Sonuç olarak, HTTP/2 desteği olan siteleri test ederken, Burp Repeater’da protokolleri manuel olarak değiştirmemiz gerekir.

CL.TE vulnerabilities

Burada frontend server Content-Length header’ını, backend server ise Transfer-Encoding header’ını kullanır. Basit bir HTTP request smuggling saldırısı şu şekilde yapılabilir,

POST / HTTP/1.1
Host: vulnerable-website.com
Content-Length: 13
Transfer-Encoding: chunked

0

SMUGGLED

Frontend server, Content-Length header’ını işler ve request body’sinin 13 byte uzunluğunda olduğunu, yani SMUGGLED’in sonuna kadar olduğunu belirler. Bu request, backend server’a iletilir.

Backend server Transfer-Encoding header’ını işler ve bu nedenle mesaj body’sinin chunked encoding kullandığını kabul eder. İlk chunk’u işler, bu chunk sıfır uzunlukta olarak belirtilmiştir ve bu nedenle request’i sonlandırdığı kabul edilir. İzleyen byte’lar, yani SMUGGLED, işlenmeden bırakılır ve backend server bunları sıradaki request’in başlangıcı olarak kabul eder.

Lab: HTTP request smuggling, basic CL.TE vulnerability

Laboratuvarımız frontend ve backend server içermektedir ve frontend server chunkend encoding desteklemez. Frontend server , GET veya POST method’unu kullanmayan request’leri reddeder. Laboratuvar çözümü için bizden istenen sonraki request’in farklı bir karekterle başlatılarak XPOST gibi bir durumu yaşayacak mıyız bunu gözlemlemektir.

Request’imizi yukarıda görüldüğü şekilde iki kez yaptığımızda “Unrecognized method SPOST” response’ını görüyoruz ve başarılı olarak laboratuvar çözümünü tamamlıyoruz.

Burada, Connection: keep-alive header’ı ile tek bir tcp bağlantısı üzerinden birden fazla request göndermemizi sağlıyor.

İlk request’i yaptığımızda frontend server content-length header’ına göre işlem yapar. S karekteri dahil request’i backend server’a iletir. Ancak backend server request’i aldığında transfer-encoding header’ına göre işlemi yapacaktır. Bu sebeple belirttiğimiz gibi chunked’ı 0 olarak işleyecek ve daha S karekterine gelmeden işlemi sonlandıracaktır. Ancak burada S karekterinin bırakılması gelecek sonraki request’in başlangıcı olarak kabul etmesine sebep olacaktır.

Sonuç olarak ikinci request’imizin başı SPOST olarak başlyacağından ve böyle bir HTTP method’u desteklenmediğinden hata alacağız ve laboratuvar çözümünü tamamlayacağız.

TE.CL vulnerabilities

Bu senaryoda ise frontend server Transfer-Encoding header’ını, backend server ise Content-Length header’ını kullanır. Basit bir HTTP request smuggling saldırısı şu şekilde yapılabilir,

POST / HTTP/1.1
Host: vulnerable-website.com
Content-Length: 3
Transfer-Encoding: chunked

8
SMUGGLED
0

Frontend server Transfer-Encoding header’ını işler ve böylece mesaj body’sini chunked encoding kullanıyormuş gibi ele alır. 8 byte uzunluğunda olduğu belirtilen ilk chunk’ı SMUGGLED’den sonraki satırın başına kadar işler. Sıfır uzunlukta olduğu belirtilen ikinci chunk’ı işler ve bu nedenle request’in sonlandırılması olarak değerlendirilir. Bu request backend server’a iletilir.

Backend server, Content-Length header’ını işler ve request body’sinin 8'den sonraki satırın başına kadar 3 byte uzunluğunda olduğunu belirler.(8\r\n) SMUGGLED ile başlayan sonraki byte’lar işlenmeden bırakılır ve backend server bunları sıradaki bir sonraki request’in başlangıcı olarak değerlendirecektir.

Lab: HTTP request smuggling, basic TE.CL vulnerability

Laboratuvarımız frontend ve backend server içermektedir ve backend server chunkend encoding desteklemez. Frontend server , GET veya POST method’unu kullanmayan request’leri reddeder. Laboratuvar çözümü için bizden istenen sonraki request’in farklı bir karekterle başlatılarak XPOST gibi bir durumu yaşayacak mıyız bunu gözlemlemektir.

HTTP Request Smuggling saldırılarında başarılı olabilmek için şuan da Burp suite’te 3 tane ayar yapmamız gerekiyor.

Inspector menüsünden HTTP/2 protokolünü HTTP/1.1 protokolüne düşürmemiz gerekiyor.

CRLF karekterlerini görebilmek için Repeater Request bölümünün sağ üstünde bulunan \n butonuna tıklayarak CRLF karekterleri gözlemleyebiliriz.

Repeater ayarlarından Update Content-Length tikini kaldırmamız gerekir.

Laboratuvar bize hangi server’ın CL ya da TE kullandığını söylüyor. Normal şartlarda bunların tespiti için kullanılan yöntemler vardır ve ilerleyen konularda değineceğiz. Şimdilik sadece hangi saldırı türlerinin olduğu üzerinde duruyoruz. Öyleyse Frontend server’ın TE kullandığını ve backend server’ın CL kullandığını bildiğimize göre,

POST / HTTP/1.1
Host: YOUR-LAB-ID.web-security-academy.net
Content-Type: application/x-www-form-urlencoded
Content-length: 4
Transfer-Encoding: chunked

5c
GPOST / HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 15

x=1
0

Böyle bir request’te bulunabiliriz. Bu request iki kez gönderildiğinde neler oluyor ve önemli olan bir kaç durumdan bahsedeyim Frontend server TE kullanacağından ilk olarak chunk verisinin kaç byte olduğunu söylememiz gerekmektedir. Bunun için smugg request’in üzerini taradığımızda,

Inspector sekmesinde 5c byte’lık bir veri olduğunu gözlemliyoruz. Ayrıca ilk request’in Content-length’ini 4 byte olmasının sebebi de CRLF karekterlerle birlikte 5c\r\n = 4 byte olmasından kaynaklıdır.

Bu request’i gönderdiğimizde Frontend server TE ‘e göre 0 byte’ı görene kadar 5c’lik chunk’ı işleyecektir.(0\r\n\r\n chunk’ın sonunu belirler) Backend server bu request’i aldığında CL’e göre yani 4 byte’lık veriyi okuyacaktır ve işlemesini bitirecektir. Geriye kalan GPOST request’i ikinci kez göndereceğimiz request’in başına eklenecektir.

Bu noktada dikkat edilmesi gereken smuggling request’inde CL’in uzunluğunun neye göre ayarlanması gerektiğidir.

x=1\r\n
0\r\n
\r\n

Bu durumda normal 10 byte’lık veri olduğunu görüyoruz. Ancak ardından gelen ikinci request’in başına eklenmesi için min verebileceğimiz değer normal+1 byte uzunluk ayarlamamız gerekir. Yani 11 byte olması gerekir. Max verebileceğimiz değer ise normal+ ikinci request’in toplam byte değeri yani bu örneğimizde aynı request’i ikinci kez gönderdiğimiz için, request’imizin toplam uzunluğunu, taradığımızda inspector sekmesinden 284 byte olarak gözlemliyoruz. O sebeple smuggling request’imizde CL değeri max, normal+284 yani 294 byte olabilir. Son bir dikkat edilmesi gereken durum CL değerinin iki basamaktan üç basamağa çıkması genel verinin uzunluğunu arttırdığından chunked 5c verisini de 5d olarak güncellememiz gerekir.

Peki 295 verirsek ne olur? Backend server 295. verinin gelmesini bekleyecektir. Ancak gelmeyeceği için,

Timeout hatası verecektir. Sonuç olarak smuggling request’imizde 11 ile 294 arası değer verebiliriz.

Bu request’i iki kez gönderdiğimizde,

Bizden istenen GPOST çıktısını görebilmekteyiz. Böylece,

Laboratuvar çözümünü tamamlıyoruz.

TE.TE behavior: obfuscating the TE header

Bu senaryoda ise frontend ve backend server’ların her ikisi de Transfer Encoding header’ını destekler, ancak server’lardan biri, header’ı bir şekilde gizleyerek onu işlememeye teşvik edilebilir.

Transfer-Encoding header’ını obfuscate etmenin potansiyel olarak sonsuz yolu vardır. Örneğin,

Transfer-Encoding: xchunked

Transfer-Encoding : chunked

Transfer-Encoding: chunked
Transfer-Encoding: x

Transfer-Encoding:[tab]chunked

[space]Transfer-Encoding: chunked

X: X[\n]Transfer-Encoding: chunked

Transfer-Encoding
: chunked

HTTP Spesifikasyonundan Sapma

  • Bu tekniklerin her biri, HTTP spesifikasyonundan küçük bir sapma içerir.
  • Gerçek dünya kodları, protokol spesifikasyonlarını uygularken nadiren tam kesin hareket eder.
  • Farklı uygulamalar, spesifikasyondan çeşitli sapmaları tolere eder.

TE.TE Zafiyetini Ortaya Çıkarmak

  • TE.TE zafiyetini ortaya çıkarmak için, Transfer-Encoding header’ının bazı varyasyonlarını bulmak gereklidir.
  • Öyle ki, frontend veya backend server’lardan yalnızca biri bunu işlerken diğer server bunu görmezden gelir.

Saldırının Yürütülmesi

  • Frontend server header’ı görmezden gelirse, saldırı CL.TE zafiyetine benzer şekilde yürütülür.
  • Backend server header’ı görmezden gelirse, saldırı TE.CL zafiyetine benzer şekilde yürütülür.

Lab: HTTP request smuggling, obfuscating the TE header

Laboratuvarımız, frontend ve backend server içerir ve iki server, duplicate HTTP request header’larını farklı şekillerde işler. Frontend server , GET veya POST method’u kullanmayan request’leri reddeder. Laboratuvar çözümü için bizden istenen sonraki request’in farklı bir karekterle başlatılarak XPOST gibi bir durumu yaşayacak mıyız bunu gözlemlemektir.

Bir önceki laboratuvarda hangi ayarların yapılması gerektiği ve uzunlukların nasıl ayarlanması gerektiğine dair ayrıntılı bir bilgi verdik. Aynı bilgiler bu laboratuvar için de geçerlidir.

GET / HTTP/2 request’imizi uygun hale getirelim Burpsuite ayarlarımızı yapalım ve duplicate biri obfuscate Transfer-encoding header’ını göndererek hangi server’ın nasıl davrandığını belirlemeye çalışalım.

Frontend ve backend server’ın hangi header’ı kullandığını anlamaya çalışalım.

POST / HTTP/1.1\r\n
Host: 0a9d002b0325b398831b00e900960025.web-security-academy.net\r\n
Content-Type: application/x-www-form-urlencoded\r\n
Content-length: 6\r\n
Transfer-Encoding: chunked\r\n
\r\n
3\r\n
abc\r\n
X\r\n

Request’ini gönderdiğimizde, response invalid request olarak döner bunun sebebi frontend server transfer-encoding kullanır ve 3 byte’lık veri okuduktan sonra bittiğini gösteren 0\r\n\r\n değerini arar ancak X uyumsuzdur bu sebeple hata verir.

Şimdi ise backend server’ın ne kullandığını anlamak için,

POST / HTTP/1.1\r\n
Host: 0a9d002b0325b398831b00e900960025.web-security-academy.net\r\n
Content-Type: application/x-www-form-urlencoded\r\n
Content-length: 6\r\n
Transfer-Encoding: chunked\r\n
\r\n
0\r\n
\r\n
X

Request’ini gönderelim. Bu request’te 0\r\n\r\n ile chunk’ın sonu belirtilir ve frontend server transfer-encoding kullanır ve backend server’a iletir. Burada başarılı bir response aldığımızı gözlemliyoruz. Bu durumda backend server’a iletilen veride X olmadığından ve response başarılı döndüğünden transfer-encoding kullanıldığını anlıyoruz. Çünkü content-length kullansaydı X işlenmediğinden request body’de 0\r\n\r\n = 5 byte’lık veri var ve content-length değeri 6 olduğundan timeout hatası almamız gerekirdi.

Şimdi obfuscate transfer-encoding header kullanarak frontend ve backend server’ların nasıl davrandığını kontrol edeceğiz. Beklentimiz obfuscated header’ı birinin farklı şekilde ele alması ve transfer-encoding yerine content-length’i kullanmayı seçmesidir. Son request’imize aşağıda görüldüğü şekilde,

POST / HTTP/1.1\r\n
Host: 0a9d002b0325b398831b00e900960025.web-security-academy.net\r\n
Content-Type: application/x-www-form-urlencoded\r\n
Content-length: 6\r\n
Transfer-Encoding: chunked\r\n
Transfer-Encoding: sefa\r\n
\r\n
0\r\n
\r\n
X

Transfer-Encoding: sefa\r\n header’ını eklediğimizde response’da timeout hatasını alıyor olmamız frontend server’ın transfer-encoding’i kullanırken backend server’ın content-length’i kullandığını gösteriyor. Frontend server obfuscate header’da daha iyimser davranırken backend server daha katı davranır ve transfer encoding header’ını reddeder. Bunun yerine content length’i işler. Bu durumda frontend server nginx backend server caddy kullanıyor olabilir ya da ikisi de nginx kullanıyordur ancak implementasyonları farklı olabilir veya farklı sürümler çalışıyor olabilir gibi durumları düşünebiliriz.

Sonuç olarak zafiyet çeşidimiz TE.CL zafiyetine dönüşmüş oldu. Aşağıdaki payload’ı gönderdiğimizde,

POST / HTTP/1.1\r\n
Host: YOUR-LAB-ID.web-security-academy.net\r\n
Content-Type: application/x-www-form-urlencoded\r\n
Content-length: 4\r\n
Transfer-Encoding: chunked\r\n
Transfer-encoding: sefa\r\n
\r\n
5c\r\n
GPOST / HTTP/1.1\r\n
Content-Type: application/x-www-form-urlencoded\r\n
Content-Length: 15\r\n
\r\n
x=1\r\n
0\r\n
\r\n

Transfer-encoding kullanan frontend server’ımız 5c byte’lık verimizi yani body’nin tamamını işleyecek ve backend server’a iletecektir. Bu request’i alan backend server transfer-encoding header’ını reddecek ve content-length’e göre işlemi yapacaktır. Yani işleyeceği veri 5c\r\n = 4 byte’lık veri olacaktır. İkinci kez aynı request’i gönderdiğimizde bir önceki request’ten kalan yani 4 byte’lık veriden sonrasını okumayan server, bu kısmı ikinci requestin başına ekleyecektir ve ,

Bizden istenen, Unrecognized method GPOST response'ını elde ederek,

Laboratuvar çözümünü tamamlıyoruz.

--

--