/
カテゴリー

解説

問題文

これはみんな大好きフリー素材を提供する、いやすとやの「横から失礼する人」です。

Aさんはこの画像を使って右側から失礼したかったのですが、元のままでは左側からしか失礼できません。
なんとかしたいAさんはこの画像を左右反転させることで右から失礼することに決めました。
<
以下はC言語で書かれたAさんの左右反転するための擬似コードです。
Aさんのコード (左右反転)

flip_horizontal.c

ところでその傍ら、Aさんの友人のBさんはこの画像を使って下側から失礼したかったのですが、元のままでは上側からしか失礼できないことに気づき困っていました。
なんとかしたいBさんはこの画像を上下反転させることで下から失礼することに決めました。

以下はC言語で書かれたBさんの上下反転するための擬似コードです。
Bさんのコード(上下反転)

flip_vertical.c

ところでこの横から失礼する人は縦横同じサイズの正方形の画像で二人とも同じもを使用した為、左右に反転させようと上下に反転させようと計算量は変わりません。
またAさんとBさんの実行環境は以下の通り全く同じものでした。

  OS: Ubuntu18.04
  CPU: Intel(R) Xeon(R) CPU L5640
  RAM: 48GB
  

にもかかわらず、AさんとBさんのコードを実行した結果以下のように反転するのにかかる時間に大きな差が出てしまいました。
– Aさんのコードを実行: 約13秒

– Bさんのコードを実行: 約36秒

この差がなぜ出るのか困っているAさんとBさんのために、原因を記述し明らかにしてください。
また、Bさんはコードを速くするためにどうすればよかったのかも記述してください。

解説

解答のキーとしては
メモリアクセスの局所性 -> CPUのキャッシュメモリをうまく使用できたかどうか
がちゃんと記入できているかを特に重視して見ました。

一度参照した場所から近い場所が参照されると期待してキャッシュが行われる(メモリアクセスの局所性)ため、CPUのキャッシュメモリが生かされやすいかどうかが、AさんとBさんの違いにありました。
2次元配列の場合、mallocをみてもわかる通り、各rowに対して連続的にcolのメモリが確保されていることを踏まえるとメモリ上には例えばrow=0の時にcol = 0, 1, 2, …と連続的に確保され、次にrow=1の時col = 0, 1, 2,…という順番で確保されていることがわかります。
このことからAさんのコードでは内側のループにcolが来ていることから、できる限りメモリの連続したアドレスにアクセスできていることがわかります。
逆にBさんのコードでは内側のループがrowだったため、常に飛び飛びのアドレスにアクセスしていることがわかります。
結果Aさんの方がBさんよりもうまくキャッシュを活かせていたことがわかります。

以上を踏まえると、BさんのCPUメモリアクセスの順番を連続的にしてあげる = 内側と外側のforループの順番を変えてあげることでキャッシュをより活かせることができました。

具体的にはここの2重ループを逆にしてあげる必要がありました。

for (col = 0; col < COL_LENGTH; col++) {
    for (row = 0; row < ROW_LENGTH/2; row++) {
        ???
    }
}

以下が逆にした2重ループです。

for (row = 0; row < ROW_LENGTH/2; row++) {
    for (col = 0; col < COL_LENGTH; col++) {
        ???
    }
}

本来は上記のような解答を期待していたのですが、中には2次元配列の値を一つ一つ入れ替えずにrowの参照するcolの一次元配列をそのまま入れ替える、という解答もあり、確かにその方が簡単かつループ回数も減らせて計算量まで減らせるので、なるほどなぁと出題者も納得させられるものもありました。

つまり
row=0がcolの一次元配列その1を
row=1がcolの一次元配列その2を
row=2がcolの一次元配列その3を
...
row=nがcolの一次元配列そのn-1を

と参照しているため、row=0の配列とrow=n-1の配列の参照を、row=1の配列とrow=n-2の配列を...と入れ替えるだけで簡単に上下反転することができました。

for( row = 0 ; row < ROW_LENGTH/2; row++) {
   int *temp = matrix[row];
   matrix[row] = matrix[ROW_LENGTH - row - 1];
   matrix[ROW_LENGTH - row - 1] = temp;
}

これは素敵な解法ですが、これはインフラの問題というよりもプログラミングコンテストのような解法にはなってしまうので、出題者的にはできればメモリアドレスの局所性やCPUのメモリキャッシュの話をしてほしかったです。

最後に一つ目立った解答として、そもそもmallocのrowとcolの順番を入れ替えればいいのではないかという解答も複数見受けられましたが、その場合、確かにBさんのforループではメモリアドレスの局所性を考慮しうまくCPUのキャッシュは活かせるかもしれませんが、そのままではAさんと同じく左右反転を行ってしまっていることに注意して、image_loadの際にrowとcolを逆にする必要がある、という点も考慮する必要があります。

 /
カテゴリー

解説

問4

SNMP Agentのeth1のIPアドレスとサブネットマスクを回答してください。ただし、SNMP Agentにはログイン出来ません。

VNCサーバへの接続情報はpstateを参照してください。

・SNMP ManagerのSSH用 ID/パスワード: admin / KQ4iximz

・ヒント:SNMPv2cが稼働中 「コミュニティ名:ictsc2018-pre2」

