生産性のない話

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

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を参考にして、自分で作ってもいいかなと思いました。ソースコードはそれほど複雑でもないので。

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

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

10月にOracle WebLogicはパッチが出てましたが、その時に修正された脆弱性の攻撃コードが先週末あたりに公開されました。

実際に攻撃も来ているようで、探すと中国語での解説記事をちらほら見かけます。 morihi-soc 氏のハニーポットでも攻撃が観測されているそうです。

CVE-2017-10271について

JVN(JVNDB-2017-008734 - JVN iPedia - 脆弱性対策情報データベース)によると、脆弱性の内容は以下のようになっています。

概要

Oracle Fusion MiddlewareOracle WebLogic Server には、WLS Security に関する処理に不備があるため、機密性、完全性、および可用性に影響のある脆弱性が存在します。

影響を受けるシステム

オラクル

Oracle WebLogic Server 10.3.6.0.0

Oracle WebLogic Server 12.1.3.0.0

Oracle WebLogic Server 12.2.1.1.0

Oracle WebLogic Server 12.2.1.2.0

想定される影響

リモートの攻撃者により、情報を取得される、情報を改ざんされる、およびサービス運用妨害 (DoS) 攻撃が行われる可能性があります。

ただ、いくつかの中国語の記事を見る限り、場合によってはリモートコード実行が可能なようです。

【漏洞预警】Oracle WebLogic wls-wsat RCE CVE-2017-10271 & CVE-2017-3506|指尖安全|垂直互联网安全媒体

とりあえずGoogle翻訳に突っ込んだ結果をかいつまんで書くと以下のような感じです(間違っている可能性があることはご容赦ください)。

  • WebLogicのWLSコンポーネントにはリモートコード実行の脆弱性があり、該当するCVE-2017-3506のパッチが今年4月にリリースされた
  • CVE-2017-3506の攻撃コードは公開されていないが、攻撃には利用されていた
  • 4月に公開されたパッチをバイパスできる脆弱性(CVE-2017-10271)が見つかり、これは10月のパッチで修正された

ということのようです。 影響を受けるバージョンは限られていますが、リモートコード実行が可能なため、注意が必要です。

検証

WebLogic は有償のプロダクトですが、開発などの用途で使うための無償版があります(Oracleへの登録が必要です)。 今回はDockerでWebLogicの環境を作って本脆弱性の検証をやってみます。

環境構築

脆弱性のあるOracle WebLogic Server 12.1.3をDockerで建てます。

GitHub - oracle/docker-images: Official source for Docker configurations, images, and examples of Dockerfiles for Oracle products and projects

とりあえず、公式のDocker関連ファイルを持ってきます。

git clone https://github.com/oracle/docker-images.git

色々あるのを全部持ってきますが、使うのはOracleJavaとOracleWebLogicです。

まずベースとなるJavaのイメージを作成します。今回はJava8で。

cloneした「/OracleJava/java-8/」のディレクトリに「server-jre-8u151-linux-x64.tar.gz.download」というファイルがありますが、中身を見るとテキストでダウンロード先とファイルのハッシュが書いてあります。 このファイルを落としてこいということなので、リンクから同名のファイル(server-jre-8u151-linux-x64.tar.gz)を落としてきます。 落としてきたファイルは「~~.download」と同じディレクトリに置きます。

で、ビルドします。ビルドにはスクリプトが用意してあるので、叩くだけです。

sh build.sh

「doceker images」で確認するとoracle/serverjre:8のイメージができています。これが、WebLogicのベースになります。

次にWebLogicのイメージを作ります。やり方は上と同じです。今回は12.1.3なので、「/OracleWebLogic/dockerfiles/12.1.3/」のディレクトリを確認し、必要なファイルを落としてきます。 「fmw_12.1.3.0.0_wls.jar.download」と「wls1213_dev_update3.zip.download」があるので、それぞれのファイル内のリンクから同名ファイルをダウンロード(登録が必要)し、同じディレクトリに設置します。 その後、「/OracleWebLogic/dockerfiles/」にある、buildDockerImage.shを叩きます。

./buildDockerImage.sh -g -v 12.1.3

oracle/weblogic:12.1.3-genericのDockerイメージができます。

ようやくWebLogicのベースイメージができたので、ここからサンプルのDockerfileからWebLogicサーバを建てます。 「/OracleWebLogic/samples/1213-domain/」でREADME.mdの通りに実行します。

docker build -t 1213-domain --build-arg ADMIN_PASSWORD=admin1234 .

