5/13にあったセキュリティミニキャンプでトラックAの @a_zara_n さんの講義「コンテキストを読み解き進めるモダンWebセキュリティ入門」を受講しました。そこで演習としてCTFが行われたので、生まれて初めての公開Writeupを書こうと思います。
学校の課題等に追われて書くのが遅れているため、問題のタイトル等一部忘れてしまっている部分もあるのですが出来る範囲で書いていきます。
challenge1
講義の中では現代のWebサービスには複数のマイクロサービスが疎結合して成り立っているものが多くあるということに触れられていました。この問題もそのようなサービスに対しての攻撃を行うものでした。
このWebアプリケーションではAmazon Cognitoを利用しており、Webアプリケーション上でアカウントを作成するとCognitoのユーザーが作成されます。このWebアプリケーションではCognitoのカスタム属性roleを定義して、その値によってユーザーの権限を管理していました。
https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/user-pool-settings-attributes.html
if (event.requestContext.authorizer?.role === "admin") {
return {
statusCode: 200,
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": `${process.env.ORIGIN_DOMAIN as string}`,
"Access-Control-Allow-Methods": "GET,OPTIONS",
"Access-Control-Allow-Headers": "Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token",
},
body: JSON.stringify({
message: process.env.FLAG
}),
};
}
return {
statusCode: 200,
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": `${process.env.ORIGIN_DOMAIN as string}`,
"Access-Control-Allow-Methods": "GET,OPTIONS",
"Access-Control-Allow-Headers": "Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token",
},
body: JSON.stringify({
message: "You are not \"admin\" role."
}),
};
};
しかしcognitoのカスタム属性は、認証情報のJWTさえあればaws-cliを用いて変更可能でした。認証情報はログインした時のレスポンスに含まれており、またaws-cli上でログイン情報を送ることによっても得ることが出来たので、以下のようなコマンドを打つことでrole属性を変更し、flagを閲覧することが出来ました。
aws cognito-idp update-user-attributes --access-token ACCESS_TOKEN --user-attributes Name="custom:role",Value="admin"
これはwebサービス側ではroleが自由に変更できない値だと想定していたのに、実際にはaws-cliを用いて変更可能な値だったことによって生じてしまった脆弱性だと言えます。
challenge2
challenge1とほぼ同じWebサービスですが、ユーザー登録のページがなくなっていました。ヒントには確か「見えないからといってないとは限らない」みたいなことが書いてあって、実際にユーザー作成のリクエストは受け付ける状態でした。ログイン画面でパスワードの再発行などを試すとcognitoのクライアントIDらしきものが得られました。これを使い、challenge1でユーザー作成した時に送ったリクエストをクライアントIDとリクエスト先URLをchallenge2のものに書き換えて再度リクエストを送ったところユーザーを作成することができました。
これによってログインしてFlagを閲覧できました。
challenge3
challenge2までとは異なり、今度はファイルをアップロードして保存できるWebアプリケーションでした。ユーザーの管理には今まで同様cognitoを用いており、ファイルの保存にはAmazonのS3を用いていました。
S3内にはユーザー固有のid/ファイル名
の形で保存しているようで、idが不明な自分以外のユーザーが保存したファイルの中にFlagがあるようでした。ファイルパスは丸々クエリパラメータで指定することが出来ました。
ここで重要だったのが、S3では「Amazon S3 はフラットな構造であり、ファイルシステムに見られる階層はありません。」ということです(ユーザーガイド)。ディレクトリはあくまでわかりやすいようにサポートされているだけであり、用いずともファイルにアクセスできしまうため、単にクエリパラメータでflag
と指定するだけで誰かのid/flag
を閲覧することが出来ました。
これはS3のファイルシステムがlinuxなどのファイルシステムと異なっていることを考慮していなかったために生じてしまった脆弱性だと言えます。
challenge4
challenge3での脆弱性の対策を試みていました。S3上でのファイルの保存方法は変わらないものの、S3内のファイルへアクセスする際のパスが以下のように変更されていました。
const key = path.normalize(`${id}/${decodetFileId}`);
このidはユーザー登録の際にcognitoのカスタム属性を作り、それを用いています。decodetFileIdはクエリパラメータによって指定できます。この変更から、強制的にパスにid/
を含めることで自分のidの外側のファイルを閲覧するを防ぐ意図が読み取れます。
ところでidはchallenge1で見たように自由に変更することが出来ます。また、path.normalize()
はバスの冗長部分を消してくれます。これによって'./flag'
を'flag'
にしてくれます。この関数は、普段は問題なく利用できてもコンテキストによって悪い影響を及ぼすことがある例として講義でも触れられていました。
以上のことから、challenge1と同様の方法でidを'.'
に変更し、クエリパラメータでflag
を指定することによってフラグを閲覧することが出来ました。
終わりに
今回初めてIPAのイベントに参加させていただいて、自分のレベルが足りずに難しく感じる部分が多かった一方で、その分勉強になったことがたくさんあり参加して本当に良かったです。セキュリティキャンプ全国大会も、Web分野でありませんが応募したので受かってたらいいなと思ってます(合否が明日に出ます)。
齋藤先生、楽しいハンズオンをありがとうございました!講義で触れた内容が思い出されてとても勉強になったと感じました!