・【回答のフォーマット(右記を参照)】 192.168.0.254/24

◆問4. 解説◆

【正答:192.168.12.26/29】

Agentには直接ログイン出来ませんが、SNMPが稼働しているためManagerからIF設定状態を問い合わせることが可能になっています。
以下のコマンドなどでIF設定情報収集することが出来ます。

  • snmpwalk -v 2c -c ictsc2018-pre2 192.168.0.1 .1.3.6.1.2.1.4.20.1.1
  • snmpwalk -v 2c -c ictsc2018-pre2 192.168.0.1 .1.3.6.1.2.1.4.20.1.3
admin@manager-server:~$ snmpwalk -v 2c -c ictsc2018-pre2 192.168.0.1 .1.3.6.1.2.1.4.20.1.1
iso.3.6.1.2.1.4.20.1.1.127.0.0.1 = IpAddress: 127.0.0.1
iso.3.6.1.2.1.4.20.1.1.192.168.0.1 = IpAddress: 192.168.0.1
iso.3.6.1.2.1.4.20.1.1.192.168.12.26 = IpAddress: 192.168.12.26
admin@manager-server:~$
admin@manager-server:~$ snmpwalk -v 2c -c ictsc2018-pre2 192.168.0.1 .1.3.6.1.2.1.4.20.1.3
iso.3.6.1.2.1.4.20.1.3.127.0.0.1 = IpAddress: 255.0.0.0
iso.3.6.1.2.1.4.20.1.3.192.168.0.1 = IpAddress: 255.255.255.0
iso.3.6.1.2.1.4.20.1.3.192.168.12.26 = IpAddress: 255.255.255.248
admin@manager-server:~$

※指定した回答フォーマットと異なる形式で回答した場合には減点としました

問5

SNMP Agentから5分に1回の頻度で通知(trap)される情報を基にトラブルを解消してください。ただし、SNMP Agentにはログイン出来ません。
回答にはトラブルを解消する際に投入するべき「コマンド1つのみ」を記載してください。
VNCサーバへの接続情報はpstateを参照してください。

・SNMP ManagerのSSH用 ID/パスワード: admin / KQ4iximz

・ヒント1:SNMPv2cが稼働中 「コミュニティ名:ictsc2018-pre2」

・ヒント2:SNMP Managerで受信したtrap通知のログファイル「/var/log/snmpd/snmptrap.log」

・ヒント3:トラブルを解消してもAgentからのtrap通知は停止されません

・【回答のフォーマット(右記を参照)】 tcpdump -i eth0 port 80

◆問5. 解説◆

【正答:snmpset -v 2c -c ictsc2018-pre2 192.168.0.1 .1.3.6.1.2.1.1.5.0 s agent-server】

Managerにて5分毎に受信するTrapログは以下の通りでした。

 
Dec 15 18:05:01 manager-server snmptrapd[668]: 2018-12-15 18:05:01  [UDP: [192.168.0.1]:60304->[192.168.0.100]:162]:#012iso.3.6.1.2.1.1.3.0 = Timeticks: (16605010) 1 day, 22:07:30.10#011iso.3.6.1.6.3.1.1.4.1.0 = OID: iso.3.6.1.2.1.1.5.0

下記のような記事を参照すると問題でのTrapは【OID: iso.3.6.1.2.1.1.5.0】をTrap通知していることがわかります。

https://ecl.ntt.com/documents/tutorials/rsts/Firewall/fwoperation/snmp.html
https://knowledgebase.paloaltonetworks.com/KCSArticleDetail?id=kA10g000000ClG6CAK

続いてAgentが通知してきている【OID: iso.3.6.1.2.1.1.5.0】について調査します。

 
admin@manager-server:~$ snmpwalk -v 2c -c ictsc2018-pre2 192.168.0.1 .1.3.6.1.2.1.1.5.0
iso.3.6.1.2.1.1.5.0 = STRING: "test01-server"
admin@manager-server:~$

【.1.3.6.1.2.1.1.5.0】は「ホスト名」を示すOIDです。
問2でもOIDに関して出題しましたが「iso」の部分は「.1」と変換出来ます。

https://www.alvestrand.no/objectid/1.3.6.1.2.1.1.5.0.html

snmpwalkコマンド結果よりAgentから取得したMIB上のホスト名が「test01-server」になっています。
問題文にもある通り、本来は「agent-server」が設定されていなければなりませんが情報が書き変わってしまっています。
このトラブルを解消するためには下記のコマンドを用いると設定情報の差異を解消することが出来ます。

snmpset -v 2c -c ictsc2018-pre2 192.168.0.1 .1.3.6.1.2.1.1.5.0 s agent-server

この問題ではAgentの「snmpd.conf」に下記の設定をすることでMIB情報への書き込みを許可するようにしてありました。

rwcommunity ictsc2018-pre2 default

全体講評

特に問3と問5の正答率が低かったです。
問3については「(b) 標準機能でRSAなどの暗号化技術に対応したバージョンが存在するSNMPの方がセキュリティが高い」も選択するチームが多かったです。
SNMPv3の登場によってセキュリティが向上したという認識は持っていますが、どのような暗号技術を使用することが出来るかまでは把握していないというチームが多かったのだと思います。

