こんにちは。GMOインターネットグループ株式会社 セキュリティエンジニアの谷山です。私は2020年に新卒入社し、現在はセキュリティのチームにてWebアプリケーション脆弱性診断に従事しています。脆弱性診断を行うために様々な脆弱性の知識が必要なので日々勉強しているところです。さて、脆弱性には開発者の対策漏れで発生するものだけでなくそもそもプログラム言語に用意されている処理で発生してしまうものがあります。今回は、PHPなどの関数で用意されているシリアライズに潜む脆弱性についてPort Swiggerが公開しているWebSecurity Academyのページを使って一緒に挙動を見ていきましょう。
注意事項
検査により、意図せずにシステムの停止や不具合を引き起こしてしまう可能性があるため、紹介する手順を試してみたい場合は本番に影響を及ぼさない検証環境か、それ専用に作られたテストサイトでの作業をお勧めします。
自組織が管理しないWebアプリケーションへの検査は、場合によっては不正アクセスとして違法となる可能性があります。
シリアライズされたデータ
まず、シリアライズとはアプリケーション上の構造を持ったデータを保存・伝達する目的でバイト列に変換する事をいい、反対にシリアライズされたデータをもとに戻ることをデシリアライズと言います。
ん・・・・・?
なんだかよく分かりませんよね?私も言葉を聞いても良く分かりませんでした。今回のテーマは「手を動かして理解する」ですので実際にテストページを見ていきましょう。
こちらがテストページのログインリクエストとレスポンスになります。
特にレスポンスに注目するとSet-cookieされているsessionの値は下記になっていますね。
Tzo0OiJVc2VyIjoyOntzOjg6InVzZXJuYW1lIjtzOjY6IndpZW5lciI7czo1OiJhZG1pbiI7YjowO30%3d
一度ログアウトして再度ログインした際のSet-cookieされているsessionの値は上記に記載したものと全く同じでした。
つまり、乱数でsessionの値を生成しているのではなくユーザーごとにある規則性で生成されていそうですね。
先ほどのsessionの値を詳しくみていきましょう。
Tzo0OiJVc2VyIjoyOntzOjg6InVzZXJuYW1lIjtzOjY6IndpZW5lciI7czo1OiJhZG1pbiI7YjowO30%3d
値の最後に%3dとありますのでURLエンコードされていそうです。URLデコードしてみましょう。
Tzo0OiJVc2VyIjoyOntzOjg6InVzZXJuYW1lIjtzOjY6IndpZW5lciI7czo1OiJhZG1pbiI7YjowO30=
%3dはURLエンコードで=になりましたね。この=で、何か引っかかる方もいらっしゃいますかね?この=というのは、とあるエンコード方式の特徴になっています。そのエンコード方式というのは「BASE64」と呼ばれるものです。BASE64でエンコードした場合は最後に4文字区切りで足りない文字は=で補います。
本記事ではエンコード・デコードの解説はしませんが、気になる方は別途調べてみてください。
少々エンコードの話をしてしまいましたので話を戻しましょう。テストページのログインレスポンスのSessionの値をURLデコードしたら最後に=がありました。これはBASE64でエンコードされている気がするのでさらにBASE64でデコードしてみましょう。すると下記文字列になりました。
O:4:"User":2:{s:8:"username";s:6:"wiener";s:5:"admin";b:0;}
どうでしょうか?何となく読める気がしませんか?実はこれがシリアライズされたバイト列になります。シリアライズ形式はデータの先頭に型やサイズがありその後データの文字列が続いています。こちらを見ると下記のデータがありそうです。
username:wiener、admin:0
以上が実際のサイトで使用されているシリアライズされたバイト列の例になります。
権限昇格を試みる
先ほど、シリアライズされたバイト列を確認しましたね。もう一度確認していきましょう。
O:4:"User":2:{s:8:"username";s:6:"wiener";s:5:"admin"; b:0;}
特に最後のadminのところが気になりますよね。b:0とあるのでBoolean型の0だと考えられますね。という事はb:1に変更するとどうなるのかやっていきます。
テストページでログインすると下記のような画面になっています。
この時のリクエストとレスポンスは下記のとおりです。
リクエストにはやはり先ほどと同じ値のcookieを持っていますね。こちらに改ざんしたcookieを差し込んでいきます。
まずは先ほどBASE64デコードしたsessionの値のb:0部分をb:1に変更します。
O:4:"User":2:{s:8:"username";s:6:"wiener";s:5:"admin"; b:1;}
変更したらこの値をBASE64でエンコードします。
Tzo0OiJVc2VyIjoyOntzOjg6InVzZXJuYW1lIjtzOjY6IndpZW5lciI7czo1OiJhZG1pbiI7YjoxO30=
この値をさらにURLエンコードした文字列をMy-accountページを表示する際のcookieの値に指定すると
画面は下記になりました。
こちらをよく見てみるとAdmin panelが表示されていますね。AdminのBoolean型の値を1にしたことでAdmin権限として表示されたことになり実際に権限昇格が実行出来ました。
おわりに
いかがでしたでしょうか。このようにプログラム言語に関数として機能がありますが、使い方を考えないと思いもよらない攻撃をされてしまう可能性があります。基本的にはシリアライズ機能は使わずにJSON形式などでデータを受け渡しする方が良いとされています。昨今のサイトではほとんど使用していない気がしますが、昔に作成したサイトをそのまま運用しているというような場合はシリアライズ機能がないかチェックしてみると良いかと思います。