event.origin, sabit ve bağlantı noktası normalleştirilmiş bir dize değişmeziyle (ör. event.origin === 'https://app.example.com') karşılaştırıldığında basit izin verilenler listeleri için işe yarar. Ancak rastgele URL'ler, DOM bağlantı öğeleri, alt alan adları arası yönlendirmeler veya korumalı alana alınmış mesajlar doğrulanırken manuel dize karşılaştırmaları ve normal ifadeler başarısız olur:
- Önek ve alt dize sahtekarlığı:
url.startsWith('https://app.example.com')kontrolü'https://app.example.com.attacker.example'tarafından atlanır. - Normalleştirilmemiş URL'ler: Yollar, sorgu dizeleri veya açık varsayılan bağlantı noktaları (
'https://app.example.com:443/path') içeren tam URL'ler, önce ayrıştırılıp normalleştirilmeden dize eşitliği kapsamında'https://app.example.com'ile eşleşmez. - Herkese Açık Sonek Listesi hataları: Aynı site ilişkilerini kontrol etmek için ana makine adlarını
.ile bölmek, çok etiketli herkese açık soneklerde (ör..co.ukveya.github.iogibi paylaşılan barındırma alan adları) başarısız olur ve şema uyuşmazlıklarını (http:vehttps:) yoksayar. - Opak
"null"kaynak çakışmaları: Korumalı alana alınmış iFrame'ler (<iframe sandbox="allow-scripts">) vedata:URL'ler,event.origindizesini"null"dizesine dönüştürür.originA === originBile iki serileştirilmiş çalışma zamanı kaynağını karşılaştırmak, iki alakasız opak bağlamı ("null" === "null") yanlış bir şekilde eşitler.
Origin API'si (Origin.from(), isSameOrigin(), isSameSite() ve opaque), tarayıcının URL ayrıştırıcısı ve Public Suffix List tarafından desteklenen, yapılandırılmış, spesifikasyonlara uygun kaynak ve şemalı aynı site karşılaştırmaları sağlar.
Senaryonuz için doğru karşılaştırmayı seçme
- Katı ayrıcalık sınırları için
isSameOrigin()kullanın: Pencereler arasıpostMessagekomutlarını (ör. yerleşik ödeme veya kimlik doğrulama pop-up'ı) işlerken ya da kimlik bilgilerinin eklenip eklenmeyeceğine karar verirken tam(scheme, host, port)eşleşmesi zorunlu kılın. Farklı bir alt alan adı bile (https://blog.example.comvehttps://app.example.com) farklı bir kaynaktır ve reddedilmelidir. Böylece bir alt alan adındaki güvenlik açığı diğerini kontrol edemez. - Alt alan adları arası gezinme ve bağlantı sınıflandırması için
isSameSite()kullanın: Giriş sonrası yönlendirme URL'lerini (?returnTo=https://billing.example.co.uk/orders) doğrularken veya bağlantıları dahili ya da harici olarak sınıflandırırken aynı şemayı ve kaydedilebilir alan adını paylaşan tüm alt alan adlarına veya bağlantı noktalarına izin verin.isSameSite(), hemhttp:şema sürüm düşürmelerini hem de paylaşılan herkese açık son eklerdeki (.co.uk,.github.io) eş kiracıları reddeder. - Opak veya korumalı alan bağlamları için
origin.opaqueveOrigin.from(event)kullanın: Yönlendirmeler (origin.opaque === true) içindata:URL'lerini reddedin veya serileştirilmişevent.origindeğeri"null"olan belirli bir<iframe sandbox="allow-scripts">widget'ıyla iletişim kurarkenOrigin.from(event)aracılığıyla ayıklanan opakOrigindeğerini sabitleyin.
1. Ayrıcalıklı İşlemler İçin Tam Kaynakları Doğrulama (Origin.from() ve isSameOrigin())
Bir Origin nesnesini ayıklamak ve isSameOrigin() kullanarak güvenilir bir Origin ile karşılaştırmak için Origin.from() kullanın:
- Her iki taraf da
Originörneği olmalıdır:isSameOrigin(other)veisSameSite(other)yalnızca birOriginnesnesi kabul eder. Ham dize iletildiğindeTypeErrorhatası oluşur. - Desteklenen
Origin.from()girişleri:- Serileştirilmiş URL dizeleri (ör.
'https://app.example.com:443/path'). Geçersiz URL dizelerinin (ör.'example'veya opak kaynaklar içinevent.origintarafından serileştirilen'null'dizesi)Origin.from()yöntemininTypeErrorhatası vermesine neden olacağını unutmayın. - HTML spesifikasyonunda kaynak ayıklama adımlarını tanımlayan platform nesneleri:
Origin,URL,HTMLAnchorElement(<a>),HTMLAreaElement(<area>), aynı kaynakWindowveyaWorkerGlobalScope(Origin.from(self)) ve tarayıcı tarafından gönderilenMessageEventörnekleri (Origin.from(event)). Location,WorkerLocation,Document,RequestveResponsegibi nesneler, kaynak ayıklama adımlarını tanımlamaz ve doğrudanOrigin.from()yöntemine iletilirseTypeErrorhatası verir. Bunun yerineself(mevcut bağlam için) veyarequest.url/response.urlyöntemini iletin.
- Serileştirilmiş URL dizeleri (ör.
- Hem tarayıcı tarafından gönderilen hem de yapay
MessageEventörneklerini işleyin: Gerçek tarayıcıpostMessageetkinlikleri,Origin.from(event)tarafından ayıklanan dahili bir kaynak taşır. Birim testlerinde oluşturulan yapaynew MessageEvent('message', { origin })etkinlikleri ise yalnızcaevent.origindizesini doldurur veOrigin.from(event)üzerinde hata verir.try...catchiçindeOrigin.from(event.origin)kullanmak, geçersiz veya"null"dizelerini reddederken her ikisini de düzgün bir şekilde işler.
// Replace with your application's trusted origin(s)
const TRUSTED_ORIGIN = Origin.from('https://app.example.com');
function extractOrigin(candidate) {
try {
return Origin.from(candidate);
} catch (err) {
// Synthetic new MessageEvent('message', { origin }) objects in tests lack an
// internal browser origin; fall back to parsing candidate.origin if it is a URL.
if (typeof MessageEvent !== 'undefined' && candidate instanceof MessageEvent) {
return Origin.from(candidate.origin);
}
throw err;
}
}
export function isTrustedSameOrigin(candidate) {
try {
return extractOrigin(candidate).isSameOrigin(TRUSTED_ORIGIN);
} catch {
// Rejects invalid URLs, "null" strings, or unsupported objects
return false;
}
}
window.addEventListener('message', (event) => {
if (!isTrustedSameOrigin(event)) return;
// Safe to process privileged commands from the verified origin
});
2. Alt Alan Adları Arası Yönlendirmeleri ve Bağlantıları Doğrulama (isSameSite())
Yönlendirme hedeflerini (ör. ?returnTo= parametreleri) veya aynı kayıtlı alanın alt alan adlarındaki (ör. https://app.example.co.uk ve https://billing.example.co.uk:8443) dahili bağlantıları doğrularken isSameSite() kullanın:
isSameSite(), şemalı aynı site karşılaştırması yapar:https://sub.example.comvehttp://sub.example.com, şemaları farklı olduğu için aynı site değildir.isSameSite(), tarayıcının yerleşik Herkese Açık Sonek Listesi'ni kullanır. Bu nedenle, paylaşılan herkese açık soneklerdeki farklı kiracılar (ör.https://tenant-a.github.iovehttps://tenant-b.github.ioveyahttps://a.co.ukvehttps://b.co.uk)falseolarak değerlendirilir.
// Replace with your application's primary origin
const APP_ORIGIN = Origin.from('https://app.example.co.uk');
export function isAllowedSameSiteUrl(candidateUrlOrElement) {
try {
return extractOrigin(candidateUrlOrElement).isSameSite(APP_ORIGIN);
} catch {
return false;
}
}
// true: same scheme (https) and same registrable domain (example.co.uk)
isAllowedSameSiteUrl('https://billing.example.co.uk:8443/checkout');
// false: scheme downgrade (http vs. https) is rejected by schemeful same-site
isAllowedSameSiteUrl('http://billing.example.co.uk/checkout');
3. Opak Kaynakları Ayırt Etme ve Sabitleme (origin.opaque ve Origin.from(event))
Korumalı alana alınmış iframe'ler (<iframe sandbox="allow-scripts">), data: URL'leri ve new Origin(), opak kaynaklar (origin.opaque === true) oluşturur. Dize serileştirmeleri her zaman "null" olsa da Origin.from(event), gönderenin gerçek temel opak kaynağını tarayıcı tarafından gönderilen bir MessageEvent öğesinden ayıklar:
- Aynı korumalı alana alınmış iFrame'den gönderilen iki mesaj, birbiriyle
isSameOrigin()olanOriginnesneleri (true) verir. - Farklı korumalı alan iframe'lerinden veya ayrı
Origin.from('data:...')/new Origin()çağrılarından gönderilen mesajlar,isSameOrigin()değerinifalseolarak değerlendiren farklı opak kaynaklar oluşturur. Bu sayede, alakasız opak bağlamlarda"null" === "null"sahtekarlığı önlenir. new Origin(), kendisi dışında hiçbir şeyle eşleşmeyen yeni ve benzersiz bir opak kaynak oluşturur. Bu, beklenen bir eş kaynağın başlatma sırasında bir kez sabitlenmesinden önce varsayılan olarak tümünü reddetme nöbetçisi olarak kullanışlıdır.
// Initialize with a unique opaque sentinel that matches no other origin
let pinnedSandboxOrigin = new Origin();
let isSandboxPinned = false;
export function handleSandboxedWidgetMessage(event, expectedWidgetWindow) {
try {
const senderOrigin = Origin.from(event);
// Pin the opaque origin only once on initialization from the expected sandboxed iframe
if (!isSandboxPinned && event.source === expectedWidgetWindow && event.data?.type === 'init') {
pinnedSandboxOrigin = senderOrigin;
isSandboxPinned = true;
}
// Subsequent messages from the SAME sandboxed iframe match pinnedSandboxOrigin;
// messages from any other sandboxed iframe (also event.origin === "null") return false.
return senderOrigin.isSameOrigin(pinnedSandboxOrigin);
} catch {
return false;
}
}
En iyi uygulamalar
isSameOrigin(other)veyaisSameSite(other)yöntemini çağırmadan önce karşılaştırmanın her iki tarafını daOrigin.from()aracılığıylaOriginörneklerine dönüştürün.- Opak kaynak kimliğini korumanız gerektiğinde tarayıcı tarafından gönderilen
MessageEventdeğerini doğrudanOrigin.from(event)değerine GEÇİRİN ve geçerli yürütme bağlamınınOrigindeğerini elde etmek içinOrigin.from(self)(TypeErrorhatası verenLocationveyaDocumentyerine) kullanın. - Güvenilmeyen veya çalışma zamanı girişlerindeki
Origin.from()çağrılarını birtry...catchbloğuna sarmalayın. Böylece geçersiz URL'ler,"null"dizeleri veya desteklenmeyen nesneler, yakalanmamış birTypeErrorhatası vermek yerine güvenli bir şekildefalsedöndürür. - DO NOT compare two runtime
event.originorurl.originstrings directly without first rejecting"null"(origin !== 'null'), as two unrelated opaque origins both serialize to"null". startsWith(),includes(),endsWith()veya basit.split('.')ana makine adı dilimleme ile kaynakları veya siteleri DOĞRULAMAYIN.
Yedek strateji
Baseline hedefiniz henüz Origin arayüzünü desteklemeyen tarayıcılar içeriyorsa 'Origin' in globalThis özelliğini algılayın ve URL'leri new URL() ile ayrıştırmaya geri dönün:
- Aynı kaynak yedek (demet kaynaklar): URL dizesini ayıklayın (
MessageEventnesneler içinevent.originveya bağlantı öğeleri içincandidate.hrefkullanarak), hem aday hem de güvenilir URL'lerinew URL()ile ayrıştırın,"null"opak kaynakları (candidateUrl.origin !== 'null') reddedin vecandidateUrl.origin === trustedUrl.originkarşılaştırın. Opak kaynak,'https://app.example.com'gibi bir tuple kaynağıyla hiçbir zaman aynı kaynak olmadığından'null''i hemen reddetmekOrigin.isSameOrigin()ile eşleşir. - Aynı site yedek (uyarı):
URLarayüzü, tarayıcının Genel Ek Listesi'ni göstermez.Originiçermeyen tarayıcılarda, yalnızca kontrol ettiğiniz açık ve bilinen özel kaydedilebilir alan adına karşı son ek eşleme gerçekleştirin (hemprotocolhem de tam veya nokta ön eklihostnameeşleşmesini doğrulayarak) ya da güncel bir Herkese Açık Son Ek Listesi kitaplığı kullanın. - Opak/Korumalı alana alınmış iframe yedek:
Originiçermeyen tarayıcılar,MessageEventüzerinde farklı opak kaynak kimlikleri göstermez (event.originher zaman"null"olur). Bilinen bir korumalı alana alınmış<iframe sandbox="allow-scripts">kaynağından gelen mesajları kasıtlı olarak kabul ediyorsanızevent.origindeğerini karşılaştırmak yerineevent.source === expectedWidgetWindowdeğerini doğrulayın (veya başlatma sırasında iframe'e aktarılan özel birMessageChannelMessagePortüzerinden iletişim kurun).
// Replace with your trusted reference URL and known registrable domain
const TRUSTED_URL = 'https://app.example.com';
const TRUSTED_SITE_DOMAIN = 'example.com';
function extractUrlString(candidate) {
if (typeof candidate === 'string') return candidate;
if (typeof MessageEvent !== 'undefined' && candidate instanceof MessageEvent) {
return candidate.origin;
}
if (candidate === globalThis) {
return globalThis.location?.href ?? '';
}
return candidate?.href ?? '';
}
function extractOrigin(candidate) {
try {
return Origin.from(candidate);
} catch (err) {
if (typeof MessageEvent !== 'undefined' && candidate instanceof MessageEvent) {
return Origin.from(candidate.origin);
}
throw err;
}
}
export function checkSameOriginWithFallback(candidate) {
try {
if ('Origin' in globalThis) {
return extractOrigin(candidate).isSameOrigin(Origin.from(TRUSTED_URL));
}
const candidateUrl = new URL(extractUrlString(candidate));
const trustedUrl = new URL(TRUSTED_URL);
return candidateUrl.origin !== 'null' && candidateUrl.origin === trustedUrl.origin;
} catch {
return false;
}
}
export function checkSameSiteWithFallback(candidate) {
try {
if ('Origin' in globalThis) {
return extractOrigin(candidate).isSameSite(Origin.from(TRUSTED_URL));
}
const candidateUrl = new URL(extractUrlString(candidate));
const trustedUrl = new URL(TRUSTED_URL);
return (
candidateUrl.origin !== 'null' &&
candidateUrl.protocol === trustedUrl.protocol &&
(candidateUrl.hostname === TRUSTED_SITE_DOMAIN ||
candidateUrl.hostname.endsWith(`.${TRUSTED_SITE_DOMAIN}`))
);
} catch {
return false;
}
}