問5については監視ツール(Zabbixなど)との連携が完了して運用フェーズになると直接SNMPログに触れる機会が少なくなってしまうのであえて直接ログを読み解く問題としました。
あまり詳細な情報を与えなかったので戸惑ったチームも多かったかと思いますが、不慣れなログについても対処を求められる場面は多くあります。
「ログフォーマットを調べる」「収集できる情報はとりあえず収集してみる」「事前情報との差分はないか?」という判断が出来たチームは回答出来たと思います。

 /
カテゴリー

解説

問題1

あるクラウドでは1時間あたり1ドルでvCPU 16コア メモリ64GBのサーバインスタンスを借りることができます。
このサーバを1年間利用したとき、必要なコストはいくらですか。一番近いものを選んでください。
※ 1年は365日とします。

解説

1ドル * 24時間 * 365日 = 8760ドル

問題2

あるクラウドではサーバインスタンスを使う方法が2種類あります。

  1. 1時間あたり1ドルでvCPU 16コア メモリ64GBのサーバインスタンスを借りることができます。
  2. サーバ1台につき、2628ドル一括で支払うと、そこから1年間は1時間あたり0.3ドルでvCPU 16コア メモリ64GBのサーバインスタンスを借りることができます。

2番目の方法は実際のクラウド事業者でも用意されていることのある、インスタンスの利用料金をいくらか先払いする代わりに通常の時間課金のみの場合よりも安く使うことができるという購入方法です。
2番目の方法でサーバを1年間利用した場合、1番目の購入方法に比べてかかるコストは何%安くなりますか。 一番近いものを選んでください。
※ 1年は365日とします。

解説

2番目の方法で1年間サーバを1台利用した場合は

2628ドル + 0.3ドル * 24時間 * 365日 = 5256ドル

問題1より1番目の方法で1年間サーバを1台利用した場合は8760ドルなので、

1 – (5256 / 8760) = 0.4

40%

問題3

あるクラウドではサーバインスタンスを使う方法が2種類あります。

  1. 1時間あたり1ドルでvCPU 16コア メモリ64GBのサーバインスタンスを借りることができます。
  2. サーバ1台につき、2628ドル一括で支払うと、そこから1年間は1時間あたり0.3ドルでvCPU 16コア メモリ64GBのサーバインスタンスを借りることができます。

2番目の方法は実際のクラウド事業者でも用意されていることのある、インスタンスの利用料金をいくらか先払いする代わりに通常の時間課金のみの場合よりも安く使うことができるという購入方法です。
2番目の方法でサーバを継続して利用した場合、1番目の購入方法に比べて2番目の購入方法の方が支払い予定のコストが安くなる(損益分岐点)は何日目ですか。 一番近いものを選んでください。
※ 1年は365日とします。

解説

2つの方法のコストの計算式が交差する場所が損益分岐点なので、

普通に使った時: y = 1 * x
前払いしたとき: y = 2628 + 0.3 * x

の式が交差する時だから、

2628 + 0.3 * x = 1 * x
x = 約3754.28

3754.28 / 24 = 約156.4

答えの選択肢のうち一番近いのは156日目

問題4

あるクラウドではサーバインスタンスを使う方法が2種類あります。

  1. 1時間あたり1ドルでvCPU 16コア メモリ64GBのサーバインスタンスを借りることができます。
  2. サーバ1台につき、2628ドル一括で支払うと、そこから1年間は1時間あたり0.3ドルでvCPU 16コア メモリ64GBのサーバインスタンスを借りることができます。

2番目の方法は実際のクラウド事業者でも用意されていることのある、インスタンスの利用料金をいくらか先払いする代わりに通常の時間課金のみの場合よりも安く使うことができるという購入方法です。
あるWebサービスでは1日のなかでアクセス数が変動するのに伴い、サーバ負荷が変動します。クラウドの従量課金制という特性を生かしてサーバの台数を時間帯によって増減させることでコストを安く抑えることを検討しました。1番目の買い方と2番目の買い方をうまく組み合わせることでさらなるコスト削減を検討しています。サービスを1年間運用する場合、2番目の買い方でサーバインスタンスを何台分購入すべきでしょうか。
あるWebサービスでは、サーバインスタンスの台数を1年間毎日、日本時間で0時〜8時は4台、8時〜16時は6台、16時〜24時は8台という構成になるようにスケジュールする予定です。
※ 1年は365日とします。
※ サーバインスタンスの従量課金分は1時間ごとに請求されます。(例) 1分間使った場合は1時間分請求、50分間使った場合は1時間分請求、61分間使った場合は2時間分請求される。
※ 2番目の買い方での従量課金分の充当は1台分につき、1台にしかできません。 (例) 2番目の方法で2台分を購入しているユーザがサーバインスタンスを3台起動すると、従量課金分は1台目のサーバは1時間0.3ドル、2台目のサーバは1時間0.3ドル、3台目のサーバは1時間1ドル、というように計算されます。ただし、この状態で1台目のサーバインスタンスを終了した場合は安く使えるサーバインスタンス台数の枠が余るので、次の課金単位時間より1時間あたり0.3ドルの支払いとなります。

解説

問題3より、全期間の42.8%が経過すると元が取れるということがわかる。

24時間起動しているサーバ(100%): 元が取れるから買うべき
16時間起動しているサーバ(66.6%): 元が取れるから買うべき
8時間起動しているサーバ(33.3%): 元が取れないので買わないべき

