JWT Saldırıları

Yayınlayan
Hüseyin Kaan Karataş
Yayınlanma tarihi
25/7/2022
Okuma süresi
15
Dakika
go back icon
Geri Dön

JWT Saldırıları

JWT Nedir?

JSON Web Token (JWT), RFC 7519’de belirlenen standart ile taraflar arasında toplu bir şekilde JSON objesi halinde şifreli veya imzalı bilgi aktarılmasını sağlayan bir token türüdür. Bu bilgiler kullanılan dijital imza ile onaylanabilir. İmzalarda Public/Private anahtar çiftleri veya gizli anahtar ile doğrulanır. Aktarılan bilgiye "claim" adı verilir. JWT, kontrol JWS payload’ı olarak imzalanabilir veya JWE ile şifrelenebilir. Her zaman JWS ve JWE ile temsil edilir. Günümüzde en çok kullanılan yapı JWS’dir.

JWT Nerelerde Kullanılır?

JSON Web Token (JWT), URI sorgu parametresi, HTTP Authorization header gibi alanlarda claim bilgilerini temsil etmesi için oluşturulmuştur. Buradaki header veya sorgulara sığabilmesi için basit bir token olarak tasarlanmıştır. Bu şekilde sunucu, isteği yollayan kullanıcının yetkilerini ve kimliğini tek parametre içinde kontrol edebilir. Örneğin kullanıcı giriş yaptıktan sonra her isteğe JWT eklenir, eklenen JWT’nin claim bilgilerine bakılarak kullanıcının erişebileceği servis ve kaynaklar belirlenir, kullanıcı yetkisi bu şekilde kontrol edilir.

Örnek bir JWT, OAuth 2.0 ile access token olarak kullanıldığında aşağıdaki gibi görünür.

Örnek bir JWT token, Cookie içinde aşağıdaki gibi görünür.

JWT Yapısı

JWT, bir dizi claim bilgisini JSON nesnesi olarak temsil eden ve tek başına bir yapıya sahip olmayan bir tokendir. JWS veya JWE yapıları kullanılarak oluşturulur. En basit haliyle, JWS’de claim görülebilirken, JWE claim bilgilerini şifreleyerek gizli tutar.

JWS

JWT, JWS ile temsil ediliyorsa, claim bilgileri dijital olarak imzalanır veya hash tabanlı mesaj doğrulama kodu (Hash-based Message Authentication Code) yani HMAC kullanılarak korunur. Base64 ile encode edilir ve claim bilgileri decode edilerek görülebilir. JWS çok fazla kullanıldığından genellikle JWT olarak bilinen token JWS’dir.

Token, 3 parçadan oluşur ve her bölüm nokta ile birbirinden ayrılır.

  • Header (JOSE Header)
  • Payload
  • Signature

JOSE Header

JSON nesnesini temsil eden JWT hakkında bilgilerin bulunduğu ilk alana "Javascript Object Signing and Encryption (JOSE)" header adı verilir. Header, token hakkında algoritması, anahtar kimliği gibi temel bilgileri barındırır. Bu alanda genelde iki parametre bulunur. Bunlar JWT’nin türü ve algoritmasını belirtir. Tercihen farklı parametreler de eklenebilir. JWS ve JWE header bölümlerinde farklılıklar görülebilir. Aşağıdaki örnek token’in header kısmını base64 decode ederek inceleyelim.

  • Algoritma parametresi şifreleme veya imza algoritmasını belirtir. JWS’de kullanılabilecek algoritmalar JWA (RCF-7518) ile belirlenmiştir. Burada RS256 kullanıldığını görüyoruz.
  • Type parametresi, token türünü belirtir.
  • Header içinde genellikle bu iki parametre bulunur. Ancak gerektiğinde yukarıda görülen "kid" parametresi gibi farklı parametreler de eklenebilir.
  • Key ID (kid) parametresi JWS şifrelenirken hangi anahtarın kullanıldığını belirtir. Burada anahtara verilen kimliği JWK formatında tutar.
  • "JSON Web Key (jwk)", JWS’nin dijital imzasına karşılık gelen public anahtardır.
  • "JWK Set URL (jku)", JSON encode edilmiş public anahtarların kaynağını gösteren parametredir.

Payload

