コース目次 / 第3章
BFLA と mass assignment
権限チェックなしのdeleteUserミューテーション(BFLA)と、入力オブジェクトを丸ごとマージするupdateProfile(mass assignment)で、roleフィールドをadminに昇格させます。両方を実際に起こします。
同じ 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章です。
この章はまだ完了していません。
保存できませんでした(プライベートブラウズ中かもしれません)。この端末に進捗は残りませんが、先へは進めます。