24時間起動しているサーバは4台、16時間起動しているサーバは2台なので、合計6台買うときが一番コストパフォーマンスがよい。

問題5

あるクラウドではサーバインスタンスを使う方法が2種類あります。

  1. 1時間あたり1ドルでvCPU 16コア メモリ64GBのサーバインスタンスを借りることができます。
  2. サーバ1台につき、2628ドル一括で支払うと、そこから1年間は1時間あたり0.3ドルでvCPU 16コア メモリ64GBのサーバインスタンスを借りることができます。

2番目の方法は実際のクラウド事業者でも用意されていることのある、インスタンスの利用料金をいくらか先払いする代わりに通常の時間課金のみの場合よりも安く使うことができるという購入方法です。
あるWebサービスでは1日のなかでアクセス数が変動するのに伴い、サーバ負荷が変動します。クラウドの従量課金制という特性を生かしてサーバの台数を時間帯によって増減させることでコストを安く抑えることを検討しました。1番目の買い方と2番目の買い方をうまく組み合わせることでさらなるコスト削減を検討しています。サービスを1年間運用する場合、2番目の買い方を適切に組み合わせることで、すべてのサーバを1番目の買い方で運用する場合と比べて何%コスト削減ができますか。一番近いものを選んでください。
あるWebサービスでは、サーバインスタンスの台数を1年間毎日、日本時間で0時〜8時は4台、8時〜16時は6台、16時〜24時は8台という構成になるようにスケジュールする予定です。
※ 1年は365日とします。
※ サーバインスタンスの従量課金分は1時間ごとに請求されます。(例) 1分間使った場合は1時間分請求、50分間使った場合は1時間分請求、61分間使った場合は2時間分請求される。
※ 2番目の買い方での従量課金分の充当は1台分につき、1台にしかできません。 (例) 2番目の方法で2台分を購入しているユーザがサーバインスタンスを3台起動すると、従量課金分は1台目のサーバは1時間0.3ドル、2台目のサーバは1時間0.3ドル、3台目のサーバは1時間1ドル、というように計算されます。ただし、この状態で1台目のサーバインスタンスを終了した場合は安く使えるサーバインスタンス台数の枠が余るので、次の課金単位時間より1時間あたり0.3ドルの支払いとなります。

解説

(8台1番目の買い方の時) = 365日 * (1ドル * 8時間 * 4台 + 1ドル * 8時間 * 6台 + 1ドル * 8時間 * 8台) = 52560ドル

(2台1番目の買い方、6台2番目の買い方の時) = 2628ドル * 6 + 365日 * (0.3ドル * 24時間 * 4台 + 0.3ドル * 16時間 * 2台 + 1ドル * 8時間 * 2台) = 35624ドル

1 – (2台1番目の買い方、6台2番目の買い方の時) / (8台1番目の買い方の時) = 約32.2%

 /
カテゴリー

解説

問題文

弊社のコーポレートサイトに以下のような文言が追加されてしまいました。

▼ コーポレートサイトのURL
http://qew.example/
※ VNCサーバのブラウザ又はcurl等で閲覧可能です
※ ブラウザの起動方法: デスクトップ上で右クリック → Applications → Web Browser

▼ 追加された文言(少し違うときもあります)

激安コピーバッグ example.com 激安コピー時計 example.com 激安コピーアクセサリー example.com

内容に心当たりはないので、改ざんされてしまったようです。

該当ファイルを編集して元に戻しましたが、すぐにまた改ざんされてしまいます……。
(是非試してみてください……該当行を消しても、しばらくすると復活してしまうんです……)
サーバがハッキングされているのでしょうか??大至急、原因を特定して直してください!!
また、同じ手口で改ざんされないような対処もお願いします。

サーバ上のファイルや設定はどのように変更いただいて問題ありませんが、
現在も一般の利用者様が利用しているので、できるだけ閲覧に影響を及ぼさないようにしてください!!

構成
[Webサーバ (192.168.0.33)] – [ロードバランサ (192.168.0.200)] – [インターネット] – [Webサイトの利用者]

  • 出題上の踏み台となる[VNCサーバ]から[Webサーバ]にSSH接続が可能です。
      - [Webサーバ]のファイルが改ざんされてしまっています
      - [ロードバランサ]へのSSH接続は許可されていません。
  • Webサイトの利用者は、必ず[ロードバランサ]を経由して接続します
      - [インターネット]から[ロードバランサ]を経由しない接続は許可されていません
      - 将来的に分散構成にする予定でしたが、今のところ[Webサーバ]は1台です

WebサーバのSSH認証情報

VNCサーバから以下の認証情報で接続可能です。

  • IPアドレス: 192.168.0.33
  • ユーザ名: centos
  • パスワード: ********

※ VNCサーバ自身にも直接SSH接続可能です。VNC経由のTerminalで操作するよりオススメです。