Payload, "claim"leri yani aktarılan bilgileri içerir. Buradaki bilgiler genellikle kullanıcı hakkında veriler ve gerekli bilgilerden oluşur. Claim içinde yer alan bilgiler her uygulamada farklıdır. Örneğin "OpenID Connect" iss, sub, aud gibi claimleri zorunlu tutarken diğer claimlerin isteğe bağlı olarak eklenebileceğini belirtir. Kimlik Sağlayıcısı belirlenen claimlerin içine ek olarak farklı parametreler de ekleyebilir. Üç farklı claim türü (registered, public ve private) bulunmaktadır.

Registered Claim: Kayıtlı olan bu claim türünden yedi tane vardır. Zorunlu tutulmasa da üçüncü parti uygulamalarla birlikte çalışabilirliği artıracağından için kullanılması önerilmektedir.

Public Claim: Bu claim türü kayıtlı olan claim isimleriyle çakışmayacak şekilde tercihen oluşturulan claim isimlerini içerir.

Private Claim: Tamamen bir uygulamaya özgü bilgileri içeren claim oluşturulabilir. Kayıtlı claim isimleriyle çakışmaya dikkat edilmeden eklenebilir. Örnekteki token payload kısmını decode ederek inceleyelim;

  • "Issuer (iss)", JWT’nin kim tarafından hazırlandığını belirtir.
  • "Audience (aud)", alıcının kim olduğunu belirtir. Örnekteki gibi bir URI verildiğinde, verilen URI bu JWT ile ulaşılabilecek adres olduğundan aynı JWT ile farklı bir URI’a istek gönderildiğinde kabul edilmeyecektir. Bir adres yerine, birkaç adresi içeren liste de değer olarak verilebilir.
  • "Subject (sub)", JWT’nin kim için oluşturulduğunu belirtir. Burada kullanıcı adı, isim soy isim veya kullanıcıya atanan farklı bir değer görülebilir.
  • "Issued At (iat)", oluşturulduğu tarihi ve zamanı belirtir.
  • "Expiration Time (exp)", ne zaman süresinin dolacağını belirtir.
  • "Not Before Time (nbf)", verilen tarihten önce JWT kullanılamaz, verilen zaman geldiğinde JWT geçerli olacaktır.
  • "JWT ID (JTI)", token’e özel kimlik numarasıdır. Aynı token’den bir tane daha üretilmemesini sağlar.

Buraya kadar bahsedilen claim isimleri kayıtlı ve kullanılması önerilen claim isimleridir. Son kısımda yer alan üç claim ise uygulamaya özel olarak üretilmiş, public claim isimleridir. JWT payload alanında, uygulama için gerekli olan birçok bilgiyi taşıyabilir.

Yukarıdaki örnekteki gibi isim, e-posta veya rol dışında hem kayıtlı public hem de tercihen daha farklı oluşturulabilecek private claim isimleri yer alabilir. Bunlar, sessionID veya clientID gibi kullanıcıya atanan bilgiler olabileceği gibi kullanıcının telefon numarası, cinsiyeti, adresi gibi bilgiler de olabilir. Ancak, JWT çok büyük olmaması için tasarlanması sebebiyle gerektiğinden fazla bilgi JWT içerisinde tutulmaz. Yukarıdaki token üç claim türüne de sahiptir.

Signature

Son kısımda imza yer alır. JWT’yi gönderen kişinin kimliğini doğrulamak ve içeriğinin değiştirilmediğini onaylamak için kullanılır. HMAC algoritmasında header ve payload base64 ile encode edildikten sonra bir secret anahtarla oluşturulur. RSA ise gizli bir anahtarla imza oluştururken, public anahtarla imzayı doğrulama işlemini gerçekleştirir.

JWT Saldırı Türleri

Yanlış Yapılandırılmış Doğrulama (CVE-2015-9235)

JWT kütüphanelerinde en basit haliyle, decode() ve verify() fonksiyonları kullanılarak JWT token’ları onaylanabilir. Ancak, yanlış yapılandırılırsa, saldırganlar tarafından farklı saldırılara açık hale gelebilir. Örneğin, doğrulamayı düzgün yapmayan veya doğrulama kullanmayan bir JWT’nin claim bilgileri değiştirildiğinde yetkisiz erişim gibi zafiyetlere yol açabilir. JWT header parametresi olan Algoritma, sunucuya token imzalanırken hangi algoritmanın kullanıldığının bilgisini verir. Sunucu, bu şekilde imzayı hangi algoritma ile doğrulayacağını bilir. Header kısmında bulunan Algoritma parametresi bazı kütüphanelerde veya yanlış yapılandırılmış yerlerde "none" değerini kabul edebilir. Bu değer, Algoritma parametresinin imza için bir algoritma seçmemesine yol açar. Sonucunda ise sunucu, kullanıcıdan aldığı girdiye güveneceğinden dolayı saldırgan kendi doğrulanmış ve imzalı token’i istediği claim ile üretebilir hale gelecektir.

