PostMessage API'si ile güvenli kaynaklar arası iletişimi keşfedin. Web uygulamalarındaki zafiyetleri azaltmak için yeteneklerini, risklerini ve en iyi yöntemleri öğrenin.
Farklı Kökenler Arası İletişim: PostMessage API ile Güvenlik Desenleri
Modern web'de, uygulamaların sık sık farklı kökenlerden kaynaklarla etkileşime girmesi gerekir. Aynı Köken Politikası (Same-Origin Policy - SOP), komut dosyalarının farklı bir kökenden kaynaklara erişimini kısıtlayan çok önemli bir güvenlik mekanizmasıdır. Ancak, farklı kökenler arası iletişimin gerekli olduğu meşru senaryolar da vardır. postMessage API'si bunu başarmak için kontrollü bir mekanizma sağlar, ancak potansiyel güvenlik risklerini anlamak ve uygun güvenlik desenlerini uygulamak hayati önem taşır.
Aynı Köken Politikasını (SOP) Anlamak
Aynı Köken Politikası, web tarayıcılarındaki temel bir güvenlik kavramıdır. Web sayfalarının, web sayfasını sunan alan adından farklı bir alan adına istek yapmasını kısıtlar. Bir köken, şema (protokol), ana makine (alan adı) ve port tarafından tanımlanır. Bunlardan herhangi biri farklıysa, kökenler farklı kabul edilir. Örneğin:
https://example.comhttps://www.example.comhttp://example.comhttps://example.com:8080
Bunların hepsi farklı kökenlerdir ve SOP aralarındaki doğrudan komut dosyası erişimini kısıtlar.
PostMessage API'sine Giriş
postMessage API'si, farklı kökenler arası iletişim için güvenli ve kontrollü bir mekanizma sağlar. Komut dosyalarının, kökenlerinden bağımsız olarak diğer pencerelere (ör. iframe'ler, yeni pencereler veya sekmeler) mesaj göndermesine olanak tanır. Alıcı pencere daha sonra bu mesajları dinleyebilir ve buna göre işleyebilir.
Mesaj göndermek için temel sözdizimi şöyledir:
otherWindow.postMessage(message, targetOrigin);
otherWindow: Hedef pencereye bir referans (ör.window.parent,iframe.contentWindowveyawindow.open'dan elde edilen bir pencere nesnesi).message: Göndermek istediğiniz veri. Bu, serileştirilebilen herhangi bir JavaScript nesnesi olabilir (ör. dizeler, sayılar, nesneler, diziler).targetOrigin: Mesajı göndermek istediğiniz kökeni belirtir. Bu, çok önemli bir güvenlik parametresidir.
Alıcı tarafta, message olayını dinlemeniz gerekir:
window.addEventListener('message', function(event) {
// ...
});
event nesnesi aşağıdaki özellikleri içerir:
event.data: Diğer pencere tarafından gönderilen mesaj.event.origin: Mesajı gönderen pencerenin kökeni.event.source: Mesajı gönderen pencereye bir referans.
Güvenlik Riskleri ve Zafiyetler
postMessage, SOP kısıtlamalarını aşmak için bir yol sunarken, dikkatli bir şekilde uygulanmazsa potansiyel güvenlik riskleri de oluşturur. İşte bazı yaygın zafiyetler:
1. Hedef Köken Uyuşmazlığı
event.origin özelliğinin doğrulanmaması kritik bir zafiyettir. Alıcı mesaja körü körüne güvenirse, herhangi bir web sitesi kötü amaçlı veriler gönderebilir. Mesajı işlemeden önce her zaman event.origin'in beklenen kökenle eşleştiğini doğrulayın.
Örnek (Zafiyetli Kod):
window.addEventListener('message', function(event) {
// BUNU YAPMAYIN!
processMessage(event.data);
});
Örnek (Güvenli Kod):
window.addEventListener('message', function(event) {
if (event.origin !== 'https://trusted-origin.com') {
console.warn('Güvenilmeyen bir kökenden mesaj alındı:', event.origin);
return;
}
processMessage(event.data);
});
2. Veri Enjeksiyonu
Alınan veriyi (event.data) çalıştırılabilir kod olarak ele almak veya doğrudan DOM'a enjekte etmek, Siteler Arası Komut Dosyası Çalıştırma (XSS) zafiyetlerine yol açabilir. Alınan veriyi kullanmadan önce her zaman temizleyin ve doğrulayın.
Örnek (Zafiyetli Kod):
window.addEventListener('message', function(event) {
if (event.origin === 'https://trusted-origin.com') {
document.body.innerHTML = event.data; // BUNU YAPMAYIN!
}
});
Örnek (Güvenli Kod):
window.addEventListener('message', function(event) {
if (event.origin === 'https://trusted-origin.com') {
const sanitizedData = sanitize(event.data); // Uygun bir temizleme fonksiyonu uygulayın
document.getElementById('message-container').textContent = sanitizedData;
}
});
function sanitize(data) {
// Burada sağlam bir temizleme mantığı uygulayın.
// Örneğin, DOMPurify veya benzer bir kütüphane kullanın
return DOMPurify.sanitize(data);
}
3. Ortadaki Adam (MITM) Saldırıları
İletişim güvenli olmayan bir kanal (HTTP) üzerinden gerçekleşirse, bir MITM saldırganı mesajları yakalayıp değiştirebilir. Güvenli iletişim için her zaman HTTPS kullanın.
4. Siteler Arası İstek Sahteciliği (CSRF)
Alıcı, alınan mesaja dayanarak uygun doğrulama olmadan eylemler gerçekleştirirse, bir saldırgan potansiyel olarak alıcıyı istenmeyen eylemleri gerçekleştirmesi için kandırmak amacıyla sahte mesajlar oluşturabilir. Mesaja gizli bir jeton (token) eklemek ve alıcı tarafında bunu doğrulamak gibi CSRF koruma mekanizmalarını uygulayın.
5. targetOrigin'de Joker Karakter Kullanımı
targetOrigin'i * olarak ayarlamak, herhangi bir kökenin mesajı almasına izin verir. Bu, köken tabanlı güvenliğin amacını boşa çıkardığı için kesinlikle gerekli olmadıkça kaçınılmalıdır. * kullanmanız gerekiyorsa, mesaj doğrulama kodları (MAC'ler) gibi diğer güçlü güvenlik önlemlerini uyguladığınızdan emin olun.
Örnek (Bundan Kaçının):
otherWindow.postMessage(message, '*'); // Kesinlikle gerekli olmadıkça '*' kullanmaktan kaçının
Güvenlik Desenleri ve En İyi Uygulamalar
postMessage ile ilişkili riskleri azaltmak için aşağıdaki güvenlik desenlerini ve en iyi uygulamaları takip edin:
1. Katı Köken Doğrulaması
Alıcı tarafında her zaman event.origin özelliğini doğrulayın. Bunu önceden tanımlanmış güvenilir kökenler listesiyle karşılaştırın. Karşılaştırma için katı eşitlik (===) kullanın.
2. Veri Temizleme ve Doğrulama
postMessage aracılığıyla alınan tüm verileri kullanmadan önce temizleyin ve doğrulayın. Verinin nasıl kullanılacağına bağlı olarak uygun temizleme tekniklerini kullanın (ör. HTML kaçış karakterleri, URL kodlama, girdi doğrulama). HTML temizlemek için DOMPurify gibi kütüphaneler kullanın.
3. Mesaj Doğrulama Kodları (MAC'ler)
Mesajın bütünlüğünü ve özgünlüğünü sağlamak için mesaja bir Mesaj Doğrulama Kodu (MAC) ekleyin. Gönderici, paylaşılan gizli bir anahtar kullanarak MAC'i hesaplar ve mesaja dahil eder. Alıcı, aynı paylaşılan gizli anahtarı kullanarak MAC'i yeniden hesaplar ve alınan MAC ile karşılaştırır. Eşleşirlerse, mesaj otantik ve kurcalanmamış kabul edilir.
Örnek (HMAC-SHA256 Kullanarak):
// Gönderici
async function sendMessage(message, targetOrigin, sharedSecret) {
const encoder = new TextEncoder();
const data = encoder.encode(JSON.stringify(message));
const key = await crypto.subtle.importKey(
"raw",
encoder.encode(sharedSecret),
{ name: "HMAC", hash: "SHA-256" },
false,
["sign"]
);
const signature = await crypto.subtle.sign("HMAC", key, data);
const signatureArray = Array.from(new Uint8Array(signature));
const signatureHex = signatureArray.map(b => b.toString(16).padStart(2, '0')).join('');
const securedMessage = {
data: message,
signature: signatureHex
};
otherWindow.postMessage(securedMessage, targetOrigin);
}
// Alıcı
async function receiveMessage(event, sharedSecret) {
if (event.origin !== 'https://trusted-origin.com') {
console.warn('Güvenilmeyen bir kökenden mesaj alındı:', event.origin);
return;
}
const securedMessage = event.data;
const message = securedMessage.data;
const receivedSignature = securedMessage.signature;
const encoder = new TextEncoder();
const data = encoder.encode(JSON.stringify(message));
const key = await crypto.subtle.importKey(
"raw",
encoder.encode(sharedSecret),
{ name: "HMAC", hash: "SHA-256" },
false,
["verify"]
);
const signature = await crypto.subtle.sign("HMAC", key, data);
const signatureArray = Array.from(new Uint8Array(signature));
const signatureHex = signatureArray.map(b => b.toString(16).padStart(2, '0')).join('');
if (signatureHex === receivedSignature) {
console.log('Mesaj otantik!');
processMessage(message); // Mesajı işlemeye devam edin
} else {
console.error('Mesaj imza doğrulaması başarısız oldu!');
}
}
Önemli: Paylaşılan gizli anahtar güvenli bir şekilde oluşturulmalı ve saklanmalıdır. Anahtarı koda sabit olarak yazmaktan kaçının.
4. Nonce ve Zaman Damgaları Kullanımı
Tekrar saldırılarını (replay attacks) önlemek için mesaja benzersiz bir nonce (bir kez kullanılan sayı) ve bir zaman damgası ekleyin. Alıcı daha sonra nonce'un daha önce kullanılmadığını ve zaman damgasının kabul edilebilir bir zaman aralığında olduğunu doğrulayabilir. Bu, bir saldırganın önceden yakalanan mesajları tekrar oynatma riskini azaltır.
5. En Az Ayrıcalık Prensibi
Diğer pencereye yalnızca minimum gerekli ayrıcalıkları verin. Örneğin, diğer pencerenin yalnızca veri okuması gerekiyorsa, veri yazmasına izin vermeyin. İletişim protokolünüzü en az ayrıcalık prensibini göz önünde bulundurarak tasarlayın.
6. İçerik Güvenlik Politikası (CSP)
Komut dosyalarının hangi kaynaklardan yüklenebileceğini ve komut dosyalarının gerçekleştirebileceği eylemleri kısıtlamak için İçerik Güvenlik Politikası (CSP) kullanın. Bu, postMessage verilerinin yanlış işlenmesinden kaynaklanabilecek XSS zafiyetlerinin etkisini azaltmaya yardımcı olabilir.
7. Girdi Doğrulama
Alınan verinin yapısını ve biçimini her zaman doğrulayın. Net bir mesaj formatı tanımlayın ve alınan verinin bu formata uygun olduğundan emin olun. Bu, beklenmedik davranışları ve zafiyetleri önlemeye yardımcı olur.
8. Güvenli Veri Serileştirme
Mesajları serileştirmek ve seri durumundan çıkarmak için JSON gibi güvenli bir veri serileştirme formatı kullanın. eval() veya Function() gibi kod yürütülmesine izin veren formatları kullanmaktan kaçının.
9. Mesaj Boyutunu Sınırlama
postMessage aracılığıyla gönderilen mesajların boyutunu sınırlayın. Büyük mesajlar aşırı kaynak tüketebilir ve potansiyel olarak hizmet reddi (denial-of-service) saldırılarına yol açabilir.
10. Düzenli Güvenlik Denetimleri
Potansiyel zafiyetleri belirlemek ve gidermek için kodunuzun düzenli güvenlik denetimlerini yapın. postMessage'in uygulanmasına özellikle dikkat edin ve tüm güvenlik en iyi uygulamalarının takip edildiğinden emin olun.
Örnek Senaryo: Bir Iframe ile Üst Penceresi Arasında Güvenli İletişim
https://iframe.example.com adresinde barındırılan bir iframe'in, https://parent.example.com adresinde barındırılan üst sayfasıyla iletişim kurması gereken bir senaryo düşünün. Iframe'in, işlenmek üzere kullanıcı verilerini üst sayfaya göndermesi gerekiyor.
Iframe (https://iframe.example.com):
// Paylaşılan bir gizli anahtar oluşturun (güvenli bir anahtar oluşturma yöntemiyle değiştirin)
const sharedSecret = 'GUVENLI_PAYLASILAN_GIZLI_ANAHTARINIZ';
// Kullanıcı verilerini al
const userData = {
name: 'John Doe',
email: 'john.doe@example.com'
};
// Kullanıcı verilerini üst sayfaya gönder
async function sendUserData(userData) {
const encoder = new TextEncoder();
const data = encoder.encode(JSON.stringify(userData));
const key = await crypto.subtle.importKey(
"raw",
encoder.encode(sharedSecret),
{ name: "HMAC", hash: "SHA-256" },
false,
["sign"]
);
const signature = await crypto.subtle.sign("HMAC", key, data);
const signatureArray = Array.from(new Uint8Array(signature));
const signatureHex = signatureArray.map(b => b.toString(16).padStart(2, '0')).join('');
const securedMessage = {
data: userData,
signature: signatureHex
};
parent.postMessage(securedMessage, 'https://parent.example.com');
}
sendUserData(userData);
Üst Sayfa (https://parent.example.com):
// Paylaşılan gizli anahtar (iframe'in anahtarıyla eşleşmelidir)
const sharedSecret = 'GUVENLI_PAYLASILAN_GIZLI_ANAHTARINIZ';
window.addEventListener('message', async function(event) {
if (event.origin !== 'https://iframe.example.com') {
console.warn('Güvenilmeyen bir kökenden mesaj alındı:', event.origin);
return;
}
const securedMessage = event.data;
const userData = securedMessage.data;
const receivedSignature = securedMessage.signature;
const encoder = new TextEncoder();
const data = encoder.encode(JSON.stringify(userData));
const key = await crypto.subtle.importKey(
"raw",
encoder.encode(sharedSecret),
{ name: "HMAC", hash: "SHA-256" },
false,
["verify"]
);
const signature = await crypto.subtle.sign("HMAC", key, data);
const signatureArray = Array.from(new Uint8Array(signature));
const signatureHex = signatureArray.map(b => b.toString(16).padStart(2, '0')).join('');
if (signatureHex === receivedSignature) {
console.log('Mesaj otantik!');
// Kullanıcı verilerini işle
console.log('Kullanıcı verisi:', userData);
} else {
console.error('Mesaj imza doğrulaması başarısız oldu!');
}
});
Önemli Notlar:
GUVENLI_PAYLASILAN_GIZLI_ANAHTARINIZ'ı güvenli bir şekilde oluşturulmuş paylaşılan bir gizli anahtarla değiştirin.- Paylaşılan gizli anahtar hem iframe'de hem de üst sayfada aynı olmalıdır.
- Bu örnek, mesaj doğrulaması için HMAC-SHA256 kullanır.
Sonuç
postMessage API'si, web uygulamalarında farklı kökenler arası iletişimi etkinleştirmek için güçlü bir araçtır. Ancak, potansiyel güvenlik risklerini anlamak ve bu riskleri azaltmak için uygun güvenlik desenlerini uygulamak çok önemlidir. Bu kılavuzda özetlenen güvenlik desenlerini ve en iyi uygulamaları takip ederek, sağlam ve güvenli web uygulamaları oluşturmak için postMessage'i güvenli bir şekilde kullanabilirsiniz.
Her zaman güvenliğe öncelik vermeyi ve web geliştirme için en son güvenlik en iyi uygulamalarıyla güncel kalmayı unutmayın. Uygulamalarınızın potansiyel zafiyetlere karşı korunduğundan emin olmak için kodunuzu ve güvenlik yapılandırmalarınızı düzenli olarak gözden geçirin.