コース目次 / 第3章

BFLA と mass assignment

権限チェックなしのdeleteUserミューテーション(BFLA)と、入力オブジェクトを丸ごとマージするupdateProfile(mass assignment)で、roleフィールドをadminに昇格させます。両方を実際に起こします。

第3章 / 全6章目安 約12分この章のゴール: BFLAとmass assignmentを起こし、それぞれの原因を言えるようになる

同じ vuln-api.js で、あと2つの穴を見ます。

BFLA — ミューテーションの、権限チェック漏れ

deleteUser は、名前からして管理者専用の操作に見えます。一般ユーザー(X-UID: 1、role: user)のまま、呼んでみます。

curl -s -X POST -H "Content-Type: application/json" -H "X-UID: 1" \
  -d '{"query":"mutation { deleteUser(id: 2) }"}' \
  http://127.0.0.1:4200/
{"data":{"deleteUser":true}}

一般ユーザーのまま、他のユーザーを削除できてしまいました。これが BFLA(Broken Function Level Authorization)——sec-authz で見た「垂直の権限昇格」の GraphQL 版です。リゾルバの中身を見ると:
deleteUser: ({ id }) => { delete users[id]; return true; }
呼び出した人の role を、一度も確認していません。GraphQL では、管理者用の操作も一般ユーザー用の操作も、同じ1つのエンドポイントに並んでいます。だから、「これは管理者専用フィールドだ」と、リゾルバ自身が主張しない限り、誰でも呼べてしまいます。

mass assignment — 入力を、丸ごと信じる

もう一つ、GraphQL でとくに起きやすい穴です。updateProfile は、name や bio を更新するための、一見無害なミューテーションです。

curl -s -X POST -H "Content-Type: application/json" -H "X-UID: 1" \
  -d '{"query":"mutation { updateProfile(input: {name: \"架空太郎\", role: \"admin\"}) { id name role } }"}' \
  http://127.0.0.1:4200/
{"data":{"updateProfile":{"id":1,"name":"架空太郎","role":"admin"}}}

プロフィール更新のつもりで送ったリクエストで、自分の role を admin に書き換えられてしまいました。原因は、リゾルバのこの1行——
updateProfile: ({ input }) => { Object.assign(users[uid], input); return users[uid]; }
input オブジェクトに何が入っているかを見ず、丸ごとマージしています。スキーマの ProfileInput 型には role が定義されているため(たまたま、あるいは開発の都合で)、クライアントは自由に role を送れてしまいます。

なぜ「mass assignment」と呼ぶのか

名前のとおり、「入力されたパラメータを、まとめて(mass)、そのまま代入(assignment)する」実装パターンが原因です。「更新用のオブジェクトを受け取って、まるごと Object.assign(や ORM の update(input))に渡す」のは、コードとしては短く書けて便利です。でも、「この入力オブジェクトに、本来更新してよくないフィールドまで紛れていないか」を確認しないまま丸ごと適用すると、それがそのまま穴になります。GraphQL は、入力の形をスキーマで定義する性質上、この「型に入っているから安全」という錯覚が起きやすい領域です。

持ち帰る一言

BFLAは、フィールド単位の権限チェック漏れ。mass assignmentは、入力の丸信じ。 どちらも「GraphQLは1つの窓口に機能が集まる」という特性が、穴を大きくしています。次の章で、両方まとめて直します。

こうなっていればOK

卒業まであと2章です。

この章はまだ完了していません。