,

Örnek olarak bu lab giriş yapıldıktan sonra kullanıcıya bir JWT token veriyor.

JWT Editor adlı Burp Suite eklentisini kullanarak incelediğimizde RS256 ile oluşturulmuş ve kullanıcı adını tutan basit bir token görüyoruz.

İmza, header ve payload alanlarındaki bilgilerle oluşturulduğundan en ufak değişiklikte token geçersiz sayılacaktır. Elimizde herhangi bir bilgi olmadığından, "none" algoritmasını kullanabiliriz. Aynı eklenti kullanılarak veya manuel olarak "none" algoritması eklenebilir.

JWT kütüphanesindeki yanlış yapılandırmadan dolayı, "none" algoritması geçerli sayılarak token doğrulanmış kabul ediliyor ve admin kullanıcısına erişilmesini sağlıyor.

Buradaki "none" değeri, string bir ifade olduğundan filtreyi atlatmak için, "obfuscation" denilen şaşırtma teknikleri kullanılabilir. Örneğin küçük ve büyük harfleri birlikte kullanarak veya bunları encode ederek test edilebilir. Ayrıca, Burp Suite üzerinden "JSON Web Tokens" eklentisi de alternatif olarak kullanılabilir.

Zayıf Anahtar Kullanımı

Bazı uygulamaların kullandığı HMAC simetrik algoritması (örneğin HS256) token’i imzalar ancak kullanılan anahtar kolayca kırılabilecek veya tahmin edilebilecek şekilde üretilmiş olabilir. Claim bilgilerinin değiştirilmemesini ve güvenli şekilde aktarılmasını amaçlayan bu yapıda JWT’nin brute-force saldırısına açık halde olması saldırganın anahtara ulaşması ve kendi token’ini istediği claim ile üretip yetki kazanmasıyla sonuçlanabilir. Zayıf anahtarlar, hashcat, John The Ripper, JWT cracker, JWT tool kullanılarak test edilebilir. Buradaki araçlar farklı wordlist veya kendi scriptleri ile farklı anahtarları kullanarak token’i imzalar ardından sunucudan dönen imza ile eşleştirir.

Yukarıdaki lab, yine kullanıcı giriş yaptıktan sonra bir JWT token veriyor.

Algoritma parametresinde HS256 olduğundan, farklı araçlar kullanarak, secret’ı bulup ardından kendimiz başka bir token’i imzalayabiliriz. ". hashcat -a 0 -m 16500 [JWT TOKEN] [wordlist]"

Hashcat ile şifrenin iloveyou olduğunu görüyoruz. Şimdi, kendi oluşturduğumuz token ile admin hesabına erişmeyi deneyebiliriz.

Kendimiz bir token oluşturmak için, JWT Editor eklentisini kullanabiliriz. Öncelikle bulduğumuz "secret"ı base64 ile encode ediyoruz, ardından bunu "k" parametresinin değeri ile değiştiriyoruz.

Token imzalandıktan sonra, "name" claim değerini "admin" yapıp ardından admin kullanıcısına erişiyoruz.

Algoritma Karıştırma (Algorithm Confusion) - CVE-2016-5431

JWS simetrik ve asimetrik algoritmaları destekler. Simetrik yani HMAC, "secret" anahtarı hem imzalama hem de doğrulamada kullanırken asimetrik algoritmalar, örneğin RSA public-private anahtar çiftini kullanır. Private anahtar ile token’i imzalar ardından public anahtarla doğrulama işlemini gerçekleştirir. JWT kütüphaneleri;

  • HMAC  >  verify(token, secret)  
  • RSA     >  verify(token, publicKey)  gibi metotlar kullanarak imzayı doğrularlar.

Eğer, algoritma parametresi, belirlenen algoritmanın değiştirip değiştirilmediğini kontrol etmiyorsa bu saldırganın, algoritma parametresinde belirlenen algoritmayı değiştirip imzayı doğrulamasına yol açabilir. Yanlış yapılandırılma durumunda eğer public anahtara veya anahtar setine uygulamadan erişebiliyorsa, algoritmayı değiştirerek saldırı yapılabilir. Örneğin, RS256 algoritması HS256 ile değiştirildiğinde, "verify()" public anahtarı, HS256 secret anahtarı yerine kullanıp token’i imzalayacak ardından aynı anahtarla imzayı doğrulayacaktır.

