Node.js開発者が本番環境に送り込んでしまうセキュリティバグ

昨年、あるスタートアップのコードレビューを行いました。コードは綺麗に見え、テストもパスしていました。

しかし、次のような行を見つけました: const query = \SELECT * FROM users WHERE email = '${req.body.email}'``

これはSQLインジェクションのバグです。そのスタートアップはこのコードを8ヶ月間、本番環境で運用していました。開発者もCTOも誰も気づきませんでした。

これらのバグは、コードが正常に動作するため、目に見えません。ユーザーが入力フィールドに悪意のあるコマンドを入力するまでは、問題なく動き続けるのです。

次の5つのよくある間違いを避けましょう:

  1. ユーザー入力を含む生のSQL クエリにテンプレートリテラルを使用しないでください。これにより、攻撃者がデータベースにアクセスできてしまいます。
  • 悪い例: const query = \SELECT * FROM users WHERE email = '${email}'``
  • 良い例: パラメータ化クエリを使用する。 const query = 'SELECT * FROM users WHERE email = $1' db.query(query, [email])
  1. Gitへの機密情報の流出 開発者はよく .env ファイルをリポジトリにコミットしてしまいます。これにより、データベースのURLやAPIキーが露出してしまいます。必ず .env.gitignore ファイルに追加してください。

  2. jwt.verifyの代わりにjwt.decodeを使用する jwt.decode はトークンを読み取るだけです。そのトークンが本物かどうかはチェックしません。誰でもデコードされたトークンを偽造できてしまいます。

  • 悪い例: const user = jwt.decode(token)
  • 良い例: 常に署名を検証する。 const user = jwt.verify(token, process.env.JWT_SECRET)
  1. 認証エンドポイントにおけるレート制限の欠如 レート制限がないと、攻撃者はブルートフォース攻撃によって何百万ものパスワードを試行できてしまいます。ライブラリを使用してログイン試行回数を制限しましょう。
  • 良い例: 15分間に10回までの制限を追加する。
  1. 詳細すぎるエラーメッセージ 生の(詳細な)エラーメッセージをクライアントに送信すると、攻撃者がシステム構成を把握する手助けをしてしまいます。テーブル名やデータベースの種類が知られてしまいます。
  • 悪い例: res.status(500).json({ error: error.message })
  • 良い例: エラーは内部でログに記録する。ユーザーには汎用的なメッセージを送信する。 res.status(500).json({ error: 'Something went wrong' })

エンドポイントをリリースする前に、一つの質問を自分に投げかけてください: 「ユーザーが予期しないデータを送信したらどうなるか?」

セキュリティバグが複雑であることは稀です。悪意のある攻撃者の存在を考えるのを忘れたときに発生するのです。

あなたが本番環境で見つけたセキュリティバグは何ですか?

出典: https://dev.to/manolito99/the-security-bug-every-nodejs-developer-ships-to-production-49e6