7/22-7/24に開催されたImaginaryCTF 2023に参加した。
[結果]
結果は700ポイントで286/880位だった。なんとか上位1/3には入っているのでまずまずか。
Discord、Sanity Checkのご祝儀問題はともかくとして、解けたのは100ポイントの簡単な問題ばかり。500点クラスの問題は全然歯が立たなかった。こういう問題も解いて数千点とるトップクラスのチームにはとても敵わない...。

相変わらずpwn系の問題は100ポイントの簡単な問題ですら解けなかった。他の人のWriteup見て勉強せねば...。
そういえば、最後のFeedback Surveyをやれば順位は少し上がったかもしれない。次は忘れないようにしよう...。
[web/inspection]
問題文はこちら。
Here's a freebie: the flag is ictf.
これだけ。添付ファイルやURLもなし。タイトルからして開発者ツールで見たら何かわかるかも?と思ってHTMLソースを開くとそれっぽい文字列が。フラグ形式はictf{...}なので、それに合わせて整形したフラグを提出して終わり。
[web/Idoriot]
問題文はこちら。
Some idiot made this web site that you can log in to. The idiot even made it in php. I dunno.
URLを開くと、ログインできるサイトが。適当にユーザー登録してログインすると、ソースコードが表示された。
session_start();
// Check if user is logged in
if (!isset($_SESSION['user_id'])) {
header("Location: login.php");
exit();
}
// Check if session is expired
if (time() > $_SESSION['expires']) {
header("Location: logout.php");
exit();
}
// Display user ID on landing page
echo "Welcome, User ID: " . urlencode($_SESSION['user_id']);
// Get the user for admin
$db = new PDO('sqlite:memory:');
$admin = $db->query('SELECT * FROM users WHERE user_id = 0 LIMIT 1')->fetch();
// Check if the user is admin
if ($admin['user_id'] === $_SESSION['user_id']) {
// Read the flag from flag.txt
$flag = file_get_contents('flag.txt');
echo "<h1>Flag</h1>";
echo "<p>$flag</p>";
} else {
// Display the source code for this file
echo "<h1>Source Code</h1>";
highlight_file(__FILE__);
}
なんとかして管理者権限でログインするとフラグが取れるらしい。とりあえずURLに/flag.txtをつけてアクセスしたらなぜかフラグが取れてしまった。
他の人のWriteupをみると想定解とは違いそうだが...まあヨシ。
[web/roks]
ソースコードとURLが与えられる。
コンテナのルートディレクトリにフラグ画像flag.pngがあり、画面上にはimagesディレクトリにある岩の画像がランダムに表示される仕組み。
問題のコードはこの辺。
// file.php <?php $filename = urldecode($_GET["file"]); if (str_contains($filename, "/") or str_contains($filename, ".")) { $contentType = mime_content_type("stopHacking.png"); header("Content-type: $contentType"); readfile("stopHacking.png"); } else { $filePath = "images/" . urldecode($filename); $contentType = mime_content_type($filePath); header("Content-type: $contentType"); readfile($filePath); } ?>
一度urldecode()したファイル名に対して、/や.が含まれないかをチェックして、さらにもう一度urldecode()してファイルを取得している。しかし、.はエンコードしても.のまま。どうしたものか、と思ってググっていると参考になりそうなサイトが。
../をURLエンコードすると%2e%2e%2fになる。これをエンコードすると%252e%252e%252fとなり、さらにもう一度エンコードすると%25252e%25252e%25252fとなる。あとはこれをたくさんくっつけて最後にflag.pngを追加してリクエストを送ると無事フラグが取得できた。
[web/blank]
ソースコードとURLが与えられる。
node.jsアプリで、/flagエンドポイントにユーザー名adminでアクセスするとフラグが取得できそう。
app.get('/flag', (req, res) => {
if (req.session.username == "admin") {
res.send('Welcome admin. The flag is ' + fs.readFileSync('flag.txt', 'utf8'));
}
else if (req.session.loggedIn) {
res.status(401).send('You must be admin to get the flag.');
} else {
res.status(401).send('Unauthorized. Please login first.');
}
});
ログイン情報の管理はデータベースで行われていたが、データをインサートしている箇所がない。
const db = new sqlite3.Database(':memory:');
db.serialize(() => {
db.run('CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT, password TEXT)');
});
app.post('/login', (req, res) => {
console.log(req.body)
const username = req.body.username;
const password = req.body.password;
db.get('SELECT * FROM users WHERE username = "' + username + '" and password = "' + password+ '"', (err, row) => {
if (err) {
console.error(err);
res.status(500).send('Error retrieving user');
} else {
if (row) {
req.session.loggedIn = true;
req.session.username = username;
res.send('Login successful!');
} else {
res.status(401).send('Invalid username or password');
}
}
});
});
はて...と思っていると、SQLを文字列結合で組み立てている箇所に目が行った。ここでINSERTとかできるのか?と思って;で区切って2つ目のSQLとしてインサート文を実行しようとしたが、これはうまくいかず。
何かないかな...と思って探していると、UNIONを使うという情報が。なるほど。
リクエストボディを次のようにすると、ちゃんとadminといううユーザー名でログインできた。
username=admin&password=\" UNION SELECT 10, \"admin\", \"\" ;
最後に/flagにアクセスしてフラグを無事ゲット。
[misc/signpost]
いわゆるOSINTの問題。以下の画像が与えられ、場所を特定してictf{緯度,経度}がフラグとなる。

とりあえず、なんとなく地名を検索すると、一番近いSEALS STADUIMがサンフランシスコにあるらしい。その辺の緯度経度を打ち込めば正解できそう。
Google Lensの画像検索すると、もう少し全体の見える画像が。
写真に写っているmccovey coveというのが100フィートなのでかなり近そう。地図で検索してみると、Oracle Park(旧AT&T Park)という球場の近くらしい。
こちらのサイトで別の写真を見つけた。やはりAT&T Parkにあるらしい。
検索を続けると、似たような写真を載せているサイトが。
海沿いだったので、球場の海沿いの辺りの緯度経度で手当たり次第に入力したら、なんとか正解をゲットできた。