kdnakt blog

hello there.

ImaginaryCTF 2023参加レポート&Writeups

7/22-7/24に開催されたImaginaryCTF 2023に参加した。

2023.imaginaryctf.org

[結果]

結果は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をみると想定解とは違いそうだが...まあヨシ。

github.com

blog.hamayanhamayan.com

[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()してファイルを取得している。しかし、.エンコードしても.のまま。どうしたものか、と思ってググっていると参考になりそうなサイトが。

blog.hamayanhamayan.com

../を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を使うという情報が。なるほど。

blog.hamayanhamayan.com

リクエストボディを次のようにすると、ちゃんとadminといううユーザー名でログインできた。

username=admin&password=\" UNION SELECT 10, \"admin\", \"\" ;

最後に/flagにアクセスしてフラグを無事ゲット。

[misc/signpost]

いわゆるOSINTの問題。以下の画像が与えられ、場所を特定してictf{緯度,経度}がフラグとなる。

とりあえず、なんとなく地名を検索すると、一番近いSEALS STADUIMがサンフランシスコにあるらしい。その辺の緯度経度を打ち込めば正解できそう。

Google Lensの画像検索すると、もう少し全体の見える画像が。

www.alamy.com

写真に写っているmccovey coveというのが100フィートなのでかなり近そう。地図で検索してみると、Oracle Park(旧AT&T Park)という球場の近くらしい。

こちらのサイトで別の写真を見つけた。やはりAT&T Parkにあるらしい。

www.alamy.it

検索を続けると、似たような写真を載せているサイトが。

chrisepting.medium.com

海沿いだったので、球場の海沿いの辺りの緯度経度で手当たり次第に入力したら、なんとか正解をゲットできた。