この大会は2024/10/26 16:00(JST)~2024/10/28 4:00(JST)に開催されました。
この大会は個人戦。結果は1900点で126人中23位でした。
非常に残念。もう少し上位に行きたかった。
スコアのカテゴリごとの分布はこんな感じ。

1問も解けなかった問題カテゴリはReverse EngineeringとWeb Security。
Reverse Engineeringでもう1問は論理的には合っているはずですが、printableな文字列にならないので、どこか間違っているのか。。。解くことができず残念です。
自分の解けた問題をWriteupとして書いておきます。
Welcome to UrchinSec (Miscellaneous 100)
Discordに入り、#welcomeチャネルのトピックを見ると、フラグが書いてあった。
urchinsec{welcome_to_UrCh1nSe(}
Follow Us (OSINT 100)
https://www.instagram.com/urchinsec_を見てみる。2つ目の画像にコメントがあるので、見てみると、フラグが書いてあった。

urchinsec{d0nt_f0rg3t_tO_f0ll0w_us}
Heart (Secure Code Reviewing 100)
コードの脆弱性のある行と、脆弱性の名前を答える問題。コードは以下の通り。
from flask import Flask, request, render_template_string app = Flask(__name__) @app.route('/name/<input_name>', methods=['GET']) def say_name(input_name): if request.method == 'GET': if input_name is not None: return render_template_string(f"Hello {input_name}") if __name__ == '__main__': app.run(host='127.0.0.1', port=5555)
9行目で入力値が{input_name}という形で渡されていて、SSTIの脆弱性がある。
urchinsec{9_SSTI}
RedHand (Secure Code Reviewing 100)
コードの脆弱性の名前を答える問題。コードは以下の通り。
<?=`$_GET[0]`?>
Web ShellでOSコマンドを実行できるので、Command Injectionと言える。
urchinsec{command_injection}
Syringe (Secure Code Reviewing 100)
コードの脆弱性のある行と、脆弱性の名前を答える問題。コードは以下の通り。
const express = require('express'); const mysql = require('mysql'); const app = express(); const connection = mysql.createConnection({ host: 'localhost', user: 'root', password: 'Sup3rStr0ngP@ssw0rd!', database: 'syringe_hospital' }); app.get('/get-patients', (req, res) => { const patient_name = req.query.patient_name; const query = `SELECT * FROM patients WHERE patient_name = '${patient_name}'`; connection.query(query, (error, results) => { if (error) throw error; res.send(results); }); }); app.listen(3000, () => { console.log('Server running on http://localhost:3000'); });
15行目で入力値が${patient_name}という形で渡されている。SQL Injectionの脆弱性がある。
urchinsec{15_SQLi}
10 Round (Forensics 100)
RARファイルと思われるが、先頭2~3バイト目が壊れているので、以下のように修復する。
00 00 → 61 72
解凍すると、flagファイルが展開される。
$ file flag flag: ARJ archive data, v11, slash-switched, created 27 aug 1980+51, original name: flag.arj, os: Unix $ mv flag flag.arj
7.zipで解凍すると、flagファイルが展開される。
$ file flag flag: Zstandard compressed data (v0.8+), Dictionary ID: None $ mv flag flag.zst $ zstd -d flag.zst flag.zst : 598 bytes $ file flag flag: LZMA compressed data, streamed $ mv flag flag.lzma $ xz --format=lzma --decompress flag.lzma xz: flag: Cannot set the file permissions: Value too large for defined data type $ file flag flag: 7-zip archive data, version 0.4 $ mv flag flag.7z $ 7z x flag.7z 7-Zip 23.01 (x64) : Copyright (c) 1999-2023 Igor Pavlov : 2023-06-20 64-bit locale=en_US.UTF-8 Threads:32 OPEN_MAX:1024 Scanning the drive for archives: 1 file, 598 bytes (1 KiB) Extracting archive: flag.7z -- Path = flag.7z Type = 7z Physical Size = 598 Headers Size = 130 Method = LZMA2:12 Solid = - Blocks = 1 Everything is Ok Size: 464 Compressed: 598 $ file flag.tar.xz flag.tar.xz: XZ compressed data, checksum CRC64 $ tar Jxfv flag.tar.xz flag.zip $ unzip flag.zip Archive: flag.zip extracting: flag $ file flag flag: XZ compressed data, checksum CRC64 $ mv flag flag.xz $ unxz flag.xz unxz: flag: Cannot set the file permissions: Value too large for defined data type $ file flag flag: bzip2 compressed data, block size = 900k $ mv flag flag.bz2 $ bzip2 -d flag.bz2 $ file flag flag: gzip compressed data, was "flag", last modified: Fri Oct 25 05:27:03 2024, from Unix, original size modulo 2^32 47 $ mv flag flag.gz $ gzip -d flag.gz gzip: flag: Value too large for defined data type $ file flag flag: ASCII text $ cat flag urchinsec{d0ubl3_c0mpr3s51on_1s_c00l_1cf5d3a2}
urchinsec{d0ubl3_c0mpr3s51on_1s_c00l_1cf5d3a2}
Log-ical - Part 1 (Forensics 100)
途中から以下のUserAgentでアクセスしているログがある。
Mozilla/5.0 (compatible; Nmap Scripting Engine; https://nmap.org/book/nse.html)
これはnmapで偵察していると思われる。攻撃元IPアドレスは10.0.100.13になっている。またリファラに10.0.100.2が含まれているログがあるので、これがターゲットのIPアドレスと考えられる。
urchinsec{10.0.100.13_10.0.100.2}
Open Letter (Forensics 300)
解凍し、word\settings.xmlを見ると、XMLデータの中に以下が含まれていた。
Here is the Admin password: urchinsec{w0rd2z1p_zip2w0rd_c9f2d3a0}
urchinsec{w0rd2z1p_zip2w0rd_c9f2d3a0}
Box (Cryptography 100)
スキュタレー暗号と推測し、4文字ごとに改行し縦に読む。
b__o oltx xio␣ _k_␣ ieb␣
box_i_like_to_box
urchinsec{box_i_like_to_box}
Destination (Cryptography 100)
ASCIIコードをマイナス1して、デコードする。
>>> s = '118 115 100 105 106 111 116 102 100 124 66 84 68 74 74 96 117 115 53 111 116 103 49 115 110 96 50 99 105 57 102 54 126'
>>> ''.join([chr(int(c) - 1) for c in s.split(' ')])
'urchinsec{ASCII_tr4nsf0rm_1bh8e5}'
urchinsec{ASCII_tr4nsf0rm_1bh8e5}
Shifty Business (Cryptography 100)
暗号の各値を右シフトでnの各値の和だけシフトすると、フラグの各文字のASCIIコードを2乗したものになる。このことを使って復号する。
#!/usr/bin/env python3 from gmpy2 import iroot with open('output.txt', 'r') as f: enc = eval(f.read()) flag = '' for c in enc: v = c >> (16 + 32 + 64 + 128) code, success = iroot(v, 2) assert success flag += chr(code) print(flag)
urchinsec{1t's_4all_ab0u+_e45y_3ncRyPT10N_e4c8a1}
Tr3ppl3 Stuffs (Cryptography 300)
n1とn2のGCDはpなので、q, rも算出することができる。あとはn3, n2, n1をモジュロとしたRSA暗号の復号を順に行っていけばフラグになる。
#!/usr/bin/env python3 from Crypto.Util.number import * with open('public-key.txt', 'r') as f: params = f.read().splitlines() n1 = int(params[0].split(' ')[1]) n2 = int(params[1].split(' ')[1]) n3 = int(params[2].split(' ')[1]) e = int(params[3].split(' ')[1]) c = int(params[4].split(' ')[1]) p = GCD(n1, n2) q = n1 // p r = n2 // p assert q * r == n3 ns = [n1, n2, n3] phis = [(p - 1) * (q - 1), (p - 1) * (r - 1), (q - 1) * (r - 1)] for i in range(len(ns)): d = inverse(e, phis[2 - i]) c = pow(c, d, ns[2 - i]) flag = long_to_bytes(c).decode() print(flag)
URCHINSEC{Wh00ps_tr1ppl3_RS4_15_N0t_3v3n_h4rd_s0met1mes}
WarmUp (Cryptography 300)
暗号処理の概要は以下の通り。
・message: 未知 ・knapsack = generate_knapsack() ・knapsack = [1, 2] ・以下6回繰り返し ・knapsackにknapsackの和にプラス1した値を追加 ・knapsackを返却 ・m = 257 ・n: -1000以上1000以下ランダム整数 ・ciphertext = encrypt_message(message, knapsack, m, n) ・bits = convert_to_bits(message) ・bits: messageを1文字ずつ8桁の2進数文字列にし、1ビットずつにしたものの配列 ・bitsを返却 ・chunk_size: knapsackの長さ ・chunks: bitsをchunk_sizeの長さごとにした配列 ・ciphertext = [] ・chunksの各chunkについて以下を実行 ・chunkの長さがchunk_sizeより小さい場合 ・chunkに[0]をパディング ・c_value: knapsackとchunkの各値の掛け算の和 ・encrypted_value = (c_value * n) % m ・ciphertextにencrypted_valueを追加 ・ciphertextを返却 ・ciphertextを出力
フラグが"urchinsec{"から始まることを前提にnを割り出す。あとはフラグの各文字でブルートフォースで暗号が一致するものを割り出し、復号する。
#!/usr/bin/env python3 from Crypto.Util.number import * def generate_knapsack(): knapsack = [1, 2] for i in range(6): knapsack.append(sum(knapsack) + 1) return knapsack def convert_to_bits(message): bits = [] for char in message: char_bits = bin(ord(char))[2:].zfill(8) bits.extend([int(b) for b in char_bits]) return bits knapsack = generate_knapsack() m = 257 with open('out.txt', 'r') as f: ciphertext = eval(f.read().split(': ')[1]) flag_head = 'urchinsec{' bits = convert_to_bits(flag_head) chunk_size = len(knapsack) chunks = [bits[i:i + chunk_size] for i in range(0, len(bits), chunk_size)] c_value = sum(k * b for k, b in zip(knapsack, chunks[0])) n = ciphertext[0] * inverse(c_value, m) % m for i in range(1, len(chunks)): c_value = sum(k * b for k, b in zip(knapsack, chunks[i])) tmp_n = ciphertext[i] * inverse(c_value, m) % m assert tmp_n == n flag = '' for i in range(len(ciphertext)): for code in range(32, 127): bit = bin(code)[2:].zfill(8) chunk = [int(c) for c in list(bit)] c_value = sum(k * b for k, b in zip(knapsack, chunk)) encrypted_value = (c_value * n) % m if encrypted_value == ciphertext[i]: flag += chr(code) break print(flag)
urchinsec{w000oow!!!_M4st3r_H0w_d1d_y0u_g3t_it????}