出題に伴うメタ的な注意事項

  • 上記の[インターネット]と[Webサイトの利用者]は出題のための架空の存在ですが、アクセスは発生させています
      - それらしく振る舞いますが多少の違和感には目をつぶってください
      - 以下の例示用IPアドレスを利用していますが、国内外のISPが保有するセグメント(つまり普通のグローバルIPアドレス)として読み替えてください
        - 192.0.2.0/24
        - 198.51.100.0/24
        - 203.0.113.0/24
  • 採点は「問題文」にて提示した依頼内容を満たしているか自動で行います
      - サーバの状況のみで採点を行います
      - 対応完了の報告は必要ありません

解答・解説

発生していた問題

根本的には、お問い合わせフォームに設置されていたアップローダに脆弱性がありました。

# "/var/www/wordpress/wp-contact/contact.php"より抜粋
$tmpfile = $_FILES['input-file']['tmp_name'];
$filename = $_FILES['input-file']['name'];

// あぶない拡張子を判定して、実行されないように拒否する
if (preg_match('/\.(php|shtml|cgi)$/', $filename)) {
  http_response_code(400);
  exit('ERROR: 実行可能ファイルのアップロードは禁止されています。');
}

// アップロードされた一時ファイルをfiles/以下に保存する
move_uploaded_file($tmpfile, 'files/' . $filename);

非常に雑に実装されている為、複数の脆弱性を抱えている可能性がありますが、今回攻撃に利用されたのは拡張子判定の部分です。
ブラックリスト方式で拒否していますが、「.htaccess」が含まれていない為、以下のファイルをアップロードされてしまいました。

# /var/www/wordpress/wp-contact/files/.htaccess

  SetHandler application/x-httpd-php

結果として、/screenshots-.*\.jpg$/にマッチするファイルはphpとして実行されるようになり、かつjpgに対するアップロード制限は存在しない為、拡張子を偽装したphpをアップロードしGET/POSTリクエストを行うことで、任意のphpコードを実行可能な状態となっていました。

ただし、上記を利用して直接改ざんを行うと、アクセスログ等からすぐに判明してしまいます。その為、今回の攻撃者は上記の脆弱性を利用して予め踏み台を用意していました。

具体的には、POSTされた内容をeval()するだけのphpファイルです。これを以下の3箇所に設置しており、必要に応じて使い分けていました。

/var/www/wordpress/wp-content/upgrade/index.php
/var/www/wordpress/wp-includes/feed-xml.php
/var/www/wordpress/wp-content/plugins/world.php

また、任意のphpコードを実行できるということは、phpのsystem()も利用できますので、phpの実行ユーザ権限で任意のシェルコードを実行できる状態となってしまいました。今回はphpがapacheの実行ユーザ権限で動作していた為、DocumentRoot以下のファイルは基本的に改ざん可能な状況でした。

具体的に改ざん対象となったファイルはWordPressテーマのヘッダ末尾となります。

# /var/www/wordpress/wp-content/themes/lightning/header.php より抜粋
        


激!激!激!激安コピーバッグ example.com 激安コピー時計 example.com 激安コピーアクセサリー example.com

解答例

本問題では複数の解答が考えられます。以下に作問者が想定していた解法を示します。

まずはじめに、改ざん箇所を見つけて削除します。
Webページを目視にて確認すると、ヘッダ末尾付近が改ざんされているとわかります。
WordPressにて構築されているため、構成を把握している人はテーマのheader.phpに直接たどり着けるかもしれませんが、わからなくともgrepで検索すると特定が可能です。

 
# grep -r "激安" /var/www/wordpress/wp-content/
/var/www/wordpress/wp-content/themes/lightning/header.php:

激!激!激!激安コピーバッグ example.com 激安コピー時計 example.com 激安コピーアクセサリー example.com

