生産性のない話

趣味の範囲でサイバーセキュリティの話

Androidアプリ : ES ファイルエクスプローラーの脆弱性(CVE-2019-6447)を調べてみた

今回は個人的に興味を持ったAndroidアプリの脆弱性を調べてみました。

きっかけは以下のTweetです。

ES ファイルエクスプローラー という1億以上のダウンロード数のある人気アプリに脆弱性があり、端末内のファイルをネットワーク経由で盗み出すなどができるようです。

ES ファイルエクスプローラー - Google Play のアプリ

PoCが公開されており、影響範囲には現時点の最新バージョンも含まれて、アップデートはまだ提供されていないようです。

この ES ファイルエクスプローラー ですが、私自身以前利用しており、割と使い勝手はよかったと記憶しています。

ただ、一時期「中国に情報を送っている」みたいなよくある曖昧な噂が流れて、ちょっと心配になり、別のアプリに移行しました。

ということで今回、一度アンインストールしたそのアプリを再インストールして、どのような脆弱性なのかを確認してみました。

検証

インストールします

f:id:blueBLUE:20190117230341p:plain

今回は、私の古いタブレットで検証しました。 IPは「192.168.0.7」となっています。

ES ファイルエクスプローラー を起動すると、59777ポートでHTTPを待ち受けるようになります。

nmap を打ってみると、確かにポートが開いていることが確認できます。

f:id:blueBLUE:20190117230358p:plain

上がES ファイルエクスプローラーを起動する前で、下が起動した後です。

ブラウザでアクセスすると、以下のような表示がされました。

f:id:blueBLUE:20190117230410p:plain

ちなみに、一度ES ファイルエクスプローラーを起動すると、このポートはずっと待ち受けるようで、アクティビティを殺しても、端末を再起動してもバックグラウンドサービスとして動き続けているようでした。 インストールして一度起動した時点で、それ以降、いつでも攻撃を受ける可能性があるということになります。

ここに特殊な json のリクエストを投げると、様々な攻撃が行えるとのことです。 具体的には現在公開されているPoCで、以下のようなことができます。

  • ファイル一覧の取得
  • 指定したファイルの取得
  • インストール済みアプリの一覧の取得
  • インストール済みアプリのapkパッケージの取得

画像ファイル一覧を取得してみます。

f:id:blueBLUE:20190117230435p:plain

画像ファイルの一覧がずらっと表示されます。 その中のファイルを取得します。

f:id:blueBLUE:20190117230457p:plain

確かに端末内のファイルを取り出すことができました。

f:id:blueBLUE:20190117230513p:plain

f:id:blueBLUE:20190117230522p:plain

攻撃者は端末と同一のLAN内にさえいれば、ES ファイルエクスプローラーが動いている端末内のファイルを自由に取り出すことができるようです。

不特定多数の人が接続可能なWifi環境下では攻撃者により知らない間に端末内に保存した写真などが盗み出されている可能性もあります。

解析

アプリのapkファイルを取り出して簡単に中身を解析してみました。

再び脆弱性をついてapkファイルを取り出します(便利!)。

f:id:blueBLUE:20190117230540p:plain

apkファイルを unzip し、dexファイルをdex2jar で jarファイルに変換し、JD-GUIデコンパイルしたソースファイルを確認します。

同時に apktool でデコードした中身に対してそれらしい文字列でgrepをかけていくと、サーバとして待ち受けているっぽい箇所がわかります。

以下はコマンドを受け付けるコードです。

f:id:blueBLUE:20190117230602p:plain

コードを見ると、なんか認証しようとしてるっぽい if文が見えます。 ただ、今回のコマンド受付けとは関係なさそうです。

ちなみに、待ち受けているコマンドは以下の通り。

  • listFiles
  • listPics
  • listVideos
  • listApps
  • listAppsSystem
  • listAppsPhone
  • listAppsSdcard
  • listAppsAll
  • getAppThumbnail
  • appLaunch
  • appPull
  • getDeviceInfo

何ができるかは大体予想がつきますね。

脆弱性の根本的な原因はHTTPで待ち受け、認証もなしに情報を取得できてしまうことにあります。

そもそも何のためにHTTPの待ち受けを行っているのでしょうか?

アプリの機能の説明には「ローカル/インターネット管理」といった記載があるので、この辺りの機能に関連しそうですが、詳細についてはどこにも記載が見当たりません。

こういう機能はせめてユーザ側で有効/無効を切り替えられるようにすべきじゃないかと思っており、この辺りは設計上の不備ではないかと考えています。

対策

というほどのものはないけれど、少なくとも現時点で脆弱性に対応したアップデートが出ていないことを考えると、しばらくは利用を控える(アンインストールする)というのがよいかと思います。

開発チームはこの脆弱性を認識しており、近いうちに修正はされるそうです。

ちなみに私が今は X-plore File Manager というやつを使っています。宣伝でもなんでもないですし、安全かと言われれば、はっきり答えられる自信はありませんが、参考になれば……。

以上です。

追記(2018/01/19)

アップデートバージョン(バージョン 4.1.9.9)が配信され、脆弱性が修正されました。

f:id:blueBLUE:20190119010630p:plain

ポートは依然開いたままですが、PoCを打ってみると、500エラーが返ってきました。

f:id:blueBLUE:20190119010751p:plain

更新されたソースを見ると、ちゃんと追ってないですが、コマンドを受け付ける前に、認証か何かの処理を入れたようです。

f:id:blueBLUE:20190119011028p:plain

ひとまず、この脆弱性については、対処されました。 (ただ、別の脆弱性を指摘している方もいるので、そっちがどうなっているかはわかりません)

JBOSS Richfacesの脆弱性(CVE-2018-14667)を検証してみた

久しぶりの検証記事です。

今回は JBOSS RIchfaces というフレームワーク脆弱性ですが、このフレームワークは2016年6月でEOLを迎えており、すでにサポートは終了しています。

