なぜWebセキュリティを学ぶべきか
2026年、サイバー攻撃は年間数千万件を超え、企業・個人への被害は拡大し続けています。エンジニアにとってWebセキュリティの知識は「あればいい」ではなく「必須」のスキルです。本記事では、最も頻出する脆弱性と具体的な対策コードを解説します。
1. XSS(クロスサイトスクリプティング)
攻撃の仕組み
XSSは、攻撃者が悪意あるJavaScriptコードをWebページに注入し、サイト訪問者のブラウザで実行させる攻撃です。Cookieの盗取・フィッシング・マルウェア配布などに悪用されます。
// 脆弱なコード例
const name = req.query.name;
res.send(`<h1>Hello, ${name}!</h1>`); // XSS脆弱性!
// 安全な対策:エスケープ処理
import { escape } from 'html-escaper';
const safeName = escape(req.query.name as string);
res.send(`<h1>Hello, ${safeName}!</h1>`);
// React/Next.jsでは自動でエスケープ
// JSXの{変数}は自動エスケープされる(危険なのはdangerouslySetInnerHTML)
function Welcome({ name }: { name: string }) {
return <h1>Hello, {name}!</h1>; // 安全
}
XSS対策まとめ
- ユーザー入力を必ずHTMLエスケープする
- Content Security Policy (CSP) ヘッダーを設定する
- dangerouslySetInnerHTMLの使用を避ける
- HTTPOnly Cookie を使用してJavaScriptからCookieにアクセスさせない
2. SQLインジェクション
攻撃の仕組み
SQLインジェクションは、ユーザー入力をSQL文に直接組み込む脆弱なコードを悪用し、データベースを不正操作する攻撃です。
// 脆弱なコード(文字列連結)
const query = "SELECT * FROM users WHERE email='" + email + "'";
// email = "' OR '1'='1" と入力されると全ユーザーが取得される
// 安全な対策:プリペアドステートメント(Prismaの例)
const user = await prisma.user.findFirst({
where: { email: email } // Prismaは自動的にプリペアドステートメントを使用
});
// Drizzle ORM の例
const result = await db.select()
.from(users)
.where(eq(users.email, email)); // 型安全かつSQLインジェクション対策済み
3. CSRF(クロスサイトリクエストフォージェリ)
CSRFは、ユーザーが意図せず悪意あるサイトから、ログイン済みサービスへリクエストを送らされる攻撃です。
// Next.js でのCSRF対策例
import { csrf } from 'csrf-csrf';
const { generateToken, validateRequest } = csrf({
getSecret: () => process.env.CSRF_SECRET!,
cookieName: 'x-csrf-token',
cookieOptions: {
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
},
});
// APIルートでの検証
export async function POST(request: Request) {
// CSRFトークンを検証
const isValid = await validateRequest(request);
if (!isValid) {
return Response.json({ error: 'CSRF validation failed' }, { status: 403 });
}
// 処理続行...
}
4. 認証・認可の脆弱性
よくある認証の問題と対策
| 脆弱性 | 問題 | 対策 |
|---|---|---|
| 弱いパスワード | ブルートフォース攻撃に弱い | レート制限・CAPTCHA導入 |
| セッション固定攻撃 | ログイン後もセッションIDが変わらない | ログイン後にセッションを再生成 |
| JWTの鍵管理 | 弱い署名鍵の使用 | 256bit以上の強力な鍵を環境変数に |
| 平文パスワード保存 | DBが漏洩したら終わり | bcrypt/argon2でハッシュ化 |
// 安全なパスワードハッシュ化(argon2)
import { hash, verify } from '@node-rs/argon2';
// パスワード保存時
const hashedPassword = await hash(password, {
memoryCost: 19456,
timeCost: 2,
outputLen: 32,
parallelism: 1,
});
// パスワード検証時
const isValid = await verify(hashedPassword, inputPassword);
5. HTTPセキュリティヘッダーの設定
// next.config.js でのセキュリティヘッダー設定
const securityHeaders = [
{
key: 'X-DNS-Prefetch-Control',
value: 'on'
},
{
key: 'Strict-Transport-Security',
value: 'max-age=63072000; includeSubDomains; preload'
},
{
key: 'X-Content-Type-Options',
value: 'nosniff'
},
{
key: 'X-Frame-Options',
value: 'SAMEORIGIN'
},
{
key: 'Content-Security-Policy',
value: "default-src 'self'; script-src 'self' 'nonce-{nonce}'"
},
];
module.exports = {
async headers() {
return [
{
source: '/(.*)',
headers: securityHeaders,
},
];
},
};
セキュリティチェックリスト
- ✅ すべてのユーザー入力をバリデーション・サニタイズ
- ✅ プリペアドステートメントまたはORMを使用
- ✅ HTTPSの強制
- ✅ セキュリティヘッダーの設定
- ✅ 依存関係の定期的なアップデート(npm audit)
- ✅ 環境変数にシークレットを保存
- ✅ レート制限の実装
- ✅ エラーメッセージに内部情報を含めない
まとめ:セキュリティはコストではなく投資
Webセキュリティの脆弱性は、ひとたび悪用されれば企業の信頼を失い、莫大な損害をもたらします。本記事で紹介したXSS・SQLインジェクション・CSRF・認証の脆弱性と対策を実装し、セキュアなWebアプリケーション開発を心がけましょう。セキュリティは最後に追加するものではなく、設計段階から組み込むべきものです。