今回、ファイルの編集に関して特に禁止事項は無いため、vi等で該当行を削除すれば最初の改ざんは対処出来ます。
対応後、数十秒待つと再び改ざんされてしまいます。原因の特定方法は複数考えられますが、Webアクセス経由を疑う場合はアクセスログを調べると良いでしょう。
まず、header.phpのタイムスタンプから改ざんが発生した時刻を調べます。

 
# stat /var/www/wordpress/wp-content/themes/lightning/header.php`
  File: `/var/www/wordpress/wp-content/themes/lightning/header.php'
  Size: 1508            Blocks: 8          IO Block: 4096   通常ファイル
Device: fd03h/64771d    Inode: 522539      Links: 1
Access: (0644/-rw-r--r--)  Uid: (   48/  apache)   Gid: (   48/  apache)
Access: 2018-12-17 11:56:06.206341004 +0900
Modify: 2018-12-17 11:56:05.522324860 +0900
Change: 2018-12-17 11:56:05.522324860 +0900
 Birth: -

そして、該当する時刻のアクセスログを調べます。(時刻は多少前後する場合があります)

 
# grep "17/Dec/2018:11:56:05" /var/log/httpd/access_log 
192.0.2.35 192.168.0.200 - - [17/Dec/2018:11:56:05 +0900] "POST /wp-content/upgrade/ HTTP/1.0" 200 - "http://qew.example/" "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36 Edge/17.17134"
203.0.113.153 192.168.0.200 - - [17/Dec/2018:11:56:05 +0900] "GET /company/ HTTP/1.0" 200 9466 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; rv:33.1) Gecko/20100101 Firefox/33.1"

該当時間帯にアクセスのあったファイルを調べると、”/wp-content/upgrade/” つまり “/var/www/wordpress/wp-content/upgrade/index.php” にPOSTリクエストが届いており、中身を見ると明らかに不審であることがわかります。
該当ファイルを削除の上、改ざん箇所を再度直して様子を見ましょう。
……数十秒後、再び改ざんされます。
同様の方法でも対応が可能ですが、同じファイルが複数仕掛けられている能性がありますので、grepしてみましょう。

 
# grep -lr "\$_POST\['cmd'\]" /var/www/wordpress/
/var/www/wordpress/wp-content/plugins/world.php
/var/www/wordpress/wp-content/upgrade/index.php
/var/www/wordpress/wp-includes/feed-xml.php

未発見のファイルが2つ見つかりました。また、消したはずのファイルも復活しています。
全て削除の上、改ざん箇所を再度直して様子を見ましょう。
……数十秒後、再び改ざんされます。
また、/var/www/wordpress/以下のタイムスタンプが全て更新されてしまいました。隠したいファイルがあるのでしょうか。
調べると消したはずの/wp-content/plugins/world.phpが復活していました。

 
# grep -lr "\$_POST\['cmd'\]" /var/www/wordpress/
/var/www/wordpress/wp-content/plugins/world.php

“world.php”を設置するコードが存在しないかgrepすると、なぜかjpgファイルが見つかります。
fileコマンドで調べると実態はphpのようです。アクセスログを調べるとGETリクエストが来ています。

 
# grep -lr "/world.php" /var/www/wordpress/
/var/www/wordpress/wp-contact/files/screenshots-20181121-231123.jpg

# file /var/www/wordpress/wp-contact/files/screenshots-20181121-231123.jpg
/var/www/wordpress/wp-contact/files/screenshots-20181121-231123.jpg: PHP script, UTF-8 Unicode text, with very long lines

# grep screenshots-20181121-231123.jpg /var/log/httpd/access_log 
192.0.2.24 192.168.0.200 - - [17/Dec/2018:12:11:09 +0900] "GET /wp-contact/files/screenshots-20181121-231123.jpg HTTP/1.0" 200 - "http://qew.example/wp-contact/contact.php" "Mozilla/5.0 (Windows NT 6.3; Win64; x64; rv:62.0) Gecko/20100101 Firefox/62.0"

拡張子jpgはphpとして実行されない設定が普通です。ですが、同じ階層に不審な.htaccessが設置されています。このファイルがjpgを実行可能にしているようです。

 
# cat /var/www/wordpress/wp-contact/files/.htaccess

  SetHandler application/x-httpd-php

該当するファイルを全て消して、改ざん箇所を直して様子を見ましょう。
……数十秒後、再び改ざんされます。また、.htaccessも復活しています。どうやら攻撃者は.htaccess自体を設置する術を持っているようです。
そもそもこのディレクトリは何なのでしょうか?一つ上の階層を見ると、問い合わせフォームに付属するアップローダがファイルを保存する場所のようです。

 
# head -n 12 /var/www/wordpress/wp-contact/contact.php 

一応、.phpはアップロードできないように判定されていますが、.htaccessについてはチェックされないようです。
簡単に.htaccessも拒否されるように判定を追加してみましょう。

 
# grep htaccess /var/www/wordpress/wp-contact/contact.php 
if (preg_match('/\.(php|shtml|cgi|htaccess)$/', $filename)) {

先程消したファイルを全て消して、改ざん箇所を直して様子を見ます。
……改ざんされなくなりました!
試行中に復活しているファイルがある場合は忘れずに消しておきます。

 
# grep -lr "\$_POST\['cmd'\]" /var/www/wordpress/
/var/www/wordpress/wp-content/plugins/world.php

以上で対応終了です。

採点基準

本問題では問題文に記載の通り、自動採点を行いました。採点箇所は以下です。

  • 加点ポイント
    • トップページに「株式会社QWE」が含まれる
    • トップページに「<head>」が含まれる
    • お問い合わせフォームが閲覧出来る
    • お問い合わせフォームにテストファイルがアップロード出来る
  • 減点ポイント
    • トップページに「激!激!激!」が含まれる
      • 初期状態にのみ含まれる為、一度でも改ざん箇所を消していれば回避されます
    • トップページに「激安コピーバッグ」が含まれる
    • http://qew.example/wp-content/upgrade/ にてsystem(‘uptime’)が実行可能
    • http://qew.example/wp-includes/feed-xml.php にてsystem(‘uptime’)が実行可能
    • http://qew.example/wp-content/plugins/world.php にてsystem(‘uptime’)が実行可能
    • アップローダ経由でsystem(‘uptime’)が実行可能
  • 無停止ボーナス
    • 監視アクセスログのエラー行数が少なければ加点
      • 他の点数が0点となったチームには加点していません

改ざんを防ぐ方法については問いません。万能の攻撃者を想定してしまうと他の脆弱性が存在する可能性もあることから、上記項目のみで採点としています。

出題の仕組みについて

アクセスログについて

本問題では予め偽装したログファイルを設置するのではなく、リアルタイムに一般ユーザ及び攻撃者のHTTPリクエストを発生させていました。
IPアドレスについては”X-Forwarded-For”ヘッダを偽装し例示用IPアドレスに、UAも予め用意しておいたパターンから偽装しました。
同じIPアドレスは必ず同じUAに、また善良なユーザが突然攻撃者にならないよう、予めリスト化したものからランダムに利用しています。

 
==> scripts/assets/ip-attacker.txt <==
192.0.2.11      Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36
198.51.100.15   Mozilla/5.0 (iPad; CPU OS 9_3_5 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13G36 Safari/601.1
192.0.2.181     Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36
192.0.2.20      Mozilla/5.0 (Windows NT 10.0; WOW64; rv:53.0) Gecko/20100101 Firefox/53.0
203.0.113.192   Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36

==> scripts/assets/ip-dummy.txt <==
192.0.2.72      python-requests/2.18.4
203.0.113.164   curl/7.15.5 (i686-redhat-linux-gnu) libcurl/7.15.5 OpenSSL/0.9.8b zlib/1.2.3 libidn/0.6.5
203.0.113.95    Mozilla/5.0 (compatible; DotBot/1.1; http://www.opensiteexplorer.org/dotbot, help@moz.com)
203.0.113.254   YahooCacheSystem; YahooWebServiceClient
192.0.2.122     python-requests/2.10.0

==> scripts/assets/ip-user.txt <==
203.0.113.99    Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 UBrowser/6.1.2015.1007 Safari/537.36
203.0.113.212   Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.137 Safari/537.36
198.51.100.227  Mozilla/5.0 (Linux; Android 7.0; SM-G610M Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Mobile Safari/537.36
203.0.113.17    Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 YaBrowser/18.6.1.770 Yowser/2.5 Safari/537.36
203.0.113.165   Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36 Edge/15.15063
```

