13,198
回編集
| 54行目: | 54行目: | ||
<br> | <br> | ||
==== DOMのプロパティとメソッドの安全な使用 ==== | ==== DOMのプロパティとメソッドの安全な使用 ==== | ||
===== innerHTMLの代わりに、textContentを使用してテキストを設定する ===== | |||
* | *: textContentはテキストをHTMLとして解釈せず、純粋なテキストとして扱うため、スクリプトタグや悪意のあるコードが実行されるリスクを排除できる。 | ||
*: | *: DOMベースXSS攻撃からの保護が自動的に行われる。 | ||
<br> | <br> | ||
<u>※注意</u><br> | |||
<u>HTMLマークアップが必要な場合は使用できない。</u><br> | |||
<u>HTMLを意図的に挿入する必要がある場合は、DOMPurify等のサニタイザーライブラリの使用を検討する。</u><br> | |||
<br> | |||
<syntaxhighlight lang="javascript"> | |||
// 危険な例 : innerHTMLの使用 | |||
function unsafeSetContent(elementId, content) { | |||
const element = document.getElementById(elementId); | |||
element.innerHTML = content; // 非推奨 : XSS攻撃の可能性あり | |||
} | |||
// 安全な例 : textContentの使用 | |||
function safeSetContent(elementId, content) { | |||
const element = document.getElementById(elementId); | |||
element.textContent = content; // 推奨 : HTMLタグとしてパースされない | |||
} | |||
// 実装例 1 : ユーザ入力の表示 | |||
function displayUserInput(input) { | |||
const displayElement = document.getElementById('output'); | |||
displayElement.textContent = input; | |||
} | |||
// 実装例 2 : 動的なリスト項目の追加 | |||
function addListItem(text) { | |||
const li = document.createElement('li'); | |||
li.textContent = text; // 安全な方法でテキストを設定 | |||
document.querySelector('ul').appendChild(li); | |||
} | |||
// 実装例 3 : エラーメッセージの表示 | |||
function showError(errorMessage) { | |||
const errorElement = document.getElementById('error'); | |||
errorElement.textContent = errorMessage; | |||
errorElement.classList.add('error'); | |||
} | |||
// 実装例 4 : 複数要素の更新 | |||
function updateMultipleElements(data) { | |||
// ユーザー名を安全に表示 | |||
document.getElementById('username').textContent = data.username; | |||
// スコアを安全に表示 | |||
document.getElementById('score').textContent = data.score; | |||
// ステータスを安全に表示 | |||
document.getElementById('status').textContent = data.status; | |||
} | |||
// 実装例 5 : テンプレートとの組み合わせ | |||
function createUserCard(user) { | |||
const template = document.getElementById('user-card-template'); | |||
const clone = template.content.cloneNode(true); | |||
// 各フィールドを安全に更新 | |||
clone.querySelector('.user-name').textContent = user.name; | |||
clone.querySelector('.user-email').textContent = user.email; | |||
clone.querySelector('.user-role').textContent = user.role; | |||
return clone; | |||
} | |||
// 使用例 | |||
const safeData = { | |||
username: "ユーザ1", | |||
message: "<script>alert('XSS')</script>" | |||
}; | |||
// 安全な実装 | |||
// 結果 : スクリプトタグがそのままテキストとして表示される | |||
safeSetContent('message', safeData.message); | |||
</syntaxhighlight> | |||
<br> | |||
===== setAttribute関数の代わりに、直接プロパティを設定する ===== | |||
直接プロパティを設定する場合は、Webブラウザの内部的なサニタイズ処理が使用できる。<br> | |||
特に、イベントハンドラの設定において、悪意のあるコードの実行を防ぐことができる。<br> | |||
<br> | |||
また、プロパティは型チェックが行われるため、型の安全性が向上する。<br> | |||
<br> | |||
使用シーンを以下に示す。<br> | |||
* 標準的なHTML属性の設定 (id, class, src, href等) | |||
* フォーム要素のプロパティ設定 (value, disabled, required等) | |||
* スタイルプロパティの設定 (style.propertyName) | |||
* データ属性の設定 (dataset.propertyName) | |||
* ARIA属性の設定 (aria-*属性) | |||
<br> | |||
<u>※注意が必要な属性</u><br> | |||
* href属性 | |||
*: URLのバリデーションを行った後に設定する。 | |||
* src属性 | |||
*: 信頼できるソースからのURLのみを使用する。 | |||
* onclick等のイベントハンドラ | |||
*: 直接文字列を設定せず、addEventListenerメソッドを使用する。 | |||
<br> | |||
<syntaxhighlight lang="javascript"> | |||
// 危険な例 : setAttributeの使用 | |||
// 悪意のあるスクリプトが実行される可能性がある | |||
function unsafeSetAttribute(element, value) { | |||
element.setAttribute('onclick', value); // 避けるべき | |||
element.setAttribute('href', value); // 避けるべき | |||
} | |||
// 安全な例 - 直接プロパティ設定 | |||
// プロパティに直接設定 | |||
function safeSetProperties(element, value) { | |||
element.id = value; // 推奨 | |||
element.className = value; // 推奨 | |||
element.title = value; // 推奨 | |||
} | |||
// 実装例 1 : フォーム要素の操作 | |||
// 安全な方法でプロパティを設定 | |||
function updateFormElement(input, value) { | |||
input.disabled = value; // disabled属性 | |||
input.required = value; // required属性 | |||
input.value = value; // value属性 | |||
input.placeholder = value; // placeholder属性 | |||
} | |||
// 実装例 2 : イメージ要素の操作 | |||
function updateImage(imgElement, data) { | |||
imgElement.src = data.url; // src属性 | |||
imgElement.alt = data.altText; // alt属性 | |||
imgElement.width = data.width; // width属性 | |||
imgElement.height = data.height; // height属性 | |||
} | |||
// 実装例 3 : アンカー要素の操作 | |||
// URLのバリデーションを行ってから設定 | |||
function updateLink(linkElement, data) { | |||
if (isValidUrl(data.url)) { | |||
linkElement.href = data.url; | |||
} | |||
linkElement.target = '_blank'; // target属性 | |||
linkElement.rel = 'noopener'; // rel属性 | |||
} | |||
// 実装例 4 : スタイルの操作 | |||
// CSSプロパティを直接設定 | |||
function updateStyles(element, styles) { | |||
element.style.backgroundColor = styles.bg; | |||
element.style.color = styles.color; | |||
element.style.fontSize = styles.size; | |||
} | |||
// 実装例 5 : データ属性の操作 | |||
// data-*属性を安全に設定 | |||
function updateDataAttributes(element, data) { | |||
element.dataset.id = data.id; | |||
element.dataset.name = data.name; | |||
element.dataset.value = data.value; | |||
} | |||
// 実装例 6 : ARIA属性の操作 | |||
function updateAriaAttributes(element, state) { | |||
element.ariaLabel = state.label; | |||
element.ariaExpanded = state.expanded; | |||
element.ariaHidden = state.hidden; | |||
} | |||
// URLバリデーション用のヘルパー関数 | |||
function isValidUrl(url) { | |||
try { | |||
new URL(url); | |||
return true; | |||
} | |||
catch { | |||
return false; | |||
} | |||
} | |||
// 使用例 | |||
const element = document.getElementById('target'); | |||
// 安全な実装 | |||
element.className = 'safe-class'; | |||
element.id = 'safe-id'; | |||
const img = document.createElement('img'); | |||
img.src = 'safe-url.jpg'; | |||
img.alt = 'Safe image'; | |||
// データ属性の安全な設定 | |||
const dataElement = document.getElementById('data-element'); | |||
dataElement.dataset.userInput = 'Safe data'; | |||
</syntaxhighlight> | |||
<br> | |||
==== URLパラメータの処理 ==== | ==== URLパラメータの処理 ==== | ||
* location.hashやlocation.searchからの値を使用する前に、<code>encodeURIComponent</code>関数でエンコードする。 | * location.hashやlocation.searchからの値を使用する前に、<code>encodeURIComponent</code>関数でエンコードする。 | ||