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にあるらしい。
検索を続けると、似たような写真を載せているサイトが。
海沿いだったので、球場の海沿いの辺りの緯度経度で手当たり次第に入力したら、なんとか正解をゲットできた。