ip-dummy.txtは比較的不審なUAを利用しています。アクセス先も以下のような「なんとなく不審なURL」でした。本問題では特に意味はなく、実際の攻撃によるアクセスログを隠すためのダミーアクセスです。
```
http://qew.example/yao.php
http://qew.example/editor/fckeditor.js
http://qew.example/web.config
http://qew.example/sql/php-myadmin/index.php?lang=en
http://qew.example/51.php
http://qew.example/wwb/index.php?module=site&show=home
http://qew.example//pma/scripts/setup.php
http://qew.example/static/home/css/common.css
http://qew.example/index.htm
http://qew.example/xx.php

UAとダミーアクセス先は、実際に公開しているWebサーバから抽出した本物のログです。本問題は閉じた環境でしたが、グローバルに公開されていた場合は同様のアクセスが発生していたと思われます。

攻撃リクエストについて

本問題では改ざんを直しても、すぐに改ざんされてしまう状況を作りました。
これはロードバランサ内に設置したスクリプトが毎分トップページを監視しており、改ざん箇所が消えている場合のみ以下の手順で改ざんを試みます。

 
1, "http://qew.example/wp-content/upgrade/" による改ざん
2, "http://qew.example/wp-includes/feed-xml.php" による改ざん 及び、/wp-content/upgrade/index.php の復元
3, アップローダによる改ざん 及び、/wp-content/plugins/world.php の復元

http://qew.example/wp-content/plugins/world.php については、今回の攻撃スクリプトから利用されていません。これは、攻撃が完全に防がれてしまった場合に、後日改めて利用する目的で設置している為です。
攻撃に利用するとアクセスログ等から存在が露見してしまうため、今回は利用しないのです。このファイルも消しておかないと、数日後に再発してしまいます。

その他

本問題で発生した攻撃はフィクションですが、現実に発生した事案を元に作成しています。特に踏み台として設置されるphpは、現実では難読化や様々な機能を追加した「WebShell」と呼ばれるものが利用されます。比較的「よくある」サイバー攻撃なので、是非覚えておいてください。

 /
カテゴリー

解説

問題

DockerサーバとDockerレジストリサーバがあります。
Dockerサーバでdocker-composeでRedmineのサービスを立ち上げようとしたが、うまく起動しない・・・。
もう少しで立ち上がりそうだが、原因がわからないので修正し、VNCマシンからRedmineのページにアクセスできるようにしてください!

構成

ルータ[VyOS]

  • eth0(192.168.0.1) — VNC操作マシン(192.168.0.254)
  • eth1(172.16.0.1) — Dockerサーバ(172.16.0.2)
  • eth2(172.17.0.1) — Dockerレジストリ(172.17.0.2)

トポロジ図

ログイン情報(ssh)

  • VyOS: 操作不可
  • Dockerサーバ:
    • ID: root
    • Pass: xxxxxx
    • sshポート: 22
  • Dockerレジストリ: 操作不可

条件:

  • DockerRegistry上のDockerImageをコンテナとして使用すること
  • Dockerサーバにあるdocker-compose.yml(変更不可)を用いてRedmineサービスを立ち上げること
  • 必要に応じてDockerの設定を変更してかまわない
  • VNCマシンのブラウザからredmineページが表示されること

回答方法:

  • 「問題文」にて提示した条件が達成されているかで採点します。
  • サーバの状況のみで採点を行います。
  • 対応完了の報告は必要ありません。

解説

DockerでRedmineサービスを立ち上げる過程でのトラブルシューティングの問題です。
以下の問題を解決するとサービスが立ち上がります。

  • ① DockerレジストリLANとDockerサーバのnic-docker0のIP帯重複の解消
  • ② docker pullによるDockerイメージのダウンロードをhttp(80)にする(insecureオプション設定)

Dockerサーバにてdocker-compose up等でredmineサービスを立ち上げようとするとエラーとなります。
docker-compose.ymlに記載されているDockerイメージを見ると

...
redmine:
    image: docker-registry.local/redmine
...

イメージの取得元がdocker-registry.localになっています。
名前解決すると172.17.0.2で、これは/etc/hostsに記載されています。

つまり172.17.0.2のDockerレジストリからイメージを取得して使う意図が読み取れます。
しかしながらDockerサーバから172.17.0.2にpingを実行すると応答がありません。

[root@docker-server ~]# ping 172.17.0.2
PING 172.17.0.2 (172.17.0.2) 56(84) bytes of data.
From 172.17.0.1 icmp_seq=1 Destination Host Unreachable

これはDockerが作成している仮想NIC(docker0)が172.17.0.1/16であることが原因です。
Dockerレジストリ(172.17.0.2)向けのパケットがdocker0へルーティングされるため、正しくDockerレジストリと通信できません。

このため、docker0のIP帯を使用されていないプライベートIP帯に変更します。
変更する方法は複数ありますが、例として/etc/sysconfig/dockerOPTIONに記述する方法を示します。

[root@docker-server ~]# cat /etc/sysconfig/docker
...
OPTIONS='--insecure-registry 172.17.0.2 --bip 192.168.100.1/24  --selinux-enabled --log-driver=journald --signature-verification=false'
...

OPTIONS--bip 192.168.100.1/24を追加してdockerサービスを再起動します。

nicのdocker0のIPが変更されたことを確認します。

[root@docker-server ~]# ip a
...
3: docker0:  mtu 1500 qdisc noqueue state DOWN group default
    link/ether 02:42:28:18:cc:19 brd ff:ff:ff:ff:ff:ff
    inet 192.168.100.1/24 scope global docker0
       valid_lft forever preferred_lft forever
...

これでDockerレジストリ(172.17.0.2)へ疎通が取れるようになりました。

[root@docker-server ~]# ping 172.17.0.2
PING 172.17.0.2 (172.17.0.2) 56(84) bytes of data.
64 bytes from 172.17.0.2: icmp_seq=1 ttl=63 time=1.19 ms

①のIP帯重複問題を解決後にdocker-compose upを実行してもまだイメージをpullすることができません。

[root@docker-server ~]# docker-compose up
Pulling db (docker-registry.local/mariadb:)...
Trying to pull repository docker-registry.local/mariadb ...
ERROR: Get https://docker-registry.local/v1/_ping: dial tcp 172.17.0.2:443: getsockopt: no route to host

エラーを見ると172.17.0.2:443に接続し、アクセスに失敗していることがわかります。
curl等で172.17.0.2:443の状態を確認すると動作していません。

ここで、/etc/sysconfig/dockerの設定を見ると

[root@docker-server ~]# cat /etc/sysconfig/docker
...
OPTIONS='--insecure-registry 172.17.0.2 --bip 192.168.100.1/24  --selinux-enabled --log-driver=journald --signature-verification=false'
...

--insecure-registry 172.17.0.2の記述があり、docker標準のhttps(443)でアクセスさせずに、insecureアクセスであるポート80で通信を行うという意図が確認できます。
一見DockerレジストリサーバIPが172.17.0.2であるため、正しく設定できていそうですが、ここはホスト名を指定する必要があります。
docker-compose.ymlにはDockerレジストリをdocker-registry.localと記載していたため、記述を合わせます。
最終的なetc/sysconfig/dockerは以下の通りです。

[root@docker-server ~]# cat /etc/sysconfig/docker
...
OPTIONS='--insecure-registry docker-registry.local --bip 192.168.100.1/24  --selinux-enabled --log-driver=journald --signature-verification=false'
...

再度Dockerサービスを再起動し、docker-compose upをすることで、Redmineサービスが稼働し、完了となります。

講評

参加者の皆さん競技お疲れさまでした。Docker問題を出題させていただきました。
皆さんの回答結果ですが、満点のチームも多くDocker対策されているなという印象を受けました。

トラブル①

トラブル①のIP帯重複に関しては予選1でも出題された範囲になりますので、解けたチームも多かったのではないでしょうか。
Dockerを使う場合は172系のIP帯がデフォルトで使用されるため、プライベートIP帯との衝突が発生する場合があります。
原因の切り分けとしてサーバへIP層での疎通があるかどうかをしっかりと確認することが大切です。

トラブル②

トラブル②はDockerレジストリに関する問題です。
dockerイメージのpullはデフォルトでhttps通信となっています。
http通信にするためにはオプション指定が必要です。

Docker設定ファイルの/etc/sysconfig/dockerよりinsecureオプションが設定されていることからDockerレジストリへ、http通信を行う意図が読み取れます。
しかし実際には、Dockerイメージpull時のエラーで443ポートで通信を行っていることが読み取れますので、Dockerレジストリへの接続設定を見直すことが必要です。

全体

今回は実務で経験したトラブルを基に作問しました。楽しんで解いていただけたのであれば幸いです。
今後もトラブルシューティングする楽しさを大切にして、多くの技術を学んでください。

本問題で採点ミスが発生し成績発表後に点数、順位が変更となったこと深くお詫び申し上げます。