RichFaces End-Of-Life Questions & Answers |JBoss Developer

そのため、パッチなどのリリースはなく、万が一脆弱性のあるフレームワークを利用している場合、すぐに別のフレームワークへの移行が推奨されます。

CVE-2018-14667

UserResource を通じて、シリアライズ化されたJAVAのオブジェクトを利用してリモートから認証なしでコード実行を行うことが可能となります。

影響を受けるバージョン

  • RichFaces Framework 3.X through 3.3.4 (all versions)

検証

色々試したのですが、アプリケーションサーバなどの環境によって条件が異なるようで、公開されているPoCでうまくいくものといかないものがありました。

とりあえず以下のリンクの通りに試したものは成功しました。

Full Disclosure: Unauthenticated Remote Code execution in WebApps using Richfaces 3.X all versions (CVE-2018-14667)

リンクにある環境を以下のような Dockerfile にしました。

FROM java:8-jdk

RUN apt-get update && apt-get install -y wget unzip --no-install-recommends && \
  rm -rf /var/lib/apt/lists/*

COPY ./jboss-5.1.0.GA.zip /tmp/

RUN unzip /tmp/jboss-5.1.0.GA.zip -d /opt/ && \
  wget http://downloads.jboss.org/richfaces/releases/3.3.X/3.3.4.Final/richfaces-examples-3.3.4.Final.zip -P /tmp/ && \
  unzip /tmp/richfaces-examples-3.3.4.Final.zip -d /tmp/ && \
  mv /tmp/richfaces-examples-3.3.4.Final/photoalbum/dist/photoalbum-ear-3.3.4.Final.ear /opt/jboss-5.1.0.GA/server/default/deploy/ && \
  rm -rf /tmp/*

EXPOSE 8080

CMD ["/opt/jboss-5.1.0.GA/bin/run.sh", "-b", "0.0.0.0"]

アプリケーションサーバとして、JBoss AS 5.1.0.GA(jboss-5.1.0.GA.zip) を用意します。

それをDockerfileと同じディレクトリにおいてビルドします。

docker build -t richfaces-sample .

出来上がったイメージを起動します。

docker run -d -p 8080:8080 richfaces-sample

これで http://localhost:8080/ にアクセスすると、JBOSS AS の画面が表示されます。

今回の検証の対象となるアプリケーションは richfaces のサンプルアプリケーションの photoalbum です。

http://localhost:8080/photoalbum/ にアクセスします。

f:id:blueBLUE:20181202003244p:plain

開発者コンソールを開いた状態で、このサイトにアクセスすると、「/DATA/」から始まる長い文字列を含むURLがあります。

f:id:blueBLUE:20181202003341p:plain

この「/DATA/」以降の文字列が、シリアライズ化したJAVAのオブジェクトとなっており、ここに細工した Expression Language (EL) オブジェクトを投げると、それを解釈してコード実行が行われます。

ここで、「/DATA/」以降に適当な値を入れてアクセスすると、その値をJAVAオブジェクトとして正しく認識できずにエラーが返ってきます。

f:id:blueBLUE:20181202003450p:plain

では、PoCを投げてみます。

PoCは上記のリンクにありますが、同様のURLの「/DATA/」以降の文字列を細工したものになっています。

ここでの実行コマンドは「touch /tmp/richfaces0day_joaomatosf」です。

正しく処理が行われ、レスポンスとして200が返ってきます。

f:id:blueBLUE:20181202003554p:plain

ということで、コンテナの中に入って、コマンドが実行されていることを確認します。

f:id:blueBLUE:20181202003631p:plain

コマンドが実行され、「/tmp」配下にファイルが作成されました。

このURLに含まれる長い文字列ですが、これはシリアライズ化したオブジェクトをzlibで圧縮し、BASE64エンコードしたものとなっています。

そのため、以下のようにして逆処理をしてやると、シリアライズ化されたオブジェクトが現れ、実行コマンドが確認できるようになります(念のため、ペイロードを一部マスクしています)。

f:id:blueBLUE:20181202004101p:plain

後、この辺りあまりちゃんと理解できていないのですが、インジェクションするオブジェクトである javax.el.MethodExpression の「serialVersionUID」というパラメータが共通のものがないため、PoC生成の段階でアプリケーション毎に別の値を指定してやる必要があるようです。

そのため、攻撃先の環境によって、別々の値を指定する必要があり、共通のPoCが利用できないようです。

以下の検証動画ではアプリケーションサーバとして、Tomcat 8 を利用している場合と、JBOSS EAP を利用している場合の検証が行われいます。

www.youtube.com

まとめ

シリアライズ関係の脆弱性は理解しにくいところがあって、なかなか難しいです。 JAVAオブジェクトをシリアライズ化してそのままやり取りするようなアプリケーションは危険だということは理解していますが、今回のようなURLに一見してわからない形で入ってくることもあるんですね。この場合、通常通信と攻撃通信が見ただけで判断できないのがちょっと辛いです。

すでにEOLを迎えているフレームワークであり、利用しているところはないだろうと思ってはいますが……。

参考

Full Disclosure: Unauthenticated Remote Code execution in WebApps using Richfaces 3.X all versions (CVE-2018-14667)

Red Hat JBoss EAP RichFaces - unserialize + el = RCE - 【CVE-2018-14667】 - 先知社区

JBoss RichFaces EL Injection RCE Analysis(CVE-2018-14667)

2018年7月の定期パッチで修正されたOracle WebLogic Server の脆弱性(CVE-2018-2894)について

7月のOracleの定期のパッチリリースでいくつか脆弱性の修正が入りました。

CPU July 2018

危険度の高い脆弱性として、以下の2つがあり、いずれも攻撃コードが公開されています。

  • CVE-2018-2893

WLS Core Components の脆弱性でT3プロトコルを利用してネットワーク経由でコード実行ができます。 T3プロトコルはこれまでいくつかリモートからのコード実行の脆弱性が見つかっており、運用しているシステムでは無効化しておくのが無難かと思います。

  • CVE-2018-2894

WLS - Web Servicesの脆弱性。2017年10月に公開された脆弱性(CVE-2017-10271)と同じコンポーネントでHTTP経由でコード実行が可能な脆弱性。 こちらはHTTPなので、対象のモジュールが動いていて、そのパスが存在していたら危ないです。

影響を受けるバージョン

サポートされている Oracle WebLogic Server で影響を受けるのは以下のバージョン

  • 10.3.6.0
  • 12.1.3.0
  • 12.2.1.2
  • 12.2.1.3

脆弱性検証

PoCが公開されているCVE-2018-2894の脆弱性の検証を行いました。

環境は前回作ったCVE-2017-10271のDockerのイメージを使おうとしましたが、前回のDockerイメージはProductionモードでビルドしており、公開されているPoCは開発モードでしか攻撃が成功しないため、使えませんでした。

Oracle WebLogic Server のWLS Security に関する脆弱性(CVE-2017-10271)について - 生産性のない話

ということで、新しいDockerイメージをビルドします。今回はバージョン 12.2.1.2を作ります。

ちなみに、前回作ったのはバージョン 12.1.3 なのですが、このバージョンのDockerfileはデフォルトでProductionモードになっており、明示的にビルドの際に引数「PRODUCTION_MODE=dev」を付与する必要があります(バージョン 12.1.3 以外はデフォルトで開発モードになっているのに、なぜそうなっているのかは不明……)。

Oracleの公式のDockerfileをクローンします。 OracleJavaのイメージをビルドした後で、以下のコマンドを実行します。

cd OracleWebLogic/dockerfiles/12.2.1.2/
# Oracleのサイトからファイルをダウンロードしてくる
mv ~/Downloads/fmw_12.2.1.2.0_wls_Disk1_1of1.zip ./
mv ~/Downloads/fmw_12.2.1.2.0_wls_quick_Disk1_1of1.zip ./
cd ../
./buildDockerImage.sh -d -v 12.1.3
cd ../samples/12212-domain
docker build -t 12212-domain --build-arg ADMIN_PASSWORD=admin1234 .

出来上がったイメージを起動します。

docker run -d --name wlsadmin --hostname wlsadmin -p 7001:7001 12212-domain

これで http://localhost:7001/console へアクセスします。

f:id:blueBLUE:20180722075036p:plain

今回のPoCは Web Service Test Client への攻撃のため、まずはこれを有効化します。 コンソールにアクセスし、「base_domain」から、「General -> Advanced」のメニューから「Enable Web Service Test Page」にチェックを入れます。

f:id:blueBLUE:20180722075335p:plain

コンテナを再起動して設定を有効化します。 ちなみに、この設定を行わずに攻撃対象のパスにアクセスすると404が返ってきます。

docker restart wlsadmin

http://localhost:7001/ws_utc/config.do へアクセスします。

f:id:blueBLUE:20180722075403p:plain

ここは Web Service Test Client の設定ページですが、このKeystoreをアップロードするところでjspバックドアをアップロードできます。 まず、アップロード先のディレクトリを指定する必要があります。 画像にある「Work Home Dir」の値を「/u01/oracle/user_projects/domains/base_domain/servers/AdminServer/tmp/_WL_internal/com.oracle.webservices.wls.ws-testclient-app-wls/4mcj4y/war/css」に変更し、Submitを押します。

f:id:blueBLUE:20180722075430p:plain

左のメニューから「Security」を選択し、「Add」を押してKeystoreを追加します。

名前に適当な値を入れ、JSPバックドアをアップロードします。

f:id:blueBLUE:20180722075504p:plain

アップロードしたPOSTのレスポンスにタイムスタンプ(id)の値が返ってくるので、この値を覚えておきます。

アップロードしたファイルにアクセスします。

ここでは「/ws_utc/css/config/keystore/<タイムスタンプ>_<アップロードしたファイル名>」というパスにファイルが存在します。

jspファイルがバックドアとして動作します。これで任意のコード実行が可能です。

f:id:blueBLUE:20180722075523p:plain

今回のPoCは前述の通り、開発モードでしか動作しません。

試しに Production Mode にチェックを入れて、再起動してみます。

f:id:blueBLUE:20180722075740p:plain

この状態だと、「/ws_utc/config.do」へアクセスしても503が返ってくるため、ファイルのアップロードができません。

f:id:blueBLUE:20180722075752p:plain

別のPoC

同じ脆弱性を悪用するPoCでコンフィグファイルのインポートを行うところでバックドアをアップロードするものがありました。 PoC自体は検証できていないのですが、アップロードが可能なことは確認できました。

最初に「http://localhost:7001/ws_utc/」へアクセスします。

f:id:blueBLUE:20180722234749p:plain

このフォルダのアイコンをクリックすると設定のインポート画面が出てきます。

f:id:blueBLUE:20180722234840p:plain

先ほどのバックドアファイルをインポートします。

f:id:blueBLUE:20180722234902p:plain

設定ファイルではないため、怒られてしまいます。

ですが、ファイルのアップロード自体は完了しており、Dockerコンテナ内に入って探すと確かにアップロードしたファイルを見つけることができます。 以下はKeystoreのアップロード時と同じパスを設定している場合です。

f:id:blueBLUE:20180722235220p:plain

これで、「/ws_utc/css/upload/RS_Upload_2018-07-22_14-30-17_948/import_file_name_backdoor.jsp」のURLパスにアクセスすると、バックドアが動作します。

f:id:blueBLUE:20180722235239p:plain

こちらも条件は同じで、開発モードで Web Service Test Client が有効になっている必要があります。

まとめ

攻撃は任意のファイルがアップロードできるため、影響は大きいですが、現状のPoCでは開発モードで特定のサービスを有効にしている場合のみ影響を受けるようです。商用のサービスでは、まあ、そんな環境があるとも思えないので、あまり影響はないかと考えています。 ただ、今後違う形の攻撃コードが出てくる可能性もありますので、アップデートはしておくべきかと思います。

XdebugとNetBeansを使ったDrupalのデバッグ環境をdocker-composeのみで立てる

昨年の末ごろにデバッガを使って脆弱性検証を行う勉強会に参加させていただきました。

※好評につき追加開催※ デバッガでWordPress本体やプラグインの脆弱性を追いかけてみよう - connpass

とても参考になりました。その時の内容は下記の記事がとても詳しくまとめてくれています。

tigerszk.hatenablog.com

自分が脆弱性検証をやるときにデバッガを使うことはやったことがなかったのですが、そういう方法があるというのは新しかったです。

脆弱性検証の場合、別に開発するわけではないのであくまでプロジェクトは一時的なもので、また複数のバージョンを立てて壊してとしていくので、そういう点でDockerとの相性がよく、自分もかなりの頻度で使っています。 Dockerを使ってこういう環境を立てておくと、後々応用が利きそうだなと思いました。

ということで今回はXdebugNetBeansを使ったデバッグ環境を立てます。

対象としては先日 Drupalgeddon2(CVE-2018-7600) の脆弱性が見つかったDrupalデバッグ環境を立ててみます。

NetBeansのDockerコンテナとしては以下を参考にしました。

postd.cc

Dockerを使ってNetBeans IDEGUIで起動できます。

  • Dockerfile
FROM debian:latest

RUN apt-get update && apt-get install -y wget openjdk-8-jdk && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/* && \
    rm -rf /tmp/*

ADD state.xml /tmp/state.xml

RUN wget http://download.netbeans.org/netbeans/8.2/final/bundles/netbeans-8.2-php-linux-x64.sh -O /tmp/netbeans.sh -q && \
    chmod +x /tmp/netbeans.sh && \
    echo 'Installing netbeans' && \
    /tmp/netbeans.sh --silent --state /tmp/state.xml && \
    rm -rf /tmp/*

CMD /usr/local/netbeans-8.2/bin/netbeans

NetBeansPHPIDEをインストールしています。state.xmlというのは一回インストールしたときに「--record」というオプションを付けて出力したファイルで、これを指定して 「--silent」オプションを付けることでGUIなしでインストールできます。

実行時にはGUIで起動するためにホスト側で「xhost +」とか実行してX Window Systemの認証を通しておく必要があります (終わったら「xhost -」とかで認証拒否の状態に戻すことを推奨します)。

次にDrupalのDockerコンテナです。公式のものにXdebugをインストールしています。

FROM drupal:7.57-apache

RUN pecl install xdebug
・・・

Xdebugの設定は以下の通りです。

[xdebug]
zend_extension=/usr/local/lib/php/extensions/no-debug-non-zts-20151012/xdebug.so
xdebug.remote_enable=1
xdebug.remote_handler=dbgp
xdebug.remote_connect_back=0
xdebug.remote_autostart=1
xdebug.remote_host=172.42.0.4
xdebug.remote_port=9000
xdebug.max_nesting_level=1000
xdebug.idekey="netbeans-xdebug"

設定についてはいろいろと必要なオプションがあります。 また、デバッガの接続元のIPを指定する必要があるので、NetBeansのIPを指定する必要があります。 そのため、docker-composeでネットワークを作成し、IPを固定しています。

また、デバッグ対象となるアプリケーションのファイルを共有しておく必要があるので、ボリュームを作成しています。 このボリュームを触るときに権限の設定が面倒なので、NetBeansはrootで動かしています。

以上から出来上がったDockerの設定ファイルは以下になります。

github.com

これをビルドします。結構時間かかります。

docker-compose build
docker-compose up

うまく起動すると、NetBeans IDEが起動します。

f:id:blueBLUE:20180523231849p:plain

あと、ローカルホストに行くとDrupalインストーラが起動します。 そのままだとベースになるコンテナのイメージは drupal:7.57-apache になっています。

f:id:blueBLUE:20180523231911p:plain

データベースはMySQLのコンテナが同時に立ちます。 パラメータは以下になっているので、入力してインストールします。

  • データベース名:drupal
  • データベースのユーザ名:drupal
  • データベースのパスワード:password
  • データベースのホスト:drupal-db
  • データベースホストのポート:3306

インストールが終わったら、NetBeansの設定です。 まずは新規プロジェクトを作ります。 PHPの既存のソースを選択します。

f:id:blueBLUE:20180523231932p:plain

ソースのフォルダにはDrupalのコンテンツがマウントしてある「/root/project/drupal」を指定します。

f:id:blueBLUE:20180523232014p:plain

ローカルのWebサイトとして、プロジェクトURLは対象となるDrupalのホスト(ここでは「http://drupal-vuln/」)とします。

f:id:blueBLUE:20180523232024p:plain

設定が終わったらデバッグを開始します。 メニューバーの一番右にあるデバッグ開始のボタンを押します。

リクエストを待ち受けているので、この状態で、攻撃コードを打てば、デバッグが開始されます。 その前にブレークポイントを設定しましょう。

三井物産セキュアディレクション (MBSD) の検証レポートによると、「includes/common.inc」の「#post_render」を処理する箇所でコード実行が行われるようなので、ここにブレークポイントを設定します。 Drupal 7系への攻撃ではリクエストは2回あり、1回目でキャッシュに実行するコマンドを仕込み、2回目で実行します。

f:id:blueBLUE:20180523232059p:plain

これで実行すると、1回目のリクエストでは何度もこの関数を通るのですが、そのうちの1回でパラメータで指定した値が変数に入っている処理があります。2回目のリクエストで1回目のリクエストでキャッシュされた値が取り出され、コード実行が行われることが確認できます。

f:id:blueBLUE:20180523232139p:plain

  • 2回目のリクエストで呼び出されている変数

f:id:blueBLUE:20180523232149p:plain

ここまでやってあれですが、結構処理が分かりにくい上に、リクエストが2つに分かれているので、初心者の勉強にはあまりいい例ではないのかな……と。

ただ、任意のコードが実行される個所は分かりました。

せっかくなのでDrupal 8系でもやってみました。

DrupalのDockerfileとdocker-compose.ymlのバージョン(7.57-apache ⇒ 8.5.0-apache)を書き換えてビルドするだけ…… と思ったらXdebugのインストールパスがかわっていました、、、

DrupalのDockerfileのphp.iniの値も書き換える必要があります。 (何かオプションとかで固定できそう?)

また、Dockerで共有したボリュームが残っているので、このままやると、Drupal 8のコンテンツがDrupal 7のコンテンツで上書きされます。 ので、先に作ったボリュームは削除しておきましょう。

f:id:blueBLUE:20180523232211p:plain

同じように「docker-compose up」のコマンドだけで、NetBeansが起動し、Drupal 8が立ちました。

f:id:blueBLUE:20180523232222p:plain

後の流れは同じです。ブレークポイントを張って、デバッグを開始して、攻撃コードを打ちます。

f:id:blueBLUE:20180523232306p:plain

ということで、簡単にXdebugNetBeansを使った脆弱性検証の環境が立ちました。

Drupal脆弱性の攻撃コードが出る前に用意しとけよ、という話ですね。 まったくもって、その通りです。

これ自体はもう少し応用が利きそうなので、PHP系のCMSJAVAミドルウェアを使ったアプリケーションは同じことができそうだなーと思っています。 そのへんは今後、頑張っていきたいです。

参考

デバッガを利用してWebアプリの脆弱性を分析してみた - とある診断員の備忘録

Dockerを用いたGUIアプリケーションの実行 | POSTD

GitHub - fgrehm/docker-netbeans: NetBeans in a container

NetBeans NetBeans 日本語サイト

NBIFAQ - NetBeans Wiki

Docker for Windowsでxdebugを使う

Drupalgeddon2に関する検証レポート(CVE-2018-7600)

Docker上でGoogle Chromeを使ったWebクローラを作る

最近Webサイトのクローリング(スクレイピング?)に興味を持ちまして、Webサイトのクローラ的なものを作りたいと思い、いろいろ試行錯誤していました。

Webサイトのコンテンツを取得するなら、一番簡単なものだとwgetcurl、ちょっと手を加えるならスクリプトを書く、Pythonならrequestsやmechanizeなどのライブラリを使うと、比較的単純なことなら簡単にできます。 より高度なことをやろうとすると、やはり実際のブラウザを利用するのが一番でしょう。

昨年末にこちらの記事を読みまして、

qiita.com

Docker上でGoogle ChromeをHeadlessモードで動かし、Seleniumで制御してスクレイピングしようという記事です。

とても面白そうだったのですが、自分の欲しい機能にあと一歩というところでした。

これにmitmproxyを組み合わせれば、幸せになれそうだったので、試してみました。

mitmproxy - an interactive HTTPS proxy

mitmproxyというのは、その名の通り、MITM(Man In The Middle)を行うプロキシです。 暗号化された通信に割り込んで、盗聴、改ざんを行う攻撃手法ですが、このテクニック自体は攻撃に限らず色々な場面で利用されています。

mitmproxyを利用すると、HTTPS(HTTP)の通信の中身を覗き見たり、書き換えたりが可能になります。

コンテンツを取得するだけであればそんなことをする必要はないのですが、これができると色々と便利なので、何とか組み込みたいと思いました。

で、さっそく、上の記事にある内容をトレースしつつ、mitmproxyを挟んでみたのですが、結論を言うとダメでした。 できました。

mitmproxyを利用するとき、「ブラウザ⇔mitmproxy⇔Webサイト」という形で通信が流れますが、ブラウザとmitmproxyの間はmitmproxyが発行する自己証明書が利用されます。こちらはブラウザから見ると不正な証明書になります。そこで、Chromeに「--ignore-certificate-errors」というオプションを付けてやることで、不正な証明書のエラーを無視して通常通りアクセスできるようになるはずです。

ですが、なぜかChromeをHeadlessモードで動かすと、このあたりのオプションが正常に動かないらしく、Chrome Headlessでmitmproxyを動かすのは現状難しそうという結論に至りました。

もう少ししたら、Chrome Headlessでも正常に動くようになるかもしれないので、そこは気長に待つことにします(あるいは僕の知らない別のオプションがあるのかもしれませんが、どなたか知っていたら教えていただきたい……)。

追記(2018/5/15)

個人のプロファイルにルート証明書をちゃんと読み込ませてやると、証明書のエラーがでなくなり、うまくいきました。 一番下に追記しています。

ということで、今回はChrome Headlessを使うことは諦めて、普通のGoogle ChromeをDockerから動かすことを考えました。

unskilled.site

こういう方法を使ってDocker内のGUIアプリケーションを起動させることができるようです。Linuxでリモートから(ここではローカルで動くコンテナ)のX Window Systemを許可してUNIXソケット経由でディスプレイを共有する方法です。

この方法を使ってGoogle ChromeをインストールしたコンテナでChromeを起動させると見事にChromeが立ち上がり、普通のGUIアプリケーションとして利用可能となりました。

f:id:blueBLUE:20180509211449p:plain

つまり、わざわざHeadlessモードを使わなくとも、GUIアプリケーションとしてDocker内で起動させることもできるということです。

ということで、Chrome Headlessは無視してとりあえず、GUIアプリケーションとして動作するGoogle Chromeのコンテナイメージを作ってみました。

インストールしたもの:

一応日本語環境で利用することを想定して日本語関係のフォントやパッケージも入れました。

本当はイメージを分けて作った方が後々やりやすいのかもしれませんが、mitmproxyの起動時に作成される自己証明書をChromeが入っているコンテナにインストールする必要があり、いろいろと面倒だったので、ひとまず全部一つのコンテナイメージにぶち込みました(イメージサイズが1.5Gくらいになってます……)。

出来上がったDockerfileがこちらになります。

github.com

起動時に色々やることがあるので、docker-entrypoint.shが色々面倒なことになっています。

docker-composeでそのまま起動すると、普通にGoogle Chromeが起動するはずです。mitmproxyのログはそのまま標準出力に流れていきます。 (追記 : 今はHeadlessでGoogleにアクセスするようになっています。GUIで利用する場合はdocker-compose.ymlと、実行スクリプトを書き換えるなどする必要があります)

ただし、X Window Systemの認証を許可するために、ホスト側で以下のコマンドを実行しておく必要があります。

xhost local:

認証を許可してそのままにしておくのはセキュリティ的によろしくないので、終わったら戻しておきましょう。

xhost -local:

Chromeを自動で動かしたい場合はscriptsフォルダ内に「crawling-script.sh」を作成すると、そこに記載されているコマンドを実行します。 例として、特定のサイトにアクセスするスクリプトを実行します。

  • scripts/crawling-script.sh
#!/bin/sh

python3 /home/chrome/scripts/crawling.py
  • scripts/crawling.py
from selenium import webdriver
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.keys import Keys
from datetime import datetime
from time import sleep

if __name__ == '__main__':

    url = 'https://www.google.co.jp/'

    outputdir = '/home/chrome/output/'
    timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")

    options = webdriver.ChromeOptions()
    options.binary_location = '/usr/bin/google-chrome'
    options.add_argument('--no-sandbox')
#    options.add_argument('--headless')
    options.add_argument('--disable-gpu')

    options.add_argument("--start-maximized")

    options.add_argument('--ignore-certificate-errors')
    options.add_argument('--ssl-protocol=any')
    options.add_argument('--allow-running-insecure-content')
    options.add_argument('--disable-web-security')

    PROXY = 'localhost:8080'
    options.add_argument('--proxy-server=http://%s' % PROXY)

    LANGUAGE = 'ja'
    options.add_argument('--lang=%s' % LANGUAGE)

    capabilities = options.to_capabilities()

    driver = webdriver.Remote(command_executor='http://localhost:9515', desired_capabilities=capabilities)

    try:
        driver.get(url)
        sleep(3)
        print('title:', driver.title)
        driver.save_screenshot(outputdir + 'screenshot-' + timestamp + '.png')
    finally:
        driver.quit()

これが置いてある状態で「docker-compose up」すると、サイトにアクセスしてスクリーンショットを撮って終了します。

f:id:blueBLUE:20180509211734p:plain

「--headless」のオプションを付けると、Headlessモードで動きますが、先に述べた通りの理由でHTTPSのサイトではmitmproxyのエラーのために正常にアクセスできません。 アクセス先がHTTPのみのサイトであれば、問題はないですが。

次にscriptsフォルダに「mitmproxy-script.py」を作成します。例として、アクセス先のレスポンスからhtmlファイルをすべて保存するスクリプトです。 (こちらのサイトを参考にしました)

  • scripts/mitmproxy-script.py
#!/usr/bin/env python

from mitmproxy import io
from mitmproxy.exceptions import FlowReadException

outputpath = '/home/chrome/output/'

def response(flow):
    content_type = flow.response.headers.get('Content-Type', '')
    path = flow.request.url.replace('/', '_').replace(':', '_')
    if content_type.startswith('text/html'):
        with open(outputpath + path, 'w') as f:
            f.write(flow.response.text)

これで起動すると、outputフォルダに保存されたダウンロードしたhtmlファイルが保存されているのを確認できます。

f:id:blueBLUE:20180509212120p:plain

mitmproxyのサンプルはこちらにあります。

こんな感じでmitmproxyとseleniumスクリプトを書いて、うまくやれば好きなWebサイトに好きなようにアクセスした際の通信を解析できるようになる、と思います。 スクリプトについては今後作っていこうかと思います。 まあ、seleniumもmitmproxyも割とサンプルはあるので書きやすいのではないかなー、と。

とにかくWebサイトの解析用のクローラを作ることはできたので、満足はしました。ただ、Headlessで動かせなかったのは残念でした。 なにかしら方法はありそうなので、もうしばらく調べてみたいです。 あと、FirefoxにもHeadlessモードが搭載されているそうなので、いずれはそっちも試してみたいです。

最後に、Webサイトのクローリング、スクレイピングはマナーを守って行うようお願いします。

追記(2018/5/15)

headlessモードでうまく動作したので、追記です。 ルート証明書をちゃんとインストールすると、エラーがなくなりました。 docker-entrypoint.shの中でコンテナの起動時にやるようにしています(ので、コンテナ起動に若干余計な時間がかかります)。

まず、OSに証明書を認識させます(ここまでは前回やっていた)。

openssl x509 -in /home/chrome/.mitmproxy/mitmproxy-ca-cert.pem -inform PEM -out /home/chrome/.mitmproxy/mitmproxy-ca-cert.crt
mkdir /usr/share/ca-certificates/extra
cp /home/chrome/.mitmproxy/mitmproxy-ca-cert.crt /usr/share/ca-certificates/extra/
echo 'extra/mitmproxy-ca-cert.crt' >> /etc/ca-certificates.conf 
update-ca-certificates

chromeを一度起動させると、デフォルトのプロファイルが作成されます。 すると、ホームディレクトリの「.pkiディレクトリ配下に証明書を関連のデータベースができるので、ここにcertutilコマンドを使ってmitmproxyの証明書をインストールします。

for certDB in $(find /home/chrome/ -name "cert9.db")
do
  prefdir=$(dirname ${certDB});
  echo ${prefdir};
  certutil -A -n ${certname} -t "TCu,Cu,Tu" -i ${certfile} -d sql:${prefdir}
done

これで、headlessモードでchromeが動いてくれるようになりました。

ちなみに、Firefox版も作りました。こちらも同じように動いてくれるかと思います。 (Firefox版を作っていて証明書に気づいた)

これで幸せになれそうです。

参考

Dockerで手軽にスクレイピング環境を手に入れる - Qiita

Dockerコンテナの中でGUIアプリケーションを起動させる | Unskilled?

mitmproxyを使ってどんなサイトでもクローリング・スクレイピングする - Qiita

Making Chrome Headless Undetectable

AmazonLinuxでselenium + chromedriver + headlessするメモ - 私事ですが……

Spring Framework のリモートコード実行の脆弱性(CVE-2018-1270)について

久しぶりにブログ書きます。 最近あまりネタがなかったのでちょっとさぼり気味でした。

今月に公開されたSpring Framework脆弱性(CVE-2018-1270)についてです。

CVE-2018-1270: Remote Code Execution with spring-messaging | Security | Pivotal

Spring Framework の脆弱性に関する注意喚起

Apache Struts同様JAVAのアプリケーションフレームワークです。

概要

STOMP (Streaming Text Oriented Messaging Protocol) というメッセージプロトコルをWebSocketで利用している場合に、攻撃者から細工されたメッセージを受け取ることで、任意のコード実行が行われる脆弱性

同様に細工されたURLにアクセスすることでディレクトリトラバーサルが行われる脆弱性も報告されています(CVE-2018-1271)。

影響を受けるバージョン

また、すでにサポートが終了している過去のバージョンにおいても本脆弱性の影響を受けるとのことです。

修正バージョン

Spring Bootを利用している場合は 2.0.1.RELEASE で修正されています。

検証

PoCがすでに公開されており、検証用のアプリケーションもあったので、検証してみました。 ベースに「openjdk:8-jdk-alpine」のDockerコンテナを用い、Spring Boot 2.0.0.RELEASE で検証用のアプリケーションを動かしました。

8080ポートを開けてコンテナを動かします。

docker run -p 8080:8080 -it openjdk:8-jdk-alpine /bin/sh

このコンテナの中に、Spring Bootの脆弱性のあるバージョンを持ってきます(このへんから)。

解凍したSpring Bootのソースの中に検証用のアプリケーションのソースとpom.xmlを入れて、Mavenでビルドし、アプリケーションを動かします。

./mvnw spring-boot:run

f:id:blueBLUE:20180413134300p:plain

Spring Bootが色々と必要なものを落としてきて、アプリケーションを動かしてくれます。 終わったら、localhostの8080番にアクセスします。

f:id:blueBLUE:20180413134315p:plain

検証用のサンプルなので、すでにPoCが入力されており、あとはSendするだけという親切設計になっています。

で、Sendします。

アプリケーションを止めて、/tmp 配下を確認すると、確かに実行した通り、/etc/passwd の中身がコピーされていました。

f:id:blueBLUE:20180413134333p:plain

ついでに修正されたバージョンでもやってみました。 詳しくは上と同じなので書きませんが、Spring Boot 2.0.1.RELEASE を用意し、pom.xml の中身のバージョンを書き換えます。

<spring.boot.version>2.0.1.RELEASE</spring.boot.version>

これやらないと、せっかく修正バージョンを用意しても Spring Boot が気を利かせて、脆弱なバージョンを持ってきてビルドするみたいですね。

で、同じようにやってみましたが、コード実行は失敗、それ以外には変化はありませんでした。

攻撃時のパケット

パケットキャプチャをとってみました。

f:id:blueBLUE:20180413134348p:plain

GETリクエスト ⇒ 101(Switching Protocols) ⇒ WebSocketでやり取り(STOMP)

という感じですかね。 攻撃コードがバイナリになってどこが何をやっているのかがちょっとわからないです……。

f:id:blueBLUE:20180413134400p:plain

どうやらWebSocketがデータ圧縮を行っているため、攻撃コードが可読でなくなってるみたいです。 「Sec-WebSocket-Extensions: permessage-deflate」というヘッダのせいみたいですね。

試しにBurpを間に挟んで攻撃コードを実行してみました。

f:id:blueBLUE:20180413150801p:plain

例のヘッダがなくなり、一部平文で見えるようになりました。ただ、それでも攻撃コードはマスクされています。 (BurpってWebSocketも対応してるんですね)

ただ、WireSharkで見ると、マスクを解除してくれるので、攻撃コードがちゃんと見えています。

f:id:blueBLUE:20180413151231p:plain

普通のHTTP通信ならレスポンスが圧縮されることはあってもリクエストは平文だったのですが、WebSocketはリクエストが圧縮されたりマスクされたりするので、攻撃のポイントが見えにくくなり、検知がしにくくなります。 WebSocket経由の攻撃はこれまで見たことなかったのですが、割と面倒くさいなというのが今回の印象です。

追記(2018/04/17)

WebSocketのマスクついて調べてみました。

RFC 6455 - The WebSocket Protocol

RFC 6455 - The WebSocket Protocol (日本語訳)

ポイントとしては

  • クライアントからサーバへ送信される すべてのフレームはマスクされる
  • マスクに利用されるキーは32bitのランダムな値
  • ペイロードは「マスク用キー + マスクされたペイロード」という形で流れる
  • 元のペイロード = マスク用キー XOR マスクされたペイロード

ということらしいです。

攻撃者が任意のトラフィックを通信路に流せると、キャッシュ汚染の恐れがあるといった理由でこうなっているみたいです。

これをIDSなどで検知しようとすると、攻撃コードがWebSocketのマスクされた値の中にあるので、それを解除したうえで、検知を行う必要があり、かなり難しいと思われます。 WebSocket経由での攻撃というのは初めて聞いたのですが、このようなWebSocketのリクエストに対応した検知システムとかって、何かあるんでしょうかね……。

まとめ

Spring FrameworkApache Struts と同様JAVAのWebアプリケーションフレームワークなので、Struts脆弱性に懲りてSpring Framework に乗り換えようという人もいるかもしれませんが、こまめにアップデートする必要はいずれにせよありそうですね。 今回Spring Frameworkは初めて触ったのですが、割と開発の使い勝手は良さそうな印象でした。

今回の攻撃は影響は大きそうですが、まだ攻撃が行われたという話は聞かないです。 ただ、先に述べたように一般的なWebの攻撃に比べて検知が難しいので、気づかないうちに攻撃が行われている、なんてこともあるかもしれません。自分の管理するサーバに異変がないか、確認してみるのがいいかと思います。

参考

Spring Frameworkに含まれるリモートコード実行に関する脆弱性(CVE-2018-1270)についての検証レポート | NTTデータ先端技術株式会社

WOWHoneyhotを植えてみる

久しぶりにハニーポット関連。 morihi-soc 氏が作成した WOWHoneypot というWebハニーポットを使ってみました。

github.com

詳しい説明は以下のスライドで。

sssslide.com

Python3だけで動く極々単純な仕組みのハニーポットです。ざっくり言うと、待ち受けポートでHTTPが動いて、デフォルトではとりあえず200を返すWebサーバです。 正直初めてWebハニーポットを建てるのなら、これくらいシンプルなものでいいと思います。どうせ高機能なの入れてもデフォルトでしか動かさないだろうし……。

いつものようにDockerで動かします。 必要なのはPython3のみなので、別にそのままホスト上でやってもいいんですが、最近なんかツールを入れるときはとりあえずDockerに使うようになってきました。

こんな感じのDockerfileを作ります。

FROM ubuntu:latest

RUN apt-get update && apt-get -y upgrade
RUN apt-get -y install git python3

WORKDIR /root
RUN git clone https://github.com/morihisa/WOWHoneypot.git wowhoneypot
WORKDIR /root/wowhoneypot
CMD ["python3", "wowhoneypot.py"]

で、ビルドします。

docker build -t wowhoneypot .

で、立ち上げます。

docker run -it -v /home/blue/wowhoneypot/log:/root/wowhoneypot/log -p 80:8080 -d --name wowhp wowhoneypot

ポートは80番に接続し、コンテナ内のログ用のディレクトリを共有にしています。

1か月ほど動かしてみた

期間

2017年12月24日 ~ 2018年1月26日

アクセス数

全アクセス : 2149

ユニークなソースIP : 290

1つのIPからの最多アクセス回数 : 287

観測した攻撃の例

S2-045を狙った攻撃は色々なところから色々なコマンドを実行しようとしょっちゅう来ています。 D-Link製のルーターを狙った攻撃では、「cd /var/tmp && echo -ne \x00\xA0\xAF\x21\x20 >> drop && echo OK」というようなコマンド実行が大量にあり、おそらくバイナリファイルを直接echoで書き出そうとしているようでした。 Shellshockも一度来ると複数のURLパスに大量に同じコマンド実行を試みる攻撃があり、アクセス数がかなり増えました。 phpmyadmin脆弱性を狙った攻撃もかなり頻繁にあり、特にZmEuというツールを利用したスキャン(参考)がかなり多いです。

後は意味は分からないですが、/直下へのPOSTで謎のBase64で符号化されたデータを送ってくるリクエストが900件以上ありました(参考)。

それ以外には、Ruby on Railsを狙った攻撃や、魔法少女アパッチマギカTomcatの管理画面へのログイン試行、新年の挨拶なんかを観測できました。

WebLogicを狙う攻撃を観測してみたい

植えてからしばらくして、WebLogic脆弱性(CVE-2017-10271)のPoCが公開され、実際の攻撃に利用されだしました。前回の記事 でも書きましたし、WOWHoneypotでも観測できたようです。

ハニーポット観察記録(38)「WebLogic の WLS Security に対するコマンド実行の試み(CVE-2017-10271)」 at www.morihi-soc.net

ということで、うちでも観測してみたいなーと思い、記事では7001番ポート(WebLogicのデフォルトポート)に攻撃が来ていたので、こちらでも7001番ポートで待ち受けてみました。

docker run -it -v /home/blue/wowhoneypot/log_7001:/root/wowhoneypot/log -p 7001:8080 -d --name wowhp_7001 wowhoneypot

Dockerで立ち上げるポートを付け替えているだけです。 結果として、2017年12月28日 ~ 2018年1月26日の期間で42のアクセスがありました。

内CVE-2017-10271を狙った攻撃は全部で6つのIPから24件ありました。「/wls-wsat/CoordinatorPortType」へのPOSTですが、それとは別に、調査目的のGETリクエストも4つのIPから5件ありました。PoCによっては最初にGETしてWebLogicのコンテンツであるかを確認するようなものもあったので、そうした攻撃前の調査通信と思われます。

攻撃の内容はpingコマンドで攻撃の成功を確認するものや、wgetでファイルをダウンロードするもの、また、touchコマンドで「/tmp」配下にファイルを作成するものなどがありました(touchでファイルを作成したところで何がしたいのか不明ですが……)。

WebLogicの攻撃は事前にHTTPヘッダでWebLogicを利用していることを調べたり、GETでコンテンツがあるかを確認してから攻撃が来るパターンもあるようなので、ちゃんと観測しようとすると、その辺の調整は必要ですね。

(ちなみに、CVE-2017-10271を狙った攻撃を観測するようのルールはすでにあるようなので、アップデートすれば観測できそうです。)

今回は以上です。 漠然とハニーポットを立てて攻撃を観測するのもいいですが、何か特定の脆弱性を狙った攻撃を観測したいとなった場合はある程度カスタマイズは必要ですね。 WOWHoneypotでもルールにマッチした場合に応答を変えるような機能もありますし、単純なものなら、それだけで事足りそうです。 もしくは、WOWHoneypotを参考にして、自分で作ってもいいかなと思いました。ソースコードはそれほど複雑でもないので。

後はログの分析の方法を考える必要がありますね。同じ攻撃ばっかり見ててもつまらないので……。