ADMIN_PASSWORDは英数8文字以上みたいな制約があるので気を付けてください。 これでWebLogicサーバのDockerイメージができました。 できたイメージはこんな感じ。

f:id:blueBLUE:20171226022002p:plain

これを起動します。

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

http://localhost:7001/console へアクセスして、こんな画面がでれば成功です。

f:id:blueBLUE:20171226022016p:plain

攻撃コード検証

まず、「http://localhost:7001/wls-wsat/CoordinatorPortType」へアクセスしてみます。このパスが今回の攻撃対象になります。

f:id:blueBLUE:20171226022035p:plain

PoCはこのパスに対して、POSTで実行するコマンドを含んだSOAPXMLを投げつけます。

f:id:blueBLUE:20171226022116p:plain

成功すると、サーバからは500 Internal Server Errorが返ります。コマンドの結果はレスポンスには載ってきません。

ここでは例として、「echo "HACKED!!" > /tmp/Hack」というコマンドを実行してみました。

コンテナの中に入って、確認します。

f:id:blueBLUE:20171226022053p:plain

確かにファイルが作成されていました。

ということで、リモートでコード実行ができました。

まとめ

日本ではあまり騒がれていませんが、すでに攻撃が行われており、該当するバージョンのWebLogicを利用している場合、おそらくデフォルトの状態で刺さる可能性がある危険な脆弱性です。 対策としては、WebLogicのバージョンアップ、暫定策として、wls-wsatへのアクセス制御や、関連するコンポーネントの削除が挙げられています。

WebLogicのログがどこにあるのか分からなかったのですが、もしログがあるなら、「/wls-wsat/CoordinatorPortType」へのPOSTでのアクセスログあるか、そのレスポンスが500になっていないかを確認するのがいいかと思います。

WebLogicの環境構築がちょっと面倒でしたが(有償の製品なので仕方ないですね)、とりあえずは検証まで行うことができて満足しました。

追記 (2017/12/28)

以下2点を補足として。

  • 今回利用したPoCでは「/wls-wsat/CoordinatorPortType」のパスへ攻撃しましたが、実際には「/wls-wsat/」配下の存在するパスであれば攻撃は成功するようです。アクセス制限を考えている場合は「/wls-wsat/」以下すべてのパスの制限をした方がいいです。

  • 検証したところ、上記の影響を受けるシステムとなっている以外のWebLogicのバージョンでも利用した攻撃コードが刺さりそうな感じでした。詳細な影響範囲は分かりませんが、WebLogicの10系と12系を利用している場合はとりあえず、攻撃影響・侵害の有無を確認した方がいいと思います。

SECCON 2017 Online CTF

今年も会社のチームで参加しました。 時間いっぱいの参加はできませんでしたが、 今年は昨年よりpwn以外の問題が増え、とっつきやすくなった感じです。 最近はどのCTFも軒並み難易度が高く、参加しても1番簡単な問題が1問解けるかどうかというくらいだったので、今回のような手ごろな問題を解いて達成感を得られるのはとてもありがたいです。 達成感、重要ですよね。

以下、軽くWrite up。

Run me! (Programming 100)

import sys
sys.setrecursionlimit(99999)
def f(n):
    return n if n < 2 else f(n-2) + f(n-1)
print "SECCON{" + str(f(11011))[:32] + "}"

再起でフィボナッチ数列を求めていますが、再起が深すぎて実行するとプログラムが止まります。なので、再起なしで、フィボナッチ数列を求めてやれば答えが出ます。

n={1:1,2:1}
for i in range(3,11012):
    n[i]=n[i-1]+n[i-2]
print("SECCON{" + str(n[11011])[:32] + "}")

この問題はすぐに解けました。再起の上限の設定なんてあるんだなーと。

Vigenere3d (Crypto 100)

$ python Vigenere3d.py SECCON{************} POR4dnyTLHBfwbxAAZhe}}ocZR3Cxcftw9

