技術者ブログ
クラウド型WAF「Scutum(スキュータム)」の開発者/エンジニアによるブログです。
金床“Kanatoko”をはじめとする株式会社ビットフォレストの技術チームが、“WAFを支える技術”をテーマに幅広く、不定期に更新中!

Tomcatの脆弱性、CVE-2020-9484の解説
はじめに
Tomcatに新たな脆弱性CVE-2020-9484が発見されました。既にアップデートは提供されています。影響を受けるバージョンなどの情報についてはオフィシャルのページを参照ください。今回はこの脆弱性の技術的な側面について解説します。
攻撃成功率は高くない
CVE-2020-9484は一応リモートからの任意のコード実行(RCE)であるとされていますが、現実的には攻撃を成功させるためのハードルは高く、実際に被害が発生する可能性はそれほどは高くありません。攻撃が成功するために必要となる条件は下記になります。
- 攻撃者がTomcatが稼働しているサーバ上のどこかに任意の名前・中身のファイルを配置できること
- Tomcatがセッションの永続化のためにPersistenceManagerをFileStoreで使っており、かつsessionAttributeValueClassNameFilterがnullになっているなど、特にセッションに入れることができるオブジェクトのクラスに制限を設けていないこと
- 攻撃者がFileStoreのセッション保存先ディレクトリがどこかを把握していること
攻撃者の視点からだと、まず上記のうち1を満たすのが難しいでしょう。これが可能になるのは、例えばウェブアプリケーションがファイルのアップロード機能を提供しているケースで、かつファイル名をユーザが指定できるような場合などが考えられます。
種別としては「安全でないデシリアライゼーション」
TomcatはJava製のアプリケーションです。CVE-2020-9484はRCEですが、脆弱性の種別としては、今やすっかりJavaのRCEとして定着してしまった「安全でないデシリアライゼーション」です。何のオブジェクトをデシリアライズするのか?についてですが、これはいわゆるウェブアプリケーションの「セッション」オブジェクトです。
CVE-2020-9484の発現条件のひとつにセッションの永続化をFileStoreを使って行うという点があります(前項の2)。このとき、各セッションはTomcat終了時などに、それぞれ個別のファイルに保存されます。例えばセッションID(パラメータ名はJSESSIONID)がABCDの場合は、ABCD.sessionというファイル名で保存されます。
HTTPリクエストの中のCookieか、あるいはリクエスト行の;jsessionid=...で指定されるパラメータによってセッションIDが指定されます。これを受けてTomcatは再びファイルからセッションを復元しようとしますが、この際にObjectInputStreamのreadObjectが実行され、攻撃者が置いておいたペイロードが起動され、RCEに繋がります。
Tomcat中のRCEが起こる実際の場所はStandardSession.javaの1559行目になります。単純にファイルの先頭のデータからreadObjectが実行されるだけなので、この部分の攻撃には難しさはありません。単にガジェットを使ってよくあるパターンのオブジェクトを置いておけば動いてしまいます。
このときのスタックトレースは下記になります。
org.apache.catalina.session.StandardSession.doReadObject(StandardSession.java:1559) org.apache.catalina.session.StandardSession.readObjectData(StandardSession.java:1058) org.apache.catalina.session.FileStore.load(FileStore.java:229) org.apache.catalina.session.PersistentManagerBase.loadSessionFromStore(PersistentManagerBase.java:769) org.apache.catalina.session.PersistentManagerBase.swapIn(PersistentManagerBase.java:719) org.apache.catalina.session.PersistentManagerBase.findSession(PersistentManagerBase.java:494) org.apache.catalina.connector.Request.doGetSession(Request.java:3017) org.apache.catalina.connector.Request.getSessionInternal(Request.java:2737) org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:513) org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81) org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:690) org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:615) org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:818) org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1627) org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) java.lang.Thread.run(Thread.java:745)
WAFで止まるかどうか?
FileStoreでは、HTTPリクエスト中のJSESSIONIDの値が直接ファイル名として解釈されてしまいます。そのため、例えばTomcatが本来セッションを保存するディレクトリがそれなりに深い位置にあり、かつ攻撃者が/tmp/にしかファイルを置けないような場合には、../../../tmp/のような文字列がセッションIDの値の一部に含まれることになります。
このような場合には、パストラバーサル(ディレクトリトラバーサル)を防止できるWAFであれば、攻撃を止めてくれる可能性が高いでしょう。Scutumは、このパターンです。
最新版でも攻撃が成立してしまうケース
前項のように../../を伴う場合はWAFで止めることができる可能性が高いです。しかし一方で、Tomcatがセッションを保存する本来のディレクトリそのものに攻撃者がファイルを置けてしまう状況だと、ドットもスラッシュも使わず、単に攻撃者がセッションIDとしてありえそうなファイル名を使って攻撃が出来てしまいます。このときはWAFで見つけることはできませんし、そもそもこのパターンだと最新版のパッチが当たっているTomcatでも防ぐことができません。 これは今回Tomcatが行った修正が「デシリアライズするファイルは../../と辿っていくような離れたディレクトリではなく、本来のディレクトリ以下に置かれていること」を確認する、というロジックになっているからです。
Tomcatのチームがなぜこの状態でよしとしたのかは良くわかりません。「そこまで心配な場合はsessionAttributeValueClassNameFilterを使って明示的にシリアライズ可能なクラスを指定してください」という事である可能性はありそうです。いずれにせよ、任意のファイルを置かれてしまうという前提条件を今回のTomcatの脆弱性のみでは満たす事が出来ないため、Tomcat単体としてそれほど「やらかした」という感じではないかもしれません。
まとめ
今回は新しく見つかったTomcatの脆弱性であるCVE-2020-9484について簡単に解説しました。readObjectあるところにRCEあり。そのくらいの勢いで開発者は気をつけないとダメかもしれません。