localStorage
์ ๋ฏผ๊ฐํ ์ ๋ณด๋ฅผ ์ ์ฅํ๊ฑฐ๋ XSS ๊ณต๊ฒฉ์ ์ทจ์ฝํ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ๋ฉด ๋ณด์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค. ์๋ฒ ์ธก์์ ๊ด๋ฆฌํ๊ฑฐ๋ ๋ฐ์ดํฐ๋ฅผ ์ํธํํ์ฌ ์ ์ฅํ๊ณ , ์
๋ ฅ๊ฐ ๊ฒ์ฆ ๋ฐ ์ถ๋ ฅ๊ฐ ์ธ์ฝ๋ฉ์ ์ฒ ์ ํ ํด์ผ ํฉ๋๋ค.
localStorage๋ ํด๋ผ์ด์ธํธ ์ธก์ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ๋ ๊ฐ๋จํ ๋ฐฉ๋ฒ์ด์ง๋ง, ๋ฏผ๊ฐํ ์ ๋ณด๋ฅผ ์ ์ฅํ๊ฑฐ๋ XSS ๊ณต๊ฒฉ์ ์ทจ์ฝํ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ๋ ๋ฐ ์ฌ์ฉํ๋ฉด ์ฌ๊ฐํ ๋ณด์ ๋ฌธ์ ๋ฅผ ์ผ๊ธฐํ ์ ์์ต๋๋ค. localStorage์ ์ ์ฅ๋ ๋ฐ์ดํฐ๋ JavaScript๋ฅผ ํตํด ์ ๊ทผํ ์ ์์ผ๋ฏ๋ก, ๊ณต๊ฒฉ์๊ฐ XSS ๊ณต๊ฒฉ์ ํตํด ์ ์ฑ ์คํฌ๋ฆฝํธ๋ฅผ ์ฝ์ ํ๋ฉด localStorage์ ๋ฐ์ดํฐ๋ฅผ ํ์ทจํ๊ฑฐ๋ ๋ณ์กฐํ ์ ์์ต๋๋ค. ๋ํ, localStorage๋ ๋๋ฉ์ธ ๊ฐ์ ๊ณต์ ๋์ง ์์ง๋ง, ํ์ ๋๋ฉ์ธ์์๋ ์ ๊ทผ์ด ๊ฐ๋ฅํ๋ฏ๋ก, ๋ฏผ๊ฐํ ์ ๋ณด๋ฅผ ์ ์ฅํ๋ ๊ฒฝ์ฐ ํ์ ๋๋ฉ์ธ์์์ ๋ณด์ ์ํ์ ๊ณ ๋ คํด์ผ ํฉ๋๋ค.
localStorage
์ ๋ฏผ๊ฐํ ์ ๋ณด๋ฅผ ์ ์ฅํ๋ ๊ฒ์ ์ต๋ํ ํผํด์ผ ํฉ๋๋ค. ๋ฏผ๊ฐํ ์ ๋ณด๋ฅผ ์ ์ฅํด์ผ ํ๋ ๊ฒฝ์ฐ, ์๋ฒ ์ธก์์ ๊ด๋ฆฌํ๊ณ ํด๋ผ์ด์ธํธ ์ธก์์๋ ์ ๊ทผ ํ ํฐ๋ง ์ ์ฅํ๋ ๋ฐฉ์์ ๊ณ ๋ คํด์ผ ํฉ๋๋ค. ๋ํ, ์ ์ฅ๋ ๋ฐ์ดํฐ๊ฐ XSS ๊ณต๊ฒฉ์ ์ทจ์ฝํ์ง ์๋๋ก ์
๋ ฅ๊ฐ ๊ฒ์ฆ ๋ฐ ์ถ๋ ฅ๊ฐ ์ธ์ฝ๋ฉ์ ์ฒ ์ ํ ํด์ผ ํฉ๋๋ค. HTTPOnly ์ฟ ํค๋ฅผ ์ฌ์ฉํ์ฌ JavaScript์์ ์ ๊ทผํ ์ ์๋๋ก ์ค์ ํ๊ฑฐ๋, Web Crypto API๋ฅผ ์ฌ์ฉํ์ฌ ๋ฐ์ดํฐ๋ฅผ ์ํธํํ์ฌ ์ ์ฅํ๋ ๋ฐฉ๋ฒ๋ ๊ณ ๋ คํ ์ ์์ต๋๋ค. ์น ์คํ ๋ฆฌ์ง API๋ฅผ ์ฌ์ฉํ ๋์๋ ํญ์ ๋ณด์์ ์ธ ์ธก๋ฉด์ ๊ณ ๋ คํด์ผ ํฉ๋๋ค.
// โ ๋ฏผ๊ฐํ ์ ๋ณด๋ฅผ localStorage์ ์ ์ฅ (์ํ)
localStorage.setItem('apiKey', 'YOUR_API_KEY');
// โ XSS ๊ณต๊ฒฉ์ ์ทจ์ฝํ ๋ฐ์ดํฐ๋ฅผ localStorage์ ์ ์ฅ (์ํ)
localStorage.setItem('username', '<script>alert("XSS");</script>');
// localStorage์์ ๋ฐ์ดํฐ๋ฅผ ์ฝ์ด์ DOM์ ์ง์ ์ฝ์
(์ํ)
document.getElementById('username').innerHTML = localStorage.getItem('username');
// โ
๋ฏผ๊ฐํ ์ ๋ณด๋ ์๋ฒ์์ ๊ด๋ฆฌํ๊ณ ํ ํฐ๋ง ์ ์ฅ
localStorage.setItem('authToken', 'YOUR_AUTH_TOKEN');
// โ
XSS ๊ณต๊ฒฉ ๋ฐฉ์ง๋ฅผ ์ํ ์
๋ ฅ๊ฐ ๊ฒ์ฆ ๋ฐ ์ถ๋ ฅ๊ฐ ์ธ์ฝ๋ฉ
function sanitizeInput(input) {
const div = document.createElement('div');
div.textContent = input;
return div.innerHTML;
}
const username = localStorage.getItem('username');
if (username) {
document.getElementById('username').innerHTML = sanitizeInput(username);
}
// โ
Web Crypto API๋ฅผ ์ฌ์ฉํ์ฌ ์ํธํ
async function encryptData(data) {
const key = await crypto.subtle.generateKey(
{ name: 'AES-GCM', length: 256 }, true, ['encrypt', 'decrypt']
);
const iv = crypto.getRandomValues(new Uint8Array(12));
const encodedData = new TextEncoder().encode(data);
const cipherText = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv: iv }, key, encodedData
);
return {
cipherText: Array.from(new Uint8Array(cipherText)),
iv: Array.from(iv),
key: await crypto.subtle.exportKey('jwk', key)
};
}
async function saveEncryptedData(data, keyName) {
const encrypted = await encryptData(data);
localStorage.setItem(keyName, JSON.stringify(encrypted));
}
// ์ฌ์ฉ ์์
saveEncryptedData('Sensitive Data', 'encryptedData');