昨年のSECCONでも出てきたヴィジュネル暗号の3D版です。昨年と解き方は同じだろうということで、暗号処理の中身はあまり気にせず、頭の「SECCON{」を暗号し、出力の先頭と一致する鍵を探します。

s = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz_{}"

def _l(idx, s):
    return s[idx:] + s[:idx]
def crypto(p, k1, k2):
    t = [[_l((i+j) % len(s), s) for j in range(len(s))] for i in range(len(s))]
    c = t[s.find(p)][s.find(k1)][s.find(k2)]
    return c

key = []
cp = [['S','P'], ['E','O'], ['C','R'], ['C','4'], ['O','d'], ['N','n'], ['{','y']]
for c in cp:
    keys = []
    for i in s:
        for j in s:
            if crypto(c[0], i, j) == c[1]:
                keys.append([i,j])
    key.append(keys[0])
print key

鍵は2つあり、14文字の鍵とその逆順を使います。1文字ごとの2つの鍵の組み合わせを総当たりしています。 この処理で鍵の前半7文字とその逆順の前半7文字(つまり後半7文字)の組み合わせが出てきます。 ここで、それぞれの鍵は変数sのパターン分66通り出てくるのですが、実行してみると、66通りのどの鍵を使っても出力は同じになることが分かります。 そのため、1文字目はすべて「A」となる鍵を持ってきています。 これで、14文字の鍵が全てわかりますので、その鍵で復号して暗号文に一致する文字を探します。

flag = ''
i = 0
tmp = []
for g in reversed(key):
    tmp.append([g[1], g[0]])
key = (key + tmp) * 3
cstr = 'POR4dnyTLHBfwbxAAZhe}}ocZR3Cxcftw9'
for p in cstr:
    for k in s:
        if crypto(k, key[i][0], key[i][1]) == p:
            flag += k
    i += 1
print flag

換字のテーブルの構造はほとんど考えず、ただ総当たりでやりました。鍵が66通りどれでやっても結果が同じことに気づくのに時間がかかりました。

Powerful_Shell (Binary 300)

難読化されたPowershellを実行する問題。 まず、ps1ファイルを実行できるよう、管理者権限のPowershellコンソールで「Set-ExecutionPolicy RemoteSigned」を実行します。

ファイルの中身を見ると、「$ECCON」という変数に色々入れて最後に実行しているっぽいので、ラスト1文を消して「echo $ECCON」にすると、難読化を解除したスクリプトが見えます。

最初に耐解析的なセクションがあり、イベントログの件数を確認し、少なすぎればサンドボックスと判定します。この処理におそらく管理者権限が必要で、管理者権限のあるコンソールでなければ処理が停止します。

管理者権限のコンソールでスクリプトを実行してみます。

f:id:blueBLUE:20171212204400p:plain

背景が黒くなり「SECCON」ロゴが出てきます。耐解析のセクションを抜けると鍵盤が出てきて、入力に応じて音がでます。 出力する音を決めているのは「$keytone」という変数なので、ここをechoとかで見てみる(難読化解除後の16~20行目をコピペしてコンソールに張り付けてから、「echo $keytone」)。

Name                           Value
----                           -----
j                              493.891672853823
s                              293.669745699181
y                              415.31173722644
w                              277.187329377222
u                              466.171663254114
g                              392.002080523246
e                              311.132257498162
t                              370.000694323673
h                              440.007458245659
f                              349.234151046506
d                              329.633144283996
a                              261.63
k                              523.26

91行目の「$secret」変数の値と一致するkeyが正解。

「$secret=@(440,440,493,440,440,493,440,493,523,493,440,493,440,349)」

「hhjhhjhjkjhjhf」を入力すると「さくらさくら」が流れて、次のステージに行けます。 (ロゴといい、すごい凝ってる) パスワードを求められるので、さらに100行目以降の難読化を解除していきます。

色々やっているっぽいけど、とりあえず、「$plain」変数をechoします。 すると、asciiが10進になっているようなものが出てくるので、これを文字列に変換。 また、意味の分からない難読化されたスクリプトが出てきます。

${;}=+$();${=}=${;};${+}=++${;};${@}=++${;};${.}=++${;};${[}=++${;};
${]}=++${;};${(}=++${;};${)}=++${;};${&}=++${;};${|}=++${;};
${"}="["+"$(@{})"[${)}]+"$(@{})"["${+}${|}"]+"$(@{})"["${@}${=}"]+"$?"[${+}]+"]";
${;}="".("$(@{})"["${+}${[}"]+"$(@{})"["${+}${(}"]+"$(@{})"[${=}]+"$(@{})"[${[}]+"$?"[${+}]+"$(@{})"[${.}]);
${;}="$(@{})"["${+}${[}"]+"$(@{})"[${[}]+"${;}"["${@}${)}"];"${"}${.}${(}+${"}${(}${|}+${"}${(}${)}+${"}${(}${)}+${"}${)}${|}+${"}${)}${&}+${"}${(}${+}+${"}${&}${@}+${"}${+}${=}${+}+${"}${|}${)}+${"}${+}${=}${=}+${"}${[}${]}+${"}${)}${@}+${"}${+}${+}${+}+${"}${+}${+}${]}+${"}${+}${+}${(}+${"}${.}${@}+${"}${[}${]}+${"}${&}${=}+${"}${+}${+}${[}+${"}${+}${+}${+}+${"}${+}${=}${|}+${"}${+}${+}${@}+${"}${+}${+}${(}+${"}${.}${@}+${"}${.}${|}+${"}${(}${|}+${"}${+}${+}${=}+${"}${+}${+}${(}+${"}${+}${=}${+}+${"}${+}${+}${[}+${"}${.}${@}+${"}${+}${+}${(}+${"}${+}${=}${[}+${"}${+}${=}${+}+${"}${.}${@}+${"}${+}${+}${@}+${"}${|}${)}+${"}${+}${+}${]}+${"}${+}${+}${]}+${"}${+}${+}${|}+${"}${+}${+}${+}+${"}${+}${+}${[}+${"}${+}${=}${=}+${"}${.}${|}+${"}${+}${.}+${"}${+}${=}+${"}${)}${.}+${"}${+}${=}${@}+${"}${[}${=}+${"}${.}${(}+${"}${(}${|}+${"}${(}${)}+${"}${(}${)}+${"}${)}${|}+${"}${)}${&}+${"}${.}${@}+${"}${[}${]}+${"}${+}${=}${+}+${"}${+}${+}${.}+${"}${.}${@}+${"}${.}${|}+${"}${&}${=}+${"}${[}${&}+${"}${+}${+}${|}+${"}${(}${|}+${"}${+}${+}${[}+${"}${.}${(}+${"}${)}${@}+${"}${]}${+}+${"}${[}${|}+${"}${[}${|}+${"}${.}${|}+${"}${[}${+}+${"}${+}${@}${.}+${"}${+}${.}+${"}${+}${=}+${"}${|}+${"}${&}${)}+${"}${+}${+}${[}+${"}${+}${=}${]}+${"}${+}${+}${(}+${"}${+}${=}${+}+${"}${[}${]}+${"}${)}${@}+${"}${+}${+}${+}+${"}${+}${+}${]}+${"}${+}${+}${(}+${"}${.}${@}+${"}${.}${|}+${"}${)}${+}+${"}${+}${+}${+}+${"}${+}${+}${+}+${"}${+}${=}${=}+${"}${.}${@}+${"}${)}${[}+${"}${+}${+}${+}+${"}${|}${&}+${"}${.}${.}+${"}${.}${|}+${"}${]}${|}+${"}${+}${.}+${"}${+}${=}+${"}${|}+${"}${&}${)}+${"}${+}${+}${[}+${"}${+}${=}${]}+${"}${+}${+}${(}+${"}${+}${=}${+}+${"}${[}${]}+${"}${)}${@}+${"}${+}${+}${+}+${"}${+}${+}${]}+${"}${+}${+}${(}+${"}${.}${@}+${"}${.}${[}+${"}${&}${.}+${"}${(}${|}+${"}${(}${)}+${"}${(}${)}+${"}${)}${|}+${"}${)}${&}+${"}${+}${@}${.}+${"}${.}${(}+${"}${(}${|}+${"}${(}${)}+${"}${(}${)}+${"}${)}${|}+${"}${)}${&}+${"}${+}${@}${]}+${"}${.}${[}+${"}${+}${.}+${"}${+}${=}+${"}${+}${@}${]}|${;}"|&${;}

ここは本当に意味が分からなくて、しばらく悩みましたが、結局、一番最後についている「|&${;}」がスクリプトを実行している箇所だろうと考え、ここを削除し、「${;}」をechoすると、また10進数をcharに変換しているようだったので、これをascii化。

'$ECCON=Read-Host -Prompt \'Enter the password\'\r\nIf($ECCON -eq \'P0wEr$H311\'){\r\n\tWrite-Host \'Good Job!\';\r\n\tWrite-Host "SECCON{$ECCON}"\r\n\x0c'

でフラグにたどり着きました。

Powershellはあまり詳しくないのですが、思っていたよりかなり柔軟な難読化ができるんだなあ、という印象です。

その他

後は、Log search (Web 100) や SqlSRF (Web 400) に挑んではいましたが、答えにたどり着けず。Log search はElastic SearchのQuery DSLをインジェクションするのかと思って色々試していましたが、「違うじゃん」と分かったところで、諦めました。回答はもう少し単純だったようで、Elastic Searchももっと触れておかないとなと感じました。SqlSRF みたいな問題はいつも、割と好きで解きたいと思いつついつも解けない問題です。

後はCode Blueで坂井弘亮氏が話していたSOPの問題もありましたが、手を付けられませんでした。

今回もpwn系も少し解けるようになりたいなー、とCTFのたびに思っています。