Verilen JWT token’i, JWT Editor ile incelediğimizde header kısmında RS256 algoritması, "kid" parametresini görüyoruz. Key ID parametresi birden fazla anahtar olabileceğini gösteriyor, eğer bu anahtarlar güvensiz bir şekilde tutuluyorsa, JWK setine ulaşılabilir.

Farklı endpointleri taradıktan sonra JWK setinin tutulduğu yere ulaşıyoruz. Şimdi bu seti kullanarak yeni bir anahtar oluşturabiliriz.

Yeni RSA anahtarını JWK Editor ile yukarıdaki gibi elde ettiğimiz seti kullanarak oluşturabiliriz. Algoritmayı HS256 ile değiştireceğimiz için, oluşturduğumuz anahtarın "PEM" anahtarını alıp base64 ile encode etmeliyiz. Ardından, yeni bir simetrik anahtar oluşturup "k" parametresine, encode ettiğimiz "PEM" anahtarını koyuyoruz. HS256 algoritmasıyla yeni bir anahtar oluşturduk.

Sırada claim değerini değiştirerek imzalamak var.

Anahtarı yukarıdaki gibi kullanıcı adını "administrator" ile değiştirip, JWT Editor eklentisi ile imzalıyoruz.

JWT token imzalandıktan sonra, admin paneline istek attığımızda, token imzasının doğrulandığını ve panele erişebildiğimizi görüyoruz.

JWT Header Parameter Injection

JOSE header kısmında, birçok parametre alabilse de algoritma parametresi hariç diğer parametreler tercihe bırakılmıştır. Eğer JWT gerekli header parametreleri dışındaki parametreleri de kabul ediyor veya parametrelerin değerlerini doğrulamıyorsa, kullanıcı tarafından kontrol edilebilir "kid", "jku" ve "jwk" parametreleri saldırgan tarafından token içine eklenebilir veya değerleri değiştirilebilir.

JSON Web Key (JWK) parametresi, anahtarı temsil eden bir JSON nesnesidir ve tercihen eklenebilir. Anahtarı token’in içine JWK formatı ile gömülebilir veya farklı bir yerde saklanabilir.

Örnek bir JWK header içinde şu şekilde gözükür;

Doğru yapılandırılmamış sunucular, JWK parametresiyle gömülen veriyi, anahtar olarak kullanabilir. Bunu kullanarak saldırganlar kendi tokenlerini, ekledikleri parametre ile imzalayabilir ve doğrulayabilir. JWT tool, JWT Web Tokens ve JWT Editor gibi araçlar kullanılarak test edilebilir.

JWK yanlış yapılandırıldığında oluşabilecek durumu daha iyi anlayabilmek için örnekteki lab senaryosunu inceleyelim. Öncelikle kullanıcıya verilen token JWT Editor veya benzeri bir araçla incelendiğinde, "algoritma" ve "kid" parametrelerini görüyoruz. JWK parametresini test edebilmek için yeni bir asimetrik anahtar oluşturmamız gerekiyor.

Anahtarı Burp Suite üzerinden yukarıdaki gibi oluşturduktan sonra, repeater sekmesi üzerinden anahtarı, JWK formatında token’e gömmemiz gerekiyor.

Repeater sekmesinde eklentiyi kullanarak, attack seçeneği üzerinden "Embedded JWK" saldırısını seçip, JWK formatındaki anahtarı token’e gömüyoruz. Claim’i "administrator" olarak ayarladıktan sonra, isteği admin paneline yollayabiliriz.

İmzamızın doğrulandığını ve admin paneline erişebildiğimizi istek üzerinde görüyoruz.

Key ID (kid) Parametresi

Sunucu birden fazla anahtarı depoladığında, imzayı doğrulayacağı zaman hangi anahtarı kullanacağını Key ID değeri ile belirler. Key ID değer olarak bir string ifade alabileceği gibi bir dosya, dizin veya URL de alabilir. "kid" parametresi aşağıdaki gibi değerler alabilir.

Örnektekine benzer dizin gibi tahmin edilebilir bir değer varsa, farklı değerlerle kontrol edip bir anahtarı sızdırıp sızdırmadığı test edilebilir. Aynı şekilde path belirtiliyor ise "directory traversal" zafiyeti tetiklenebilir. Eğer "directory traversal" zafiyetine açık haldeyse, farklı bir dizin belirtilerek başka bir dosya anahtar olarak gösterilebilir ve bu dosya içeriği ile imza doğrulanabilir. Depolanan anahtarlar genelde JWK formatındadır.

