未分類
個人開発で意識したセキュリティ対策
個人開発だからといってセキュリティを疎かにすると、ユーザーに迷惑をかけるだけでなく自分の信用も失う。最低限押さえるべきポイントをまとめる。
OWASP Top 10 を意識する
Webアプリケーションにおけるセキュリティリスクのトップ10である。これを知っているだけで大半の脆弱性は防げる。
| 順位 | リスク | 概要 |
|---|---|---|
| 1 | Broken Access Control | 認可の不備 |
| 2 | Cryptographic Failures | 暗号化の失敗 |
| 3 | Injection | SQLi, XSS, コマンドインジェクション |
| 4 | Insecure Design | 設計段階の問題 |
| 5 | Security Misconfiguration | 設定ミス |
| 6 | Vulnerable Components | 脆弱な依存関係 |
| 7 | Authentication Failures | 認証の不備 |
| 8 | Software Integrity Failures | 整合性の問題 |
| 9 | Logging Failures | ログ・監視の不備 |
| 10 | SSRF | サーバーサイドリクエストフォージェリ |
1. 入力値は全て信用しない
大原則: ユーザーからの入力は全て悪意があると仮定する。
SQLインジェクション対策
// NG: 文字列結合
const query = `SELECT * FROM users WHERE id = ${userId}`;
// OK: プリペアドステートメント
const query = 'SELECT * FROM users WHERE id = ?';
db.query(query, [userId]);
ORMを使っていても、生SQLを書く箇所は要注意。
XSS対策
// NG: innerHTML に直接代入
element.innerHTML = userInput;
// OK: textContent を使う
element.textContent = userInput;
// または適切にエスケープ
function escapeHtml(str) {
return str
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
Reactなどのフレームワークはデフォルトでエスケープするが、dangerouslySetInnerHTMLは要注意である。
コマンドインジェクション対策
// NG: ユーザー入力をそのままコマンドに
exec(`convert ${filename} output.png`);
// OK: 引数として渡す
execFile('convert', [filename, 'output.png']);
2. 認証・認可
パスワードの保存
// NG: 平文やMD5
const password = userInput; // 平文
const hash = md5(password); // 弱いハッシュ
// OK: bcrypt や Argon2
const bcrypt = require('bcrypt');
const hash = await bcrypt.hash(password, 12); // コスト12以上
セッション管理
- セッションIDは十分にランダムに(暗号学的乱数生成器を使う)
- ログイン後はセッションIDを再生成(セッション固定攻撃対策)
HttpOnly,Secure,SameSite属性を設定
// Express の例
app.use(session({
secret: process.env.SESSION_SECRET,
cookie: {
httpOnly: true,
secure: true, // HTTPS必須
sameSite: 'strict',
maxAge: 24 * 60 * 60 * 1000 // 1日
},
resave: false,
saveUninitialized: false
}));
JWT を使う場合
- 秘密鍵は十分に長く(256bit以上)
alg: none攻撃に注意(ライブラリで明示的にアルゴリズムを指定)- 有効期限を短めに設定
- リフレッシュトークンとアクセストークンを分離
3. HTTPS必須
2024年以降、HTTPは論外。
- Let's Encrypt で無料でSSL証明書取得可能
- Cloudflare や Vercel を使えば自動で設定される
- HSTS ヘッダーを設定
Strict-Transport-Security: max-age=31536000; includeSubDomains
4. 環境変数と秘密情報の管理
絶対にやってはいけないこと
// NG: コードにハードコード
const API_KEY = 'sk-1234567890abcdef';
const DB_PASSWORD = 'password123';
正しい管理方法
# .env ファイル(.gitignore に追加必須)
API_KEY=sk-1234567890abcdef
DB_PASSWORD=supersecretpassword
// 環境変数から読み込む
const apiKey = process.env.API_KEY;
.gitignore の設定
.env
.env.local
.env.*.local
*.pem
*.key
git履歴に入ってしまった場合: 秘密鍵は即座にローテーション。履歴から消すのは困難なので、漏れた前提で対応。
5. 依存関係の管理
脆弱性スキャン
# npm
npm audit
npm audit fix
# yarn
yarn audit
# Snyk(より詳細)
npx snyk test
定期的なアップデート
# 更新可能なパッケージを確認
npm outdated
# Dependabot や Renovate を設定して自動PR
lockfileをコミット
package-lock.json や yarn.lock は必ずコミット。依存関係のバージョンを固定することで、サプライチェーン攻撃のリスクを軽減。
6. エラーハンドリング
本番環境でスタックトレースを見せない
// NG: エラー詳細をそのまま返す
app.use((err, req, res, next) => {
res.status(500).json({ error: err.stack });
});
// OK: 本番では汎用メッセージ
app.use((err, req, res, next) => {
console.error(err); // ログには記録
res.status(500).json({
error: process.env.NODE_ENV === 'production'
? 'Internal Server Error'
: err.message
});
});
7. レート制限
ブルートフォース攻撃やDoSへの対策である。
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15分
max: 100, // 100リクエストまで
message: 'Too many requests'
});
app.use('/api/', limiter);
// ログイン試行はより厳しく
const loginLimiter = rateLimit({
windowMs: 60 * 60 * 1000, // 1時間
max: 5, // 5回まで
});
app.use('/api/login', loginLimiter);
8. CORS設定
// NG: 全許可
app.use(cors());
// OK: 必要なオリジンのみ
app.use(cors({
origin: ['https://myapp.com', 'https://www.myapp.com'],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
credentials: true
}));
9. セキュリティヘッダー
helmetミドルウェアで一括設定する。
const helmet = require('helmet');
app.use(helmet());
設定されるヘッダー:
X-Content-Type-Options: nosniffX-Frame-Options: DENYContent-Security-PolicyX-XSS-Protection(レガシーブラウザ向け)
CSP(Content Security Policy)の例
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "https://cdn.example.com"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"],
}
}));
10. ログと監視
記録すべき情報
- 認証の成功/失敗
- 認可エラー
- 入力バリデーションエラー
- 例外・エラー
記録してはいけない情報
- パスワード
- クレジットカード番号
- 個人情報(必要最小限に)
// NG
console.log(`Login attempt: user=${email}, password=${password}`);
// OK
console.log(`Login attempt: user=${email}, success=${success}, ip=${req.ip}`);
チェックリスト
個人開発でも最低限これだけは確認:
- HTTPS を使っている
- パスワードは bcrypt/Argon2 でハッシュ化
- SQLはプリペアドステートメント
- ユーザー入力はエスケープ/サニタイズ
- .env は .gitignore に入っている
- 本番でエラー詳細を露出していない
- npm audit でクリティカルな脆弱性がない
- Cookie に HttpOnly, Secure, SameSite 設定
- CORS は必要なオリジンのみ許可
- レート制限を設定している
面接での回答ポイント
「セキュリティで意識していることは?」
- OWASP Top 10 を把握し、特にインジェクション系を意識
- 入力値は全て信用しない原則
- 秘密情報は環境変数で管理、コードにハードコードしない
- 依存関係の脆弱性を定期的にチェック
「具体的にどんな対策をした?」
- bcrypt でパスワードハッシュ化(コスト12)
- helmet でセキュリティヘッダー一括設定
- express-rate-limit でブルートフォース対策
- npm audit を CI に組み込んで自動チェック
「万が一漏洩したらどうする?」
- 影響範囲の特定
- 漏洩した認証情報のローテーション
- ユーザーへの通知(必要に応じて)
- 原因調査と再発防止策の実施