本CTFの問題の原データは 現時点では公開されておりません。
公開された場合にはリンク等を追加します。
はじまり
この記事は
Sendai CTF 2022 での問題の一つである、ログ分析問題について、CTF初心者である私の復習を兼ねたいわゆるWrite UPです。一応復習を兼ねているのでかなり冗長に、かつハンズオンのように実行過程を辿れるよう書いています。
一応こんな人に
- これから知識ほぼ0でCTFに挑む人
- ログファイルの中身を見たことない人
-
grep
awk
とかなにそれ?おいしいの?ってくらいの人 - 「はっきんぐたのしそー( ᐛ👐)」 みたいな人
- 少しだけプログラミングができる人
全て私のことです。(うわっ…私の技術力、低すぎ…?)
環境
ProductName: macOS
ProductVersion: 12.5.1
BuildVersion: 21G83
実行はzshにて、Pythonは3系です。
問題
ログ分析問題
この大問は小問5つから構成され、アクセスログについてそれぞれ解答するものです。
アクセスログは以下のような構成になっています。
[2020-06-19 10:09:11+0900] XXX.XXX.XXX.XXX YYY.YYY.YYY.YYY:PP "GET /manager/html HTTP/1.1" 401 1032 R0VU************************************************************************************************************************
問題データの公開は認められていないため、部分的に情報を隠しています。
(当然ですが)CTF本番で配布されたデータでは具体的な値が記載されています。
XXX.XXX.XXX.XXX
とYYY.YYY.YYY.YYY:PP
はIPアドレスやポート番号を、R0VU*****...
はbase64でエンコードされた各種情報です。
このようなアクセスログが 36000行以上 あるファイルaccess_log
を解析します。
問題1
問題
ログファイル中の /manager/html への偵察行為の対象となるアプリケーション名を答えてください。
解答例
Googleは偉大なり。
トップに「攻撃ログ」と気になる文字列があるので開いてみると、どうやらtomcatというアプリケーションへのアクセスらしいです。
tomcatとはWebサーバにおいて動かすソフトウェアみたいなものです。
詳しくはGoogleなりYahooなりに聞いてください。
FLAGはtomcat
になります。
問題2
問題
ログファイルから一番多く偵察を受けたアプリケーション名を答えてください。
解答例
問題1から類推して、HTTPリクエスト( "GET /manager/html ..."
)に注目すると良さそうです。これをなんとかして36000行以上のデータに対して集計する必要があります。
awk
sort
uniq
コマンドを使うと良いらしいようです。(私はCTFの途中までこのコマンドを知りませんでした…)
とりあえずこの問題ではHTTPリクエストの部分だけ抜きだせれば良いので
% awk '{print $6}' access_log
を実行すると、出力(途中の抜粋)は
...
/TP/public/index.php?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=phpinfo&vars[1][]=1
/TP/public/index.php?s=captcha
/users?page=&size=5
/
/
/hudson
/
/
/manager/html
/manager/html
...
こんな感じに出てきます。
awk
コマンドでは各行の6番目の要素($6
)を表示([2020-06-19 10:09:11+0900] XXX.XXX.XXX.XXX YYY.YYY.YYY.YYY:PP "GET /manager/html HTTP/1.1" 401 1032 R0VU************************************************************************************************************************ 1 2 3 4 5 6 7 ...
といったような数え方です。(スペースで区切っています。区切り文字を変えるオプションもあるようです。)
次は重複をまとめた上で数えていきます。先ほどの出力に
% awk '{print $6}' access_log | sort | uniq -c
を実行すると出力(最初と最後3行ずつ)は
1 '/script1.sh'
535 /
3 /.git/config
...
1 www.facebook.com:443
2 www.google.com:443
1 www.ipip.net:443
となります。行頭の値はそれぞれ重複する数です。
sort
では、それぞれの出力をソートします。
uniq
では入力で隣り合う行が同じ内容の場合にまとめます。直前でsrot
するのはこのためです。
uniq -c
ではその重複を数えて一緒に表示します。
またコマンドA | B
ではA
の出力をB
の入力として実行するようです。(この辺は私は詳しくないので有識者の方教えてください。。。)
ただこの出力も膨大な行数になります。目視で最大値を見つけても良いのですがもう少し賢く見つけましょう。はい、sort
します。
% awk '{print $6}' access_log | sort | uniq -c | sort
この出力(最初と最後3行)はこんな感じです。
1 '/script1.sh'
1 /.well-known/security.txt
1 /AuPV
...
2311 /wp/wp-login.php
2316 /cms/wp-login.php
2326 /wp1/wp-login.php
これでほぼ答えに辿り着きました。あとは問題1同様'/wp1/wp-login.php'で検索すると出てきます。
FLAGはWordPress
になります。
問題3
問題
ログファイル中の2020/06/19~2020/06/20の/manager/html へのアクセス件数を答えてください。
解答例
とりあえず該当期間のデータを抽出する必要がありそうです。grep
しましょう。
grep
コマンドとは入力された文字列を含む行を出力するコマンドです。例えば、% grep /wp1/wp-login.php access_log
を実行すると
/wp1/wp-login.php
へのアクセスログのみを全て出力します。
ただ、このときに日付は2020-06-19
2020-06-20
の2つあり、単純なgrep
では実現できません。
ちょっと工夫して
% grep "2020-06-\(19\|20\)" access_log
とすると、最後の(19|20)
は19
または20
のどちらかということになります。
\
は()
や|
が文字列として認識されるのではなく、演算子として認識されるための記号です。正規表現です。
このコマンドの結果も膨大な量になります。確認するのが面倒なので
% grep "2020-06-\(19\|20\)" access_log | (head -1; tail -1)
を実行すると最初と最後の1行だけを表示してくれます。[参考文献]
こうすると出力は、[2020-06-19 00:11:34+0900] xxx.xxx.xxx.xxx blank:80 "GET / HTTP/1.1" 200 False R0VU******************** [2020-06-20 23:49:19+0900] yyy.yyy.yyy.yyy YYY.YYY.YYY.YYY:80 "GET / HTTP/1.0" 200 False R0VU****************************************************************************************************
となります。問題なさそうです。
次は/manager/html
へのアクセスを抽出にgrep
します。
% grep "2020-06-\(19\|20\)" access_log | grep "/manager/html"
あとは数えるだけです。wc
コマンドを使って
% grep "2020-06-\(19\|20\)" access_log | grep "/manager/html" | wc
出力は
676 6760 249955
となり、676(=アクセス数)であることがわかりました。
wc
コマンドでは入力に対してline, word, byte
の順で出力されます。
FLAGは676
になります。
懺悔
この記事の著者はこの問題を力技で解いたなどと供述しています。具体的にはテキストエディタにて手作業で
非常に反省しています。
問題4
問題
ログファイル中の /manager/html へのアクセスで、一番多く試行されたパスワードを調べ、その試行回数を答えてください。
解答例
パスワードどこ?????? って最初はなるかと思います。なりましたよね?
例として、一番最初に挙げたログをもう一度見てみます。
[2020-06-19 10:09:11+0900] XXX.XXX.XXX.XXX YYY.YYY.YYY.YYY:PP "GET /manager/html HTTP/1.1" 401 1032 R0VU************************************************************************************************************************
どこ??????
ただ、1箇所だけ内容がわからないところがあります。そうです、最後のR0VU...
のところです。ここは一見するとランダムな文字列ですが、base64と呼ばれる変換をした文字列になっています。
この部分をデコード(変換を元に戻すこと)すると
GET /manager/html HTTP/1.1
Connection: close
Authorization: Basic dG9tY2F0OmJvdGg=
User-Agent: Java/1.8.0_131
Host: XXX.XXX.XXX.XXX
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
意味のありそうな文字列が出てきます。
(XXX.XXX.XXX.XXX
のところは隠していますが具体的な値が入っています。)
この中にパスワードの隠し場所なりそうな箇所は... Authorization
しかなさそうです。ここもbase64になってそうな雰囲気があるのでデコードすると
tomcat:both
アアアアアアアアってなりましたか?私はなりました。
他のログについても同様に解析してみると、admin:tomcat
のような出力があります。admin
はユーザ名に使われることが多く、:
の前に書いていることからもユーザ名:パスワード
の順になっている気がします。
これでやっとパスワードを見つけることができたのでFLAG発見 ...にはならず、全てのログに対して同様の処理を施し、パスワードを見つけて、最も多いものを探さなければなりません。
ただこれまでの問題を解いてきたので簡単にできるはずです。
まずは全てのログをデコードするところから。
% grep "/manager/html" access_log | awk '{print $10}' | nkf -mBW
出力は長いので省略しますが、全ての該当するログに対してデコードできました。
nkf -mBW
はデコードするコマンドです。
なおnkf
はhomebrewなどでインストールが必要です。
ここからAuthorization
を抽出し、
% grep "/manager/html" access_log | awk '{print $10}' | nkf -mBW | grep "Authorization"
Authorization: Basic YWRtaW46
Authorization: Basic YWRtaW46YWRtaW4=
Authorization: Basic YWRtaW46MTIzNDU=
...
Authorization: Basic dGVzdDphc2RmZ2hqa2w=
Authorization: Basic YWRtaW5pc3RyYXRvcjpxcTEyMzQ1Ng==
Authorization: Basic YWRtaW5pc3RyYXRvcjphc2RmZ2hqa2w=
この第3成分を抽出してデコードすると...
% grep "/manager/html" access_log | awk '{print $10}' | nkf -mBW | grep "Authorization" | awk '{print $3}' | nkf -mBW
admin:admin:adminadmin:12345admin...(中略)...qq123456administrator:asdfghjkl%
全て1行にまとめて出してきやがります。 そう簡単には思い通りになってくれないのがプログラミングです。このままでは最も多く試行されたパスワードは分かりません。
もうコマンドラインで無理に済ませようとするのは諦めてpythonに逃げましょう。
一度デコードすべきデータをテキストファイルに保存します。
% grep "/manager/html" access_log | awk '{print $10}' | nkf -mBW | grep "Authorization" | awk '{print $3}' > userid_password_encoded.txt
COMMAND > FILENAME
を用いることで、COMMAND
実行結果をFILENAME
に記録することができます。
試しに% cat userid_password_encoded.txt
を実行すると、
YWRtaW46 YWRtaW46YWRtaW4= YWRtaW46MTIzNDU= ... dGVzdDphc2RmZ2hqa2w= YWRtaW5pc3RyYXRvcjpxcTEyMzQ1Ng== YWRtaW5pc3RyYXRvcjphc2RmZ2hqa2w=
確かに記録されていることがわかります。
あとはpythonのコードですが、
import base64
with open('./userid_password_decoded.txt', 'w', encoding='UTF-8') as w:
with open('./userid_password_encoded.txt') as f:
for line in f:
w.write(base64.b64decode(line).decode()) # ここでデコードして
w.write('\n') # ここで改行を入れている
こうすることで改行を入れます。
そろそろゴールです。pythonを実行し、出力ファイルuserid_password_decoded.txt
を集計して
% python3 decode_password.py
% awk -F ':' '{print $2}' userid_password_decoded.txt | sort | uniq -c | sort
awk -F ':'
は:
を区切り文字とする、という意味です。
出力は
1 12345
6
17 role1
...
22 admin
22 manager
22 tomcat
長かったですがついにフラグに辿り着きました。
FLAGは22
になります。
私はログ分析の問題群の中で唯一、この問題4をCTFの時間内に解くことができませんでした。
謎
% grep "/manager/html" access_log | awk '{print $10}' | base64 -d
を実行すると途中までは良いのですが、あるところでなぜか
Invalid character in input stream.
となります。(この理由をご存知のつよつよエンジニアの方は教えて下さい。)
問題5
問題
ログファイルから一日のアクセス件数が一番多い日を調べ、その日の中で一番アクセス数が多い送信元IPアドレスを答えてください。
解答例
これまでの問題を解いてきたのであれば、なんとなく解法が見えてきます。
まずはアクセス件数が一番多い日を調べます。
% awk '{print $1}' access_log | sort | uniq -c | sort
82 [2020-06-14
95 [2020-06-15
99 [2020-06-17
116 [2020-06-22
243 [2020-06-16
383 [2020-06-18
899 [2020-06-19
13896 [2020-06-20
20447 [2020-06-21
これより2020/06/21について調べると良さそうです。
次はこの日の送信元IPアドレスを調べます。ただそれぞれのログにはIPアドレスが2つあります。今回のCTFでは誤答しても何もないので両方試しても良いのですがどちらが送信元かを考えます。
私もアクセスログは初めて見たのでわからなかったのですが、後者のIPアドレスにはポート番号がついていることから、アクセス先アドレスではないかと推測しました(詳しい人教えて下さい)。
とりあえず前者のIPアドレスについて調べると
% grep "2020-06-21" access_log | awk '{print $3}' | sort | uniq -c | sort
...
351 185.128.41.50
これよりFLAGは185.128.41.50
になります。
この記事は終了しました
扱ったコマンドの一覧
- 検索エンジン:偉大。
-
awk
:1行における任意のn番目の要素を抽出しました。 -
sort
:出力を順に並べました。 -
uniq
:重複を解消したり数えたりしました。 -
grep
:特定の文字列を含む行を抽出しました。 -
wc
:行を数えました。 -
nkf
:base64などの変換をしました。
最後に
筆者はアホなのでCTF中にメモを取るのを忘れており、そのためFLAGが違うかもしれません。
最後になりますがSendaiCTFの運営の方々ありがとうございました。
稚拙な文章と低すぎる技術力で構成されているこの記事が運営の方々に見つからないことを祈ります。