Node.jsにおけるMass Assignment脆弱性を防ぐ
2023年3月28日
0 分で読めますMass Assignmentは、攻撃者が予測可能なレコードのパターンを悪用し、不正な操作を実行できる脆弱性です。通常、クライアントから提供されたデータをデータモデルにバインドする際に、プロパティがフィルタリングされていないと発生します。この種の脆弱性があると、攻撃者はPOSTリクエストのペイロードにオブジェクトを追加し、本来変更できないプロパティを変更できてしまいます。
Node.jsは、クロスプラットフォームでのWebアプリケーション開発に利用できるオープンソースのサーバー環境です。開発者は、パワフルでスケーラブルなアプリケーションをすばやく構築できます。Node.jsは実績が豊富で、本番環境にも対応したプラットフォームですが、アプリケーションの構築にインストールするサードパーティ製パッケージに関連するサプライチェーンセキュリティなど、アプリケーションセキュリティの課題もあります。Node.jsアプリケーションでは、npmレジストリから何千ものオープンソースnpmパッケージを利用することもあり、さまざまな攻撃ベクトルにさらされる可能性があります。
Mass Assignment脆弱性は、データベースにPOSTリクエストでペイロードを送信するNode.jsアプリケーションでよく見られます。攻撃者はこの脆弱性を利用してSQLインジェクションなど、データベース内のデータを標的とする攻撃を仕掛けることができます。Mass Assignment脆弱性を悪用すると、システムを完全に掌握したり、機密データを盗み出したりできるため、この種の攻撃から守ることが重要です。
この記事では、Node.jsプロジェクトにおけるMass Assignment脆弱性と攻撃者による悪用方法、Webアプリケーションを保護する方法を紹介します。
Node.jsのMass Assignment脆弱性を取り除く
この例では、Node.jsとMongoDB用のオブジェクトデータモデリング(ODM)ライブラリであるMongooseを使用します。コード内のオブジェクトとMongoDB内のオブジェクトを連携させ、スキーマを検証し、データのリレーションシップ管理を支援します。
上記を踏まえると、Node.jsのMongoDBアプリケーションをMass Assignmentから保護するには、Mongooseで対策する必要があります。以下は、Node.jsアプリケーションにおけるMass Assignment脆弱性の悪用例です。
前提条件
このプロジェクトを進めるには、次のものが必要です。
Node.jsがインストールされていること
MongoDBデータベースと有効な接続文字列へのアクセス。このチュートリアルではMongoDB Atlasのクラウド環境を使用しますが、ローカルのMongoDBサーバーを立ち上げて、接続文字列を適宜変更することもできます。
Node.jsにおけるMass Assignment
このプロジェクトを始めるには、ターミナルを開いて次のコマンドを実行します。
次に、依存関係をインストールします。
上記のフォームから送信されたデータを処理するには、modelsフォルダにnewUser.jsファイルを作成し、ユーザースキーマを定義します。
このモデルには、登録フォームのすべてのフィールドが含まれています。追加フィールドisAdminはBoolean型で、ユーザーのロールを定義します。このフィールドをtrueに設定すると管理者権限を持つユーザーが作成され、falseに設定すると管理者権限を持たないユーザーが作成されます。
routesという新しいディレクトリを作成し、ユーザー情報を処理するroutes.jsファイルを追加します。
上記のコードはフォームのデータを取得し、newUSerモデルを使って構造化します。
このコードには、Mass Assignment脆弱性を引き起こす問題が2つあります。1つ目は、機密フィールドisAdminに、よく使われる名前と型を使用していることです。2つ目は、ユーザーモデルに未使用の機密フィールドが含まれていることです。この脆弱性を悪用するため、サーバーを起動してみましょう。
プロジェクトのルートフォルダにserver.jsというファイルを作成し、次のコードを追加します。<APP_ID>は接続文字列内の値に置き換えてください。
node server.jsを実行してサーバーを起動します。
悪意ある攻撃者は、isAdminフィールドの値をtrueにしたcurl POSTリクエストを作成することで、この脆弱性を悪用できます。以下のコードを参照してください。
上記のリクエストにより、管理者権限を持つユーザーMikeが作成されます。このユーザーは認証情報を使ってシステムにログインし、管理者ができることをすべて実行できるようになります。
Mass Assignment攻撃を防ぐ
アプリケーションのMass Assignment攻撃に対する防御を強化するには、3つの変更を加えます。
シンプルなモデルを作成する
シンプルなモデルには、ユーザーが入力する想定の項目だけを含め、機密フィールドは含めません。
上記の例では、まずnewUserモデルから機密フィールドを削除することが、最も重要な対策となります。以下のように変更します。
攻撃者がソースコードにアクセスしても、機密フィールドを見つけて攻撃に利用できなくなります。また、データベースでユーザーのロールを定義する際は、isAdmin、admin、roleなど、推測されやすい用語の使用も避けましょう。攻撃者に推測され、攻撃に利用される可能性があります。
ユーザー入力にスキーマ検証を使用する
underscoreを使った検証
アプリを保護するもう1つの方法は、POSTリクエストで処理可能なフィールドを指定して制限することです。これは許可リスト方式と呼ばれ、underscoreライブラリを使用します。まず、ライブラリをインストールします。
routesフォルダに移動し、route.jsのコードを次の内容に置き換えます。
上記のコードでは、pick関数を使ってPOSTリクエストから抽出する変数を指定しています。これにより、攻撃者が機密フィールドisAdminを使用するのを防げます。
underscoreを使う場合の課題は、データが有効かどうかを判断するために、アプリケーション側でデータを処理しなければならないことです。つまり、エラーを十分早い段階で検出できません。
Zodを使った検証
堅牢なスキーマ検証には、Zodを使用しましょう。指定した構造、パターン、データ型にデータが厳密に適合するかを検証します。Zodは不完全または不正なデータを早期に検出し、アプリケーションのエラーを防ぎます。また、エラーの原因となった入力項目やエラーの種類をユーザーに知らせるメッセージも簡単に作成できます。
次のコードスニペットはZodの動作を示しています。以下のスニペットはTypeScriptで記述されているため、追加の設定が必要です。
スキーマに一致しないデータを入力すると、invalid_type_errorが発生したことを示すエラーが表示されます。フィールドを空欄のままにすると、required_errorが表示されます。
ORMにおけるMass Assignment脆弱性のその他の例
一般的なORMソリューションにおけるMass Assignment脆弱性を、さらに3つの例で見ていきましょう。
SequelizeコードにおけるMass Assignment脆弱性
Mass Assignmentに対して脆弱なSequelizeコードの例を見てみましょう。このコードはユーザーモデルを定義し、ユーザーを作成して、ユーザー情報をデータベースに保存します。
新しいユーザーを作成するとき、コントローラーは以下のように上記のモデルを呼び出します。
上記のコードは、機密フィールドis_verifiedを公開しているため脆弱です。攻撃者はis_verifiedフィールドをtrueに設定したPOSTリクエストを作成し、検証ステップを回避できます。
ユーザー作成時にis_verifiedフィールドを除外すれば、このコードのMass Assignment脆弱性を取り除けます。安全なコードは次のようになります。
これにより、受け付けるPOSTリクエストの変数をusername、email、passwordに制限できます。
PrismaコードにおけるMass Assignment脆弱性
以下は、機密フィールドRoleを公開してしまう、Mass Assignmentに対して脆弱なPrismaコードの例です。
ユーザーを追加するには、add_userルートを作成します。
このPOSTリクエストでは、ユーザーオブジェクトを含むボディが想定されています。上記のコードはMass Assignment攻撃に対して脆弱です。攻撃者がroleフィールドにADMINを設定すると、管理者権限を取得できるためです。
上記のモデルから機密フィールドRoleを除外すれば、このコードの脆弱性を取り除けます。
MySQLコードにおけるMass Assignment脆弱性
以下は、機密フィールドisAdminを公開する、Mass Assignmentに対して脆弱なMySQLコードの例です。
このコードは、上記で定義したユーザーを追加します。
上記のコードは、機密フィールドisAdminを公開し、POSTリクエストに含められるため、Mass Assignment脆弱性があります。攻撃者はisAdminフィールドをtrueに設定したPOSTリクエストを作成し、管理者ユーザーを作成できます。
userオブジェクトの作成時に機密フィールドを除外すれば、この脆弱性を修正できます。安全なコードは次のとおりです。
次のコマンドでテーブルを変更すると、isAdminフィールドのデフォルト値をfalseに設定できます。
Node.jsのコードを安全に保つ
Node.js自体は安全ですが、Webアプリケーションの構築に必要なパッケージが安全とは限りません。データベースにリクエストを送信するNode.jsアプリケーションでは、Mass Assignment脆弱性が最も一般的な攻撃ベクトルの1つです。悪意あるリクエストをデータベースに送信するSQLインジェクション攻撃を可能にします。
ただし、入力値をサニタイズすれば、Node.jsのMass Assignmentは簡単に防止できます。機密フィールドを公開しない、受け付けるフィールドを制限する、または一部のフィールドの編集を禁止することで対策できます。
堅牢なユーザー入力検証を実装しないと、システムはプロトタイプ汚染などの攻撃にさらされます。このような攻撃は、underscoreのような脆弱な検証メカニズムを回避できます。そのため、Zodのような堅牢なソリューションを必ず使用してください。
GitHubリポジトリでソースコードの完全な例を確認し、Node.jsでMass Assignment脆弱性を防ぐ方法をローカル環境で試してみましょう。
SnykのNode.jsセキュリティに関するベストプラクティス トップ10を参考に、Node.jsアプリケーションのセキュリティを強化しましょう。
Capture the Flagを始めよう
オンデマンドのバーチャル入門ワークショップを見て、Capture the Flagの課題の解き方を学びましょう。
