Ortaya çıkardığım ve Razer’ın güvenlik açığı açıklama programına bildirdiğim bir hatanın eski bir hikayesi, yakın zamanda sohbet ederken yeniden ortaya çıktı. Linus Sarud. 2017 yılında bir JavaScript kodu pasajını ortaya çıkardım. deals.razerzone.com
bir kullanıcı oturum açtıktan sonra yeniden yönlendirmeyi gerçekleştirdi.
// let rurl = document.location.href;
if (razerUserLogin) {
rurl = rurl.split("rurl=")[1];
location.href = decodeURIComponent(rurl);
}
Kod, değeri şuradan çıkardı: rurl
GET parametresini belirledim ve kullanıcıyı bu GET parametresinin değerine yönlendirdim. Örneğin, https://deals.razerzone.com/?rurl=https://deals.razerzone.com/settings
şuraya yönlendirir: https://deals.razerzone.com/settings
.
let rurl =
"https://deals.razerzone.com/?rurl=https://deals.razerzone.com/settings";
rurl.split("rurl=");
//=> [ 'https://deals.razerzone.com/?', 'https://deals.razerzone.com/settings' ]
rurl.split("rurl=")[1];
//=> 'https://deals.razerzone.com/settings'
Yönlendirme uç noktasının doğrulanmaması nedeniyle bariz açık yönlendirmenin yanı sıra (rurl
), bu kod DOM tabanlı XSS’ye karşı savunmasızdı.
Ayarlama window.location.href
bir mülk javascript:
protokol URI’si, hedef web uygulaması bağlamında JavaScript kodunu çalıştıracaktır. Bu kadar basit bir şey https://deals.razerzone.com/?rurl=javascript:alert(document.domain)
kullanıcıyı mevcut sayfanın bilgilerini görüntüleyen bir uyarı mesajıyla yönlendirir document.domain
.
Razer, güvenlik açığını aşağıdaki yöntemle düzeltmeye çalıştı if
ifade.
// let rurl = document.location.href;
// let siteURL = 'https://deals.razerzone.com';
if (razerUserLogin) {
rurl = rurl.split("rurl=")[1];
rurl = decodeURIComponent(rurl);
if (
rurl.indexOf(siteURL) > -1 &&
rurl.split("://")[1].split("/")[0] === siteURL.split("://")[1].split("/")[0]
) {
location.href = rurl;
}
}
Yazarın notu: Bu noktanın ötesini okumaya devam etmeden önce, okuyucunun kodla oynamasını ve doğrulamanın atlanıp atlanamayacağını belirlemesini tavsiye ediyorum. Lütfen yanıt vermekten çekinmeyin bu tweet bypass’ınızla.
Yukarıdaki kodu gözden geçirmek, okuyucunun bir geliştiricinin neden böyle bir kod yazdığını düşünmesine neden olabilir. İyi kodlama uygulamalarına ilişkin literatürün çoğu, “iyi kodda herhangi bir sürpriz olmamalıdır” şeklinde bir şeyden söz eder. Daha da önemlisi, amaç ( “Neden?”Yukarıda vurgulanan gibi önemli bir kod parçasının ) bir biçimde belgelenmesi gerekir. Daha da iyisi: Mümkünse kodun kendisini belgelemesi gerekir.
Razer’ın denediği yama tüm hesaplarda başarısız oldu. Neden ayrıştırma rurl
yerleşik sisteme güvenmek yerine manuel olarak URL
API’mi? Bütün iç içe geçmiş olanlar ne yapar? split()
yöntemlerden alıntı rurl
?
Kod incelemesi yaparken geliştiricinin aklından neler geçtiğini daha iyi anlamak isterim. Yani yukarıdaki kodun amacını belirleyip aşağıdaki soruyu cevaplamamız gerekiyor. “Neden?”.
-
rurl.indexOf(siteURL) > -1
bir bakıma kullanıcı tarafından sağlanan yönlendirme URL’siyle eşleşen bulanıktır (rurl
) güvenilir URL’nin (siteURL
) dizede mevcut. Geliştirici cevap vermeye çalışıyordu: Güvenilir misiteURL
kullanıcı tarafından sağlanan bir alt dizerurl
? -
rurl.split("://")[1].split("/")[0]
ana bilgisayar adını kullanıcı tarafından sağlanan yönlendirme URL’sinden çıkarma ve bunu güvenilir kaynaktaki ana bilgisayar adıyla karşılaştırma girişimidir.siteURL
.rurl.split("://")[1]
URL’nin protokol şeması bölümünü kaldırması gerekiyor (örn.https:
), Ve.split("/")[0]
ana bilgisayar adını gösteren URL yolunu atar.
let rurl = "https://example.com/settings";
rurl.split("://");
//=> [ 'https', 'example.com/settings' ]
rurl.split("://")[1];
//=> 'example.com/settings'
rurl.split("://")[1].split("/");
//=> [ 'example.com', 'settings' ]
rurl.split("://")[1].split("/")[0];
//=> 'example.com'
Geliştiricinin güvenilir URL’nin mevcut olup olmadığını belirlemeye çalıştığı anlaşılıyor rurl
ve eğer ana bilgisayar adı rurl
güvenilir ana bilgisayarlarıyla eşleşti (deals.razerzone.com
).
Ne yazık ki, bu doğrulama çeşitli şekillerde atlanabilir. indexOf()
check-in (1.) yalnızca gerekli https://deals.razerzone.com
bir yerde mevcut olmak rurl
; kesinlikle dizenin başında değil. Adım (2.), ana bilgisayar adını ilk kez ortaya çıktıktan sonra çıkaracaktır. ://
. Bu yüzden rurl
hala başlayabilir javascript:
. Fakat, ://deals.razerzone.com/
başka bir olay meydana gelmeden önce yükün bir noktasında görünmesi gerekir ://
.
let rurl = "javascript://deals.razerzone.com/";
rurl.split("://");
//=> [ 'javascript', 'deals.razerzone.com/' ]
rurl.split("://")[1];
//=> 'deals.razerzone.com/'
rurl.split("://")[1].split("/");
//=> [ 'deals.razerzone.com', '' ]
rurl.split("://")[1].split("/")[0];
("deals.razerzone.com");
Aşağıdaki bu kod parçasında vurgulanan sözdiziminin verdiği gibi, //
tek satırlık bir yorum olarak kabul edilir ve bu nedenle deals.razerzone.com/
yükün bir kısmı.
javascript://deals.razerzone.com/
Daha sonra, tek satırlık yorumdan kurtulmanın ve JavaScript kodunu eklemenin bir yoluna ihtiyacımız vardı. Bunun için bir numara öğrendim Gareth Heyes: JavaScript şunları ele alır: U+2028
Hat Ayırıcı karakter, yeni satırla sonuçlanan satır sonlandırıcı olarak kullanılır.
javascript://deals.razerzone.com/%E2%80%A8alert(document.domain)
Tüm söylenenlerle birlikte, satır besleme de dahil olmak üzere herhangi bir türdeki satır sonlandırıcı burada işe yarayabilirdi (%0A
) ve satır başı (%0D
). hoşuma gitti U+2028
hile çünkü yeni satırların çıkarıldığı durumlarla karşılaştım ve bu davranışı kullanarak atlamam gerekiyordu. U+2028
.
Son olarak, bypass etmek için indexOf()
check-in (1.), eklenebilir https://deals.razerzone.com
yükün sonuna kadar ekleyin ve onu etkilemeyecek şekilde yorum yapın. alert()
Arama.
javascript://deals.razerzone.com/%E2%80%A8alert(document.domain)//https://deals.razerzone.com
Bu, birçok yoldan birini göstermektedir. if
ifade atlanabilirdi. Bu kadar basit bir şey javascript:alert()//https://deals.razerzone.com
da işe yarardı. Keşfettiğim daha basit ve daha esprili bir geçiş yoluydu: javascript:alert("https://deals.razerzone.com/")
. Bunun neden işe yarayacağını belirleyebilir misiniz?
Savunmasız kod için hızlı bir düzeltme doğrulamak olabilirdi rurl.indexOf(siteURL) == 0
ve sabit kod siteURL
ile https://deals.razerzone.com/
(ektekilere dikkat edin /
). Bu garanti ederdi rurl
ile başladı https://deals.razerzone.com/
harici ana bilgisayarlara yönlendirmeleri engelliyor ve DOM tabanlı XSS güvenlik açığını azaltıyor.
Ancak bu hızlı düzeltme, gelecekteki okuyucuların kafasını karıştırma sorununu çözmez. Ayrıca kod inanılmaz derecede kırılgandır ve geleceğe yönelik değildir. Büyük ölçüde güveniyoruz /
sonunda siteURL
. Finali kaldır /
itibaren siteURL
ve bütün düzen bozulur. Bu yaklaşım daha çok bir şeye benziyor “kesmek”.
kullanarak daha yeterli bir düzeltme URL
API şöyle olabilir:
if (razerUserLogin) {
let params = new URL(document.location).searchParams;
let rurl = params.get("rurl");
rurl = new URL(rurl);
// Validate redirect URI to ensure user is redirected to trusted
// deals.razerzone.com endpoint. This prevents unvalidated redirects
// to malicious pages and DOM-based XSS using the javascript: protocol.
// Reference: https://hackerone.com/reports/292200
if (rurl.hostname == "deals.razerzone.com" && rurl.protocol == "https:") {
location.href = rurl;
}
}
Bu çözüm açıklıyor Neden the if
ifadesi gereklidir, kod değişikliklerini tetikleyen HackerOne raporuna atıfta bulunur ve iç içe geçmiş durumun derinliklerinde gizlenen herhangi bir sürpriz yoktur. split()
yöntemler. Gelecekte bu kodu inceleyen bir geliştiricinin bu aşamalardan geçmesine gerek yoktur. split()
başlık altında neler olup bittiğini anlamak için (2.)’de açıklandığı gibi yöntem çağrıları.
Daha öte, @filedescriptor bu uygulamanın aynı zamanda Razer’ın başlatma biçimini de ele aldığını belirtti rurl
ile location.hash
. ayrıştırma rurl
Razer’ın yaklaşımını kullanarak URI’den bilgi almak, URI parçalarında zorluklara yol açabilirdi (ör. /#rurl=
)—bir saldırganın XSS yükünü güvenlik duvarı kurallarından ve sunucu günlüklerinden gizlemesine olanak tanıyan bir yaklaşım.
Daha rafine çözüm muhtemelen ayarlamak olurdu location.href
ile 'https://deals.razerzone.com/' + rurl
Neresi rurl = new URL(rurl).pathname
. Daha sonra, ne sağlanırsa sağlansın rurl
GET parametresi kullanıldığında, istemci her zaman üzerinde bulunan bir uç noktaya yönlendirilir. deals.razerzone.com
. Bu bizi herhangi bir doğrulama yazma zorunluluğundan kurtarırdı.
Çözüm
Bu blog yazısının amacının bir kısmı, kullandığım kodu daha iyi anlamak için bir yama tasarlamayı nasıl dahil ettiğimi göstermekti. Çoğu zaman, tamamen istismara odaklanan ve azaltma stratejisine çok az odaklanan tavsiyeler ve güvenlik açığı raporları yayınlanır. Güvenlik açığının açıklanmasına yeni başlayanlar için: Umarım bu blog yazısı benim fikrimi gösterir. “yapmayı öğren; o zaman kır onu” Güvenlik araştırmalarına yaklaşım. Çok sayıda JavaScript kodunu ve bu konudaki aşinalığımı gözden geçirmek URL
API, Razer’ın yamasıyla ilgili sorunları daha kolay tanımamı sağladı.
Ayrıca, güvenlik açığı raporlarını gönderirken satıcılara yama önerme konusunda da başarılı oldum. Somut azaltma adımlarının dahil edilmesi, hata ödül programlarına rapor verirken çözüm süresini ve dolayısıyla ödeme süresini kısaltabilir. Yamanın etkilenen ürünlerdeki mevcut uygulamaya alternatif bir yaklaşımı kapsaması nedeniyle satıcıların genellikle daha anlayışlı olduğunu düşünüyorum. Bu, atölyelerimde öğrencilere düzenli olarak yapmalarını tavsiye ettiğim bir şey.
Sonuçta, bir güvenlik açığını çözmek için kodu yeniden düzenlemeye ne kadar yatırım yaparsanız yapın, eninde sonunda en basit çözüm her zaman ortaya çıkacaktır.
❯ curl https://deals.razerzone.com/
curl: (6) Could not resolve host: deals.razerzone.com