Parametre manipüle edilerek, farklı bir dosyanın içeriği ile JWT imzası doğrulanmış olur. Örneğin, "/dev/null" boş bir dosyadır. Bu dosyanın içeriği boş olduğundan, "Key Value (k)" parametresine base64 ile encode edilmiş null byte değeri (AA==) verilirse, dosya içeriği ile anahtarın değeri eşleştirilmiş olur. Bunun sonucunda, token imzalanmış ve doğrulanmış olacaktır.

Lab içinde, yukarıda verilen token kid parametresi kullanıyor. Kid parametresini boş bir sayfayı işaret edecek şekilde değiştirmemiz mümkün olabilir. Öncelikle yeni bir simetrik anahtar oluşturuyoruz.

Anahtarın, "k" parametresine null byte değerini veriyoruz.

Şimdi, kullanıcı adı bilgisini administrator ile değiştirip, "/dev/null" değerini "kid" parametresine veriyoruz. "kid" parametresi ve imza eşleşeceği için eğer bir zafiyet var ise JWT doğrulanacaktır.

JWT imzalandıktan sonra, admin paneline erişebildiğimizi görüyoruz. Key ID parametresi eğer, kullanıcıdan alınan girdiyi doğrulamıyor veya filtrelemiyorsa SQL veya LDAP Injection zafiyetlerine de yol açabilir. Bu şekilde saldırganlar birçok sayıda geçerli token’a ulaşabilir, yetki yükseltebilir, farklı kullanıcıların hesaplarına erişebilir veya veri tabanındaki verilere erişip okuyabilir.

Örneğin;

Key ID parametresi, JWT anahtarını,

SELECT key FROM keys WHERE key='key1'

gibi bir SQL sorgusu ile getiriyorsa,

kid": "test ' UNION SELECT 'XXXX';-- ‘"

değeri yukarıdaki gibi bir payload ile değiştirildiğinde, secret anahtarı "XXXX" olur ve JWT bu anahtar kullanılarak imzalanabilir hale gelir.

JWK SET URL (JKU) Parametresi

JKU parametresi JWK formatında olan public anahtarların tutulduğu yeri URL ile gösterir. JKU tercihen eklenir. Bazı sunucular JKU parametresinin, eklenmesine izin verebilir. Eğer güvenli şekilde yapılandırılmamışsa, bir saldırgan JKU değerini başka bir URL ile değiştirip, kendi JWK seti ile token’ini doğrulayabilir. URL kontrolü yapmadan direkt verilen adrese gidilmesi, "SSRF" veya "open-redirect" gibi daha zararlı zafiyetlere yol açabilir.

Örneğin header içinde jku parametresi aşağıdaki gibi gözükür.

JKU parametresinde çıkabilecek bir zafiyeti daha iyi anlayabilmek için örnek labı inceleyebiliriz.

Bize verilen token, içinde JKU parametresini içermiyor. Ancak, başta da bahsedildiği gibi parametreleri kendimiz de ekleyebiliriz. Öncelikle bir anahtar oluşturalım.

Yeni bir RSA anahtarı oluşturduktan sonra bu anahtara sağ tıklayıp JWK formatında kopyalamamız ve exploit sunucusuna yüklememiz gerekmektedir. Eğer JKU parametresi kabul edilirse token doğrulamak için bizim sağladığımız sunucuya gidip bizim oluşturduğumuz JWK setini kullanacaktır.

Sunucuya anahtarı JWK formatında yükledikten sonra, "kid" parametrelerini eşleştiriyoruz. Ardından kullanıcı adı bilgisini "administrator" olarak güncelleyip, "jku" parametresi ekliyoruz.

Son olarak, token’i imzalayıp, admin paneline istekte bulunduğumuzda panele ulaşabildiğimizi görüyoruz.

Eğer "jku" parametresine verilen değer kabul edilmiyorsa aşağıdaki gibi farklı tekniklerle, bypass denemesi yapılabilir.

  • "jku": "https[:]//example[@]saldırgan[.]com/key.json"
  • "jku": "https[:]//example[#]saldırgan[.]com/key.json"
  • "jku": "https[:]//example[.]saldırgan[.]com/key.json"
Hüseyin Kaan Karataş

Detaylı Bilgi İçin

info@gaissecurity.com