Webセキュリティ入門 — エンジニアが知るべき10の脅威と対策
Webセキュリティ入門 — エンジニアが知るべき10の脅威と対策
Webアプリケーションのセキュリティは、開発者の必須スキルです。2026年現在、サイバー攻撃の80%以上がWebアプリケーションを標的にしています。
この記事では、エンジニアが必ず知っておくべき10の脅威とその対策を、実際のコード例とともに解説します。明日から使える実践的な内容です。
目次
- XSS(クロスサイトスクリプティング)
- CSRF(クロスサイトリクエストフォージェリ)
- SQLインジェクション
- 認証・セッション管理の脆弱性
- HTTPS化とセキュアな通信
- CORS(オリジン間リソース共有)
- CSP(コンテンツセキュリティポリシー)
- 安全でないデシリアライゼーション
- 機密情報の露出
- セキュリティヘッダー
1. XSS(クロスサイトスクリプティング)
脅威の概要
攻撃者が悪意あるJavaScriptを注入し、他のユーザーのブラウザで実行させる攻撃。Cookie盗難、セッションハイジャック、フィッシングに悪用されます。
攻撃例
<!-- 脆弱なコード -->
<div>ようこそ、<?php echo $_GET['name']; ?>さん</div>
攻撃者が以下のURLにアクセスさせると:
https://example.com/?name=<script>alert(document.cookie)</script>
ユーザーのCookieが盗まれます。
対策1: エスケープ処理
// PHP
<div>ようこそ、<?php echo htmlspecialchars($_GET['name'], ENT_QUOTES, 'UTF-8'); ?>さん</div>
// JavaScript (React)
function Welcome({ name }) {
// Reactは自動でエスケープ
return <div>ようこそ、{name}さん</div>;
}
// 生HTMLを挿入する場合(危険、避ける)
<div dangerouslySetInnerHTML={{ __html: sanitizedHTML }} />
# Python (Flask)
from flask import render_template_string, escape
@app.route('/welcome')
def welcome():
name = request.args.get('name', '')
return render_template_string('<div>ようこそ、{{ name }}さん</div>', name=name)
# Jinjaテンプレートは自動エスケープ
対策2: DOMPurifyでサニタイズ
import DOMPurify from 'dompurify';
const dirtyHTML = '<img src=x onerror=alert(1)>';
const cleanHTML = DOMPurify.sanitize(dirtyHTML);
// 結果: '<img src="x">' (onerrorが除去される)
XSSの3タイプ
- 反射型XSS — URLパラメータ経由(上記の例)
- 格納型XSS — DBに保存され、他ユーザーが閲覧時に発火
- DOM-based XSS — JavaScriptの不適切な処理
// 危険なコード
const url = new URL(location.href);
const message = url.searchParams.get('message');
document.getElementById('output').innerHTML = message; // XSS脆弱性
// 安全なコード
document.getElementById('output').textContent = message;
2. CSRF(クロスサイトリクエストフォージェリ)
脅威の概要
ユーザーが意図しない操作(送金、パスワード変更等)を強制的に実行させる攻撃。
攻撃例
<!-- 攻撃者のサイト -->
<img src="https://bank.example.com/transfer?to=attacker&amount=10000" style="display:none">
ユーザーが銀行サイトにログイン中にこのページを開くと、意図せず送金されます。
対策1: CSRFトークン
<!-- フォームにトークンを埋め込む -->
<form method="POST" action="/transfer">
<input type="hidden" name="csrf_token" value="ランダムな文字列">
<input name="to" placeholder="送金先">
<input name="amount" placeholder="金額">
<button type="submit">送金</button>
</form>
# Flask
from flask_wtf.csrf import CSRFProtect
app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key'
csrf = CSRFProtect(app)
@app.route('/transfer', methods=['POST'])
def transfer():
# CSRFトークンが自動検証される
# 不正なリクエストは400エラー
pass
対策2: SameSite Cookie属性
// Express.js
app.use(session({
secret: 'your-secret',
cookie: {
sameSite: 'strict', // または 'lax'
secure: true, // HTTPS必須
httpOnly: true
}
}));
strict: 他サイトからのリクエストでCookie送信しないlax: GET等の安全なメソッドのみ許可none: 全て許可(非推奨)
対策3: カスタムヘッダー検証
// Axios (React/Vue等)
axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
// サーバー側で検証
app.post('/api/transfer', (req, res) => {
if (req.get('X-Requested-With') !== 'XMLHttpRequest') {
return res.status(403).send('Forbidden');
}
// 処理続行
});
3. SQLインジェクション
脅威の概要
SQL文に悪意あるコードを注入し、DBの不正操作(データ漏洩、改ざん、削除)を行う攻撃。
攻撃例
// 危険なコード
$username = $_POST['username'];
$password = $_POST['password'];
$query = "SELECT * FROM users WHERE username='$username' AND password='$password'";
$result = mysqli_query($conn, $query);
攻撃者が以下を入力:
username: admin' --
password: (任意)
実行されるSQL:
SELECT * FROM users WHERE username='admin' -- ' AND password='...'
-- 「--」以降はコメント扱いになり、パスワードチェックが無効化
対策1: プリペアドステートメント
// PHP (PDO)
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = ? AND password = ?");
$stmt->execute([$username, $password]);
$user = $stmt->fetch();
# Python (psycopg2 for PostgreSQL)
cursor.execute(
"SELECT * FROM users WHERE username = %s AND password = %s",
(username, password)
)
user = cursor.fetchone()
// Node.js (mysql2)
connection.execute(
'SELECT * FROM users WHERE username = ? AND password = ?',
[username, password],
(err, results) => {
// ...
}
);
対策2: ORM使用
// Prisma (Node.js)
const user = await prisma.user.findFirst({
where: {
username: username,
password: password // 実際はハッシュ化すべき
}
});
# Django ORM
user = User.objects.filter(username=username, password=password).first()
重要: パスワードは必ずハッシュ化
// bcrypt使用例
const bcrypt = require('bcrypt');
// 登録時
const hashedPassword = await bcrypt.hash(password, 10);
await prisma.user.create({
data: { username, password: hashedPassword }
});
// ログイン時
const user = await prisma.user.findUnique({ where: { username } });
const isValid = await bcrypt.compare(password, user.password);
4. 認証・セッション管理の脆弱性
脅威の概要
不適切なセッション管理により、他人のアカウントにログインされる、セッション固定攻撃などのリスク。
対策1: セッションIDの安全な生成
// Express.js
const session = require('express-session');
app.use(session({
secret: process.env.SESSION_SECRET, // 環境変数から
resave: false,
saveUninitialized: false,
cookie: {
secure: true, // HTTPS必須
httpOnly: true, // JavaScriptからアクセス不可
maxAge: 1800000, // 30分
sameSite: 'strict'
}
}));
対策2: ログイン後のセッションID再生成
app.post('/login', async (req, res) => {
const user = await authenticate(req.body.username, req.body.password);
if (user) {
// セッション固定攻撃対策
req.session.regenerate((err) => {
req.session.userId = user.id;
res.redirect('/dashboard');
});
}
});
対策3: JWT使用時の注意点
const jwt = require('jsonwebtoken');
// トークン生成
const token = jwt.sign(
{ userId: user.id },
process.env.JWT_SECRET,
{ expiresIn: '1h' } // 有効期限必須
);
// トークン検証
const decoded = jwt.verify(token, process.env.JWT_SECRET);
// ⚠️ JWTの注意点
// - 秘密鍵は絶対に漏らさない
// - alg: "none"攻撃に注意(ライブラリの最新版使用)
// - 機密情報をpayloadに入れない(Base64エンコードされるだけ)
対策4: 多要素認証(MFA)
// TOTP (Time-based One-Time Password)
const speakeasy = require('speakeasy');
// シークレット生成(登録時)
const secret = speakeasy.generateSecret({ name: 'MyApp (user@example.com)' });
// QRコード表示
const qrcode = require('qrcode');
qrcode.toDataURL(secret.otpauth_url, (err, dataUrl) => {
// dataUrlをユーザーに表示
});
// 検証(ログイン時)
const verified = speakeasy.totp.verify({
secret: secret.base32,
encoding: 'base32',
token: userInputCode,
window: 2 // 時刻ズレ許容
});
5. HTTPS化とセキュアな通信
なぜHTTPSが必須なのか
- 通信内容の暗号化(盗聴防止)
- 改ざん検知
- なりすまし防止
- SEO効果(Googleが優遇)
- 最新Web API(Geolocation等)はHTTPS必須
Let’s Encryptで無料SSL証明書
# Certbot インストール(Ubuntu)
sudo apt update
sudo apt install certbot python3-certbot-nginx
# 証明書取得
sudo certbot --nginx -d example.com -d www.example.com
# 自動更新設定
sudo certbot renew --dry-run
Node.jsでHTTPSサーバー
const https = require('https');
const fs = require('fs');
const options = {
key: fs.readFileSync('private-key.pem'),
cert: fs.readFileSync('certificate.pem')
};
https.createServer(options, app).listen(443);
// HTTPからHTTPSへリダイレクト
const http = require('http');
http.createServer((req, res) => {
res.writeHead(301, { Location: `https://${req.headers.host}${req.url}` });
res.end();
}).listen(80);
HSTS(HTTP Strict Transport Security)
// Express.js
app.use((req, res, next) => {
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
next();
});
これにより、ブラウザは常にHTTPSでアクセスするようになります。
6. CORS(オリジン間リソース共有)
CORSとは
異なるオリジン(ドメイン・ポート・プロトコル)間でのリソース共有を制御する仕組み。
例:
https://example.com → オリジンA
https://api.example.com → オリジンB(サブドメイン違い)
https://example.com:3000 → オリジンC(ポート違い)
脆弱な設定(NG)
// すべてのオリジンを許可(危険)
app.use(cors({ origin: '*' }));
安全な設定
const cors = require('cors');
// ホワイトリスト方式
const allowedOrigins = ['https://example.com', 'https://www.example.com'];
app.use(cors({
origin: function (origin, callback) {
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('CORS policy violation'));
}
},
credentials: true, // Cookieを含める場合
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization']
}));
プリフライトリクエスト
// ブラウザが自動的にOPTIONSリクエストを送信
// サーバー側で適切に処理する必要がある
app.options('*', cors()); // プリフライトに対応
7. CSP(コンテンツセキュリティポリシー)
CSPとは
読み込み可能なリソース(スクリプト、スタイル、画像等)の出所を制限し、XSS攻撃を緩和。
基本設定
app.use((req, res, next) => {
res.setHeader(
'Content-Security-Policy',
"default-src 'self'; " +
"script-src 'self' https://cdn.jsdelivr.net; " +
"style-src 'self' 'unsafe-inline'; " +
"img-src 'self' data: https:; " +
"font-src 'self' https://fonts.gstatic.com; " +
"connect-src 'self' https://api.example.com;"
);
next();
});
ディレクティブ解説
default-src 'self'— デフォルトは同一オリジンのみscript-src— JavaScriptの読み込み元'self'— 同一オリジン'unsafe-inline'— インラインスクリプト許可(非推奨)'unsafe-eval'— eval()許可(非推奨)https://cdn.example.com— 特定CDN許可
style-src— CSSの読み込み元img-src— 画像の読み込み元data:— Data URI許可
connect-src— fetch/XHRの接続先
Nonce方式(推奨)
const crypto = require('crypto');
app.use((req, res, next) => {
res.locals.nonce = crypto.randomBytes(16).toString('base64');
res.setHeader(
'Content-Security-Policy',
`script-src 'nonce-${res.locals.nonce}'`
);
next();
});
<!-- テンプレート -->
<script nonce="<%= nonce %>">
console.log('This script is allowed');
</script>
レポート機能
res.setHeader(
'Content-Security-Policy',
"default-src 'self'; report-uri /csp-violation-report"
);
app.post('/csp-violation-report', (req, res) => {
console.log('CSP violation:', req.body);
res.status(204).end();
});
8. 安全でないデシリアライゼーション
脅威の概要
信頼できないデータをデシリアライズすることで、リモートコード実行やDoS攻撃のリスク。
危険な例
# Python (pickle)
import pickle
# 攻撃者が細工したデータ
data = request.POST['data']
obj = pickle.loads(data) # 危険!任意コード実行の可能性
対策
# JSON使用(安全)
import json
data = request.POST['data']
obj = json.loads(data) # データのみ、コードは実行されない
// Node.js
// NG
const obj = eval(userInput); // 絶対NG
// OK
const obj = JSON.parse(userInput);
9. 機密情報の露出
環境変数の使用
// NG: ハードコード
const apiKey = 'sk-1234567890abcdef';
// OK: 環境変数
const apiKey = process.env.API_KEY;
# .env ファイル(Gitに含めない!)
API_KEY=sk-1234567890abcdef
DATABASE_URL=postgresql://user:pass@localhost/db
JWT_SECRET=your-secret-key
.env
.env.local
.env.production
シークレットスキャン
# git-secrets インストール
brew install git-secrets
# リポジトリに設定
git secrets --install
git secrets --register-aws
# コミット前に自動チェック
git secrets --scan
エラーメッセージの適切な処理
// NG: 詳細なエラーを返す
app.use((err, req, res, next) => {
res.status(500).json({ error: err.stack });
});
// OK: 本番環境では一般的なメッセージ
app.use((err, req, res, next) => {
console.error(err.stack); // サーバー側でログ
if (process.env.NODE_ENV === 'production') {
res.status(500).json({ error: 'Internal server error' });
} else {
res.status(500).json({ error: err.message });
}
});
10. セキュリティヘッダー
Helmet.js(Node.js)
const helmet = require('helmet');
app.use(helmet()); // 一括設定
// または個別設定
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "https://cdn.jsdelivr.net"]
}
}));
app.use(helmet.hsts({
maxAge: 31536000,
includeSubDomains: true,
preload: true
}));
主要なセキュリティヘッダー
// X-Frame-Options(クリックジャッキング防止)
res.setHeader('X-Frame-Options', 'DENY');
// X-Content-Type-Options(MIMEスニッフィング防止)
res.setHeader('X-Content-Type-Options', 'nosniff');
// Referrer-Policy(リファラー情報制御)
res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
// Permissions-Policy(機能制限)
res.setHeader('Permissions-Policy', 'geolocation=(), microphone=()');
セキュリティチェックリスト
開発・デプロイ前に必ずチェック:
入力検証
- 全ユーザー入力をサニタイズ・バリデーション
- ホワイトリスト方式で検証
- ファイルアップロードの拡張子・サイズ制限
認証・認可
- パスワードのハッシュ化(bcrypt等)
- セッションIDの安全な生成・再生成
- JWT使用時は有効期限設定
- 多要素認証の実装(重要な操作)
通信
- HTTPS化(Let’s Encrypt等)
- HSTS設定
- CORS適切に設定
ヘッダー
- CSP設定
- セキュリティヘッダー(Helmet等)
- Cookie属性(Secure, HttpOnly, SameSite)
データベース
- プリペアドステートメント使用
- 最小権限の原則(DB権限)
- バックアップ暗号化
その他
- 依存パッケージの脆弱性スキャン(npm audit, Snyk)
- エラーメッセージの適切な処理
- ログ監視
- レート制限(ブルートフォース対策)
セキュリティツール
1. OWASP ZAP(脆弱性スキャン)
# Docker版
docker run -t owasp/zap2docker-stable zap-baseline.py -t https://example.com
2. npm audit(Node.js)
# 脆弱性チェック
npm audit
# 自動修正
npm audit fix
3. Snyk
# インストール
npm install -g snyk
# 脆弱性スキャン
snyk test
# 監視
snyk monitor
まとめ
Webセキュリティは開発の一部であり、後付けではありません。この記事で紹介した10の脅威と対策を実装するだけで、大半の攻撃を防げます。
重要ポイント:
- ユーザー入力は常に疑う
- HTTPS必須
- プリペアドステートメント使用
- セキュリティヘッダー設定
- 定期的な脆弱性スキャン
2026年、Webセキュリティは法的義務にもなりつつあります。今日から実践しましょう。
関連記事:
関連ツール:
- DevToolBox — Base64エンコード、JWT解析等
- chmod計算機 — ファイル権限の安全な設定
Stay Secure!