10
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

セキュリティ学習用の脆弱なWebアプリケーションを作ってみた【React + Node.js + Docker】

Posted at

はじめに

Webアプリケーションのセキュリティを学ぶために、意図的に100以上の脆弱性を埋め込んだ学習用アプリケーションを作成しました。この記事では、その開発過程と学んだことを共有します。

IBM Bobを活用した開発

このプロジェクトは、IBM Bobの対話型AIアシスタントを使って、すべての実装を行いました。

IBM Bobで実現できたこと

  • フルスタック開発: React 18フロントエンド + Node.js/Expressバックエンドの完全実装
  • Docker環境構築: 5つのコンテナ(frontend, backend, PostgreSQL, MongoDB, Adminer)の設定
  • 100以上の脆弱性の埋め込み: OWASP Top 10に基づく体系的な脆弱性実装
  • トラブルシューティング: bcryptのネイティブモジュールエラー、CORS設定、データフェッチングの問題解決
  • 包括的なドキュメント作成: README、VULNERABILITIES.md、この記事の作成
  • Git/GitHub統合: リポジトリ初期化、コミット、プッシュまで完全自動化

IBM Bobの強み

  1. 広範囲な技術知識: フロントエンド、バックエンド、データベース、Docker、セキュリティなど、複数の技術領域を横断的にサポート
  2. 段階的な実装: 複雑なプロジェクトを適切なステップに分解し、一つずつ確実に実装
  3. 問題解決能力: エラーが発生した際、原因を分析し、適切な解決策を提示
  4. ベストプラクティスの提案: セキュリティ、コーディング規約、プロジェクト構造など、業界標準に沿った実装

このようなWeb開発の学習プロジェクトにも、IBM Bobは非常に有効なツールです。特に、セキュリティのような専門的な知識が必要な分野では、AIアシスタントの支援が大きな助けとなります。

⚠️ 重要な注意事項
この記事で紹介するコードには意図的にセキュリティ脆弱性が含まれています。教育目的のみで使用し、本番環境では絶対に使用しないでください。

🎯 プロジェクトの目的

  • OWASP Top 10の脆弱性を実践的に学ぶ
  • ペネトレーションテストの練習環境を構築
  • セキュアコーディングのベストプラクティスを理解する

🛠️ 技術スタック

  • フロントエンド: React 18
  • バックエンド: Node.js + Express
  • データベース: PostgreSQL + MongoDB
  • コンテナ: Docker + Docker Compose
  • テストツール: OWASP ZAP, Burp Suite, SQLMap

📁 プロジェクト構造

security-learning-project/
├── vulnerable-app/          # 脆弱なアプリケーション
│   ├── backend/            # Node.js API
│   ├── frontend/           # React SPA
│   └── docker-compose.yml  # 環境構築
├── VULNERABILITIES.md      # 脆弱性リスト
└── README.md

🚀 環境構築

Docker Composeで一発起動

cd vulnerable-app
docker-compose up -d

これだけで以下のサービスが起動します:

🔐 埋め込んだ脆弱性(一部紹介)

1. SQLインジェクション

脆弱なコード例:

// backend/src/routes/auth.js
router.post('/login', async (req, res) => {
    const { username, password } = req.body;
    
    // ❌ ユーザー入力を直接SQLに埋め込み
    const query = `SELECT * FROM users WHERE username = '${username}'`;
    const result = await executeRawQuery(query);
    
    // ...
});

攻撃例:

ユーザー名: admin' OR '1'='1
パスワード: anything

これで認証をバイパスしてログインできてしまいます。

正しい実装:

// ✅ パラメータ化クエリを使用
const query = 'SELECT * FROM users WHERE username = $1';
const result = await pool.query(query, [username]);

2. XSS(クロスサイトスクリプティング)

脆弱なコード例:

// frontend/src/components/Home.js
<div
    className="post-content"
    dangerouslySetInnerHTML={{ __html: post.content }}
/>

攻撃例:
投稿内容に以下を入力:

<script>alert('XSS Attack!')</script>

正しい実装:

// ✅ HTMLをエスケープして表示
<div className="post-content">
    {post.content}
</div>

3. 認証なしのパスワードリセット

脆弱なコード例:

// backend/src/routes/auth.js
router.post('/reset-password', async (req, res) => {
    const { username, newPassword } = req.body;
    
    // ❌ 認証チェックなし!
    const hashedPassword = await bcrypt.hash(newPassword, 10);
    const query = `UPDATE users SET password = '${hashedPassword}' WHERE username = '${username}'`;
    await executeRawQuery(query);
    
    res.json({ message: 'Password reset successfully' });
});

誰でも他人のパスワードを変更できてしまいます。

正しい実装:

// ✅ メール確認とトークン検証を実装
router.post('/reset-password', verifyResetToken, async (req, res) => {
    // トークンの検証後にパスワード変更
});

4. 権限昇格の脆弱性

脆弱なコード例:

// backend/src/routes/auth.js
router.post('/register', async (req, res) => {
    const { username, email, password, role } = req.body;
    
    // ❌ ユーザーが自分でroleを指定できる
    const userRole = role || 'user';
    
    const query = `INSERT INTO users (username, email, password, role) 
                   VALUES ('${username}', '${email}', '${hashedPassword}', '${userRole}')`;
});

攻撃例:
登録時にブラウザの開発者ツールで以下を送信:

{
  "username": "hacker",
  "email": "hacker@example.com",
  "password": "test123",
  "role": "admin"
}

正しい実装:

// ✅ roleはサーバー側で固定
const userRole = 'user'; // クライアントからの入力を無視

5. 機密情報の露出

脆弱なコード例:

// backend/src/server.js
app.get('/health', (req, res) => {
    res.json({
        database: {
            password: process.env.DB_PASSWORD  // ❌ パスワードを露出
        },
        secrets: {
            jwtSecret: process.env.JWT_SECRET  // ❌ JWT秘密鍵を露出
        }
    });
});

正しい実装:

// ✅ 機密情報を含めない
app.get('/health', (req, res) => {
    res.json({
        status: 'ok',
        timestamp: new Date().toISOString()
    });
});

📊 埋め込んだ脆弱性の統計

OWASP Top 10 (2021) 分類

カテゴリ 脆弱性数
A01: Broken Access Control 15
A02: Cryptographic Failures 18
A03: Injection 20
A04: Insecure Design 8
A05: Security Misconfiguration 12
A07: Identification and Authentication Failures 14
A09: Security Logging and Monitoring Failures 13

合計: 100以上の脆弱性

深刻度別

深刻度 脆弱性数
Critical 25
High 35
Medium 30
Low 10+

🧪 実際に試してみる

1. SQLインジェクションでログイン

ユーザー名: admin' OR '1'='1
パスワード: (何でもOK)

2. XSS攻撃

新規投稿で以下を入力:

<script>alert(document.cookie)</script>

3. 権限昇格

ユーザー登録時に開発者ツールのNetworkタブでリクエストを確認し、role: "admin"を追加。

🔍 開発中に遭遇した問題と解決策

問題1: Docker内でbcryptがクラッシュ

エラー:

Error: /app/node_modules/bcrypt/lib/binding/napi-v3/bcrypt_lib.node: 
Exec format error

原因:
ローカルのnode_modules(macOS ARM64用)がDockerコンテナ(Linux ARM64)にマウントされ、バイナリの互換性がなかった。

解決策:

# docker-compose.yml
volumes:
  # ❌ これだとnode_modulesもマウントされる
  # - ./backend:/app
  
  # ✅ 必要なファイルだけマウント
  - ./backend/src:/app/src
  - ./backend/package.json:/app/package.json

問題2: CORSエラー

エラー:

Access to XMLHttpRequest has been blocked by CORS policy: 
Request header field x-api-key is not allowed

解決策:

// server.js
app.use(cors({
    origin: '*',
    allowedHeaders: ['Content-Type', 'Authorization', 'X-API-Key']
}));

問題3: フロントエンドでのデータ取得エラー

エラー:

TypeError: posts.map is not a function

原因:
バックエンドAPIが{posts: [...]} という形式で返していたが、フロントエンドは配列を期待していた。

解決策:

// Home.js
const loadPosts = async () => {
    const data = await postAPI.getPosts();
    // APIレスポンスの形式に対応
    const postsArray = data.posts || data;
    setPosts(Array.isArray(postsArray) ? postsArray : []);
};

📚 学んだこと

1. セキュリティは多層防御が重要

一つの対策だけでは不十分。以下のような多層的なアプローチが必要:

  • 入力検証: クライアント側とサーバー側の両方で
  • 出力エスケープ: XSS対策
  • パラメータ化クエリ: SQLインジェクション対策
  • 認証・認可: すべてのエンドポイントで
  • セキュリティヘッダー: CSP、HSTS、X-Frame-Optionsなど

2. 「デフォルトで安全」な設計

  • パスワードは必ずハッシュ化
  • セッションはHTTPOnlyとSecure属性を有効に
  • CORSは必要最小限のオリジンのみ許可
  • エラーメッセージは詳細を含めない

3. 開発者ツールは攻撃者のツールでもある

ブラウザの開発者ツールで以下が可能:

  • リクエストの改ざん
  • localStorageの閲覧・編集
  • JavaScriptの実行

クライアント側の検証だけでは不十分で、サーバー側での検証が必須。

4. ログとモニタリングの重要性

攻撃を検知するには:

  • 適切なログ記録
  • 異常なアクセスパターンの検出
  • セキュリティイベントのアラート

ただし、ログに機密情報を含めないよう注意。

🎓 このプロジェクトを使った次のステップ(推奨)

  1. ペネトレーションテスト

    • OWASP ZAPでの自動スキャン
    • Burp Suiteでの手動テスト
    • SQLMapでのSQLインジェクション検証
  2. セキュアなアプリケーションの実装

    • すべての脆弱性を修正
    • ベストプラクティスの適用
    • セキュリティテストの自動化
  3. CI/CD統合

    • セキュリティスキャンの自動化
    • 脆弱性検出時のビルド失敗

📖 参考資料

🔗 リポジトリ

完全なソースコードとドキュメントはGitHubで公開しています:

GitHub: https://github.com/mnori0211/security-learning-project

リポジトリには以下が含まれています:

  • 完全なソースコード(フロントエンド + バックエンド)
  • Docker環境設定
  • 100以上の脆弱性の詳細リスト(VULNERABILITIES.md)
  • セットアップガイド(README.md)

このプロジェクトは教育目的で作成されています。実際のアプリケーション開発では、これらの脆弱性を絶対に含めないでください。

まとめ

脆弱なアプリケーションを意図的に作ることで、以下を学べました:

  • ✅ 一般的なセキュリティ脆弱性の実装と悪用方法
  • ✅ 攻撃者の視点でのシステム評価
  • ✅ セキュアコーディングのベストプラクティス
  • ✅ Docker環境でのトラブルシューティング

セキュリティは一度学んで終わりではなく、継続的な学習が必要です。このプロジェクトを通じて、実践的なセキュリティスキルを身につけることができました。

皆さんもぜひ、安全な環境でセキュリティテストを試してみてください!

10
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?