GraphQL採用サービスで任意カラムを取得できる脆弱性を見つけた話
初Zennです。
はじめてZennを使ってみました。いつもはTwitterにいるmipsparcです。鉄道と関連技術が好きで、本職ではPHPを書いています。別途ブログもあります https://mipsparc.wordpress.com/
今年6月、TABICA(7/26に改称し、新名称「aini」、運営会社 株式会社ガイアックス)というイベント募集仲介サービスに、個人情報を自由に取得できる脆弱性を見つけて報告したところ、速やかに修正されて報奨金をいただいた話をします。
GraphQLは知ってますか?
はじめに、GraphQLはご存知でしょうか。普通のHTTP API(RESTともいいますが)では、リクエストするエンドポイントが用途ごとに決まっているのが普通でしょう。たとえば /userdata にアクセスすると、JSONなどの形式でユーザーデータの決まった内容が帰ってくるものです。
GraphQLは先進的なWebサービス(GitHubなど)で導入されており、欲しいカラムとレスポンス構造を送ることで、欲しい情報だけ取得することができるので、効率の良いAPIです。一方で実装の複雑性が増してしまい、特に認証が必要なWebサービスでは、返して良い情報をフィルタする必要があるため、導入が難しいです。また、構造を意図的に複雑化することで、DoS攻撃を容易に引き起こせる欠点があります。オープンデータサービス(秘密情報が一切ない)には向いているかも知れません。
なぜTABICAをつかったのか
今回、JR東日本が東京総合車両センター公開イベントの募集にTABICAを使用していたため、使用しました。ところが、あまりのアクセス集中でレスポンスを返せない状況になっていたのを見た私は怒って、通信の内容を見ることにしました。そこで、GraphQLを使っていることに気づいた次第です。(幸いタイミングよくイベントの予約には滑り込めて、TIMS中央装置や端末装置を見る素敵な体験ができました。ありがとうTKのみなさん)
通信内容
さて、GraphQLを使っているのに気づいた私は、非公開APIに使うのは珍しいと思って、通信内容を見てみることにしました。
まず、ログイン済みトップページではこのようなリクエストが飛びます(再現です)
curl 'https://helloaini.com/graphql'
-H 'content-type: application/json' \
(一部省略)
--data-raw $'[{"operationName":"GetCurrentUser","variables":{},"query":"
query GetCurrentUser {
currentUser {
id
name
nickname
status
profile
area
facebookUrl
twitterUrl
instagramUrl
webUrl
signedUpAt
signedInAt
userType
mailVerified
telVerified
identityVerified
mail
tel
hashedId
customer {
id
ex {
kana
__typename
}
__typename
}
__typename
}
}
"}]'
(ログイン中ユーザーの情報を取得する部分のみ抽出)
なるほど、operationNameに取得する種別を選択した上で、カラムを指定するんですね。これでカラム名がわかりました。
他人の情報を取るエンドポイントがある
GetCurrentUser
はログイン中のユーザーの情報を取るためのものみたいです。ほかにも GetUser
など、ほかのユーザーのプロフィールを取るものもあるみたいです。(正式な呼び名はあるんでしょうけど)
ここで、ログアウトしてセッションを消してみて、 GetUser
で GetCurrentUser
で知ったカラムが、第三者(今回は自分のアカウント)に対しては取れないことを確認してみましょう。
…
名前、メールアドレス、電話番号、フリガナなどが取れました。フィルタしてなかったんですね。
インシデントレスポンス
- 6/18 19:31 気づく
- 6/18 19:56 窓口はすべて時間外なため、人事担当者様やCEOのTwitter DMに投げる
- 6/18 19:59 人事担当者様から返信をいただく
- 6/18 20:14 人事担当者様が技術にエスカレしてくれ、返信をいただく
- 6/21 指摘した脆弱性の修正が完了した旨の連絡をいただく
- 6/23 「【重要】個人情報漏えいの可能性についてのお詫びとお知らせ」というタイトルでプレスリリースおよびメール配信がされる
報告書
総評
脆弱性報奨金制度は現状なかったものの、特別に報奨金をいただきました。
また、実績として公開して良いとのことでしたので、記事として公開させていただくことにしました。
容易に想像できる場所に脆弱性があり、ユーザーの個人情報を危険にさらしたことは残念ですが、インシデントレスポンスと情報開示としては高い評価ができると思います。
なお、いただいた報奨金(脆弱性程度に十分見合った額でした)は、DE15鉄道部品などの購入のクレカ支払いにすぐに消えてしまったので、寿司はおごれません。
感想としては、最新のイケている技術を導入するにあたっては、リスク面も十分に考慮して、実は旧来の枯れた技術のほうが向いているのでは? とよく考えることが必要だと思いました。
おわり
Discussion
この国を救っていただいたんですね。ありがとう!
無造作にユーザモデルをレスポンスに渡していたという話だからRESTでも起きるというか、GraphQL固有の問題ではないような気